[PYTHON] Eine Geschichte über den Versuch, einen Chot zu automatisieren, wenn Sie selbst kochen

Überblick

Es überwacht den angegebenen Dateipfad und benennt die PDF-Datei automatisch in den Titel des Buches um, wenn Sie dort eine PDF-Datei ablegen. :octocat:book_maker

Betrieb bestätigt Betriebssystem

macOS Catalina

Dinge notwendig

  $ brew install poppler
  $ brew install tesseract
  $ brew install tesseract-lang

Wie benutzt man

$ python3 src/watch.py input_path [output_path] [*extensions]

Warum gemacht

Ich beeilte mich, eine Schneidemaschine und einen Scanner zu kaufen, weil ich viele Bücher im Haus meiner Eltern verdauen wollte. Ich habe jedoch oft gehört, dass Selbstversorgung problematisch ist, deshalb wollte ich einen gewissen Grad an Automatisierung erreichen, deshalb habe ich dieses Programm erstellt.

Arbeitsablauf

Ich habe es im folgenden Ablauf zusammengebaut.

  1. Geben Sie das zu überwachende Verzeichnis an und starten Sie src / watch.py
  2. Legen Sie die PDF-Datei in das überwachte Verzeichnis
  3. Erkennen Sie das Ereignis und rufen Sie den ISBN-Code aus dem Inhalt der PDF-Datei ab
  1. Holen Sie sich Buchinformationen von jeder API basierend auf der ISBN --API, die Sie verwenden
  2. Korrigieren Sie den Dateinamen und verschieben Sie die PDF-Datei in das Ausgabeverzeichnis

Überwachen Sie ein bestimmtes Verzeichnis

Ich habe eine Bibliothek namens watchdog verwendet, um das Verzeichnis ständig zu überwachen. Die folgenden Dokumente und Artikel waren sehr hilfreich für die detaillierte Verwendung von "Watchdog". Vielen Dank.

--watchdog offizielles Dokument


Um "Watchdog" zu verwenden, benötigen Sie "Handler" und "Observer". Handler beschreibt, was zu tun ist und wie jedes Ereignis zu behandeln ist (Erstellen / Löschen / Verschieben / Ändern). Dieses Mal wird nur die Funktion "on_created" definiert, die zum Zeitpunkt der Erstellung das Ereignis ist. Diese "on_created" -Methode überschreibt die Methode in der "FileSystemEventHandler" -Klasse in "watchdog.event".

src/handler/handler.py


from watchdog.events import PatternMatchingEventHandler

class Handler(PatternMatchingEventHandler):
    def __init__(self, input_path, output_path, patterns=None):
        if patterns is None:
            patterns = ['*.pdf']
        super(Handler, self).__init__(patterns=patterns,
                                      ignore_directories=True,
                                      case_sensitive=False)

    def on_created(self, event):
        #Etwas tun

Es definiert eine Handler-Klasse und erbt "PatternMatchingEventHandler", wodurch der Mustervergleich ermöglicht wird. Auf diese Weise können Sie die Dateitypen einschränken, die von Ereignissen erkannt werden. Es gibt auch einen RegexMatchingEventHandler, mit dem Sie Muster für reguläre Ausdrücke verwenden können. Dieses Mal wollte ich eine Verarbeitung durchführen, die nur auf PDF beschränkt ist, also habe ich "pattern = ['* .pdf']" gesetzt. Ich habe "ignore_directories = True" gesetzt, um das Verzeichnis zu ignorieren, und ich wollte sowohl "* .pdf" als auch "* .PDF" erkennen können, also habe ich "case_sensitive = False" gesetzt.


Bereiten Sie als Nächstes "Observer" vor, der für die Überwachung des Handlers verantwortlich ist.

src/watch.py


from watchdog.observers import Observer
from src.handler.handler import Handler


def watch(input_path, output_path, extensions):
    print([f'*.{extension}' for extension in extensions], flush=True)
    event_handler = Handler(input_path=input_path,
                            output_path=output_path,
                            patterns=[f'*.{extension}' for extension in extensions])
    observer = Observer()
    observer.schedule(event_handler, input_path, recursive=False)
    observer.start()
    print('--Start Observer--', flush=True)
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.unschedule_all()
        observer.stop()
        print('--End Observer--', flush=True)
    observer.join()

Beschreiben Sie im erstellten Observer-Objekt, ob das Handler-Objekt, das überwachte Verzeichnis und die Unterverzeichnisse rekursiv überwacht und erstellt werden sollen. Starten Sie die Überwachung mit Observer.start () und lassen Sie sie mit der Anweisung while undtime.sleep (1)laufen, um den Vorgang fortzusetzen. Wenn "Strg + C" gedrückt wird, beendet "Observer.unschedule_all ()" die gesamte Überwachung, löst den Ereignishandler und "Observer.stop ()" weist den Thread an, anzuhalten. Verwenden Sie abschließend Observer.join (), um zu warten, bis der Thread beendet ist.

Holen Sie sich den ISBN-Code mithilfe der Shell aus dem Barcode

Ich habe auf diesen Blog verwiesen. Vielen Dank.

Versuchen Sie beim Abrufen des ISBN-Codes, ihn aus dem Barcode abzurufen. Diejenigen, die ich verwendet habe, um die Informationen aus dem PDF zu erhalten, sind "pdfinfo", "pdfimages" und "zbarimg". pdfinfo gibt die Gesamtzahl der Seiten im PDF an. pdfimages soll nur die erste und letzte Seite jpeg machen, basierend auf den Gesamtseiten, die von pdfinfo erhalten wurden. zbarimg wurde verwendet, um den ISBN-Code aus dem von pdfimages generierten JPEG abzurufen.

getISBN.sh


#!/bin/bash

# Number of pages to check in PDF
PAGE_COUNT=1
# File path
FILE_PATH="$1"

# If the file extension is not pdf
shopt -s nocasematch
if [[ ! $1 =~ .+(\.pdf)$ ]]; then
  exit 1
fi
shopt -u nocasematch

# Delete all .image* generated by pdfimages
rm -f .image*

# Get total count of PDF pages
pages=$(pdfinfo "$FILE_PATH" | grep -E "^Pages" | sed -E "s/^Pages: +//") &&
# Generate JPEG from PDF
pdfimages -j -l "$PAGE_COUNT" "$FILE_PATH" .image_h &&
pdfimages -j -f $((pages - PAGE_COUNT)) "$FILE_PATH" .image_t &&
# Grep ISBN
isbnTitle="$(zbarimg -q .image* | sort | uniq | grep -E '^EAN-13:978' | sed -E 's/^EAN-13://' | sed 's/-//')" &&
# If the ISBN was found, echo the ISBN
[ "$isbnTitle" != "" ] &&
echo "$isbnTitle" && rm -f .image* && exit 0 ||
# Else, exit with error code
rm -f .image* && exit 1

Wenn der ISBN-Code erhalten wird, wird schließlich "echo" $ isbnTitle "als Standardausgabe auf der Python-Seite empfangen.

Auch das&&Oder||Ich habe die Bedeutung nicht gut verstanden, aber der folgende Artikel war hilfreich. Vielen Dank.

Verwenden Sie Python, um den ISBN-Code abzurufen

Holen Sie sich aus Barcode

Um vom Barcode pdf2image zu gelangen, um das PDF abzubilden, und um vom Barcode pyzbar zu gelangen. pyzbar) wurde verwendet.

Generieren Sie mit pdf2image Bilder von jpeg für 2 Seiten, die von der letzten Seite zählen, rufen Siedecode ()with pyzbar für diese Bilder auf und verwenden Sie das reguläre Ausdrucksmuster des ISBN-Codes ( Wenn es eine Zeichenfolge gibt, die mit ^ 978) übereinstimmt, wird sie zurückgegeben.

Ich habe "TemporaryDirectory ()" verwendet, weil ich wollte, dass das Verzeichnis die generierten Bilder temporär macht.

src/isbn_from_pdf.py


import re
import sys
import tempfile
import subprocess
from pyzbar.pyzbar import decode
from pdf2image import convert_from_path

input_path = input_path
texts = []
cmd = f'echo $(pdfinfo "{input_path}" | grep -E "^Pages" | sed -E "s/^Pages: +//")'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
total_page_count = int(result.stdout.strip())

with tempfile.TemporaryDirectory() as temp_path:
    last_pages = convert_from_path(input_path,
                                    first_page=total_page_count - PAGE_COUNT,
                                    output_folder=temp_path,
                                    fmt='jpeg')
    # extract ISBN from using barcode
    for page in last_pages:
        decoded_data = decode(page)
        for data in decoded_data:
            if re.match('978', data[0].decode('utf-8', 'ignore')):
                return data[0].decode('utf-8', 'ignore').replace('-', '')

Holen Sie sich aus dem Text

Eine andere Möglichkeit besteht darin, den ISBN-Code von der letzten Seite des Buches zu extrahieren, die Informationen wie den Herausgeber und die Ausgabe des Buches enthält.

Ich habe pyocr verwendet, um die Zeichenfolge aus dem Bild zu extrahieren. Um pyocr verwenden zu können, benötigen Sie das OCR-Tool. Daher müssen Sie Googles tesseract installieren.

src/isbn_from_pdf.py


import re
import sys
import pyocr
import tempfile
import subprocess
import pyocr.builders
from pdf2image import convert_from_path

input_path = input_path
texts = []
cmd = f'echo $(pdfinfo "{input_path}" | grep -E "^Pages" | sed -E "s/^Pages: +//")'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
total_page_count = int(result.stdout.strip())

with tempfile.TemporaryDirectory() as temp_path:
    last_pages = convert_from_path(input_path,
                                    first_page=total_page_count - PAGE_COUNT,
                                    output_folder=temp_path,
                                    fmt='jpeg')
    tools = pyocr.get_available_tools()
    if len(tools) == 0:
        print('[ERROR] No OCR tool found.', flush=True)
        sys.exit()

    # convert image to string and extract ISBN
    tool = tools[0]
    lang = 'jpn'
    for page in last_pages:
        text = tool.image_to_string(
            page,
            lang=lang,
            builder=pyocr.builders.TextBuilder(tesseract_layout=3)
        )
        texts.append(text)
    for text in texts:
        if re.search(r'ISBN978-[0-4]-[0-9]{4}-[0-9]{4}-[0-9]', text):
            return re.findall(r'978-[0-4]-[0-9]{4}-[0-9]{4}-[0-9]', text).pop().replace('-', '')

Holen Sie sich Buchinformationen von jeder API

Um die Informationen des Buches zu erhalten, habe ich zwei der Google Books-APIs und openBD verwendet. tat.

Beide sind im JSON-Format erhältlich. Da sie jedoch unterschiedliche Formen haben, wollte ich Code schreiben, der so häufig wie möglich ist. Deshalb habe ich eine Bibliothek namens Box verwendet. Ich tat.

Box soll es Ihnen ermöglichen, das zu bekommen, was Sie normalerweise mit dict.get ('key') unddict ['key']with dict.key.another_key erhalten würden. .. Sie können auch "dict ['key']" verwenden.

Weitere Funktionen sind die Möglichkeit, dass "key" einen Kamelfall ("camelCase") in eine Python-Namenskonvention für Schlangenfälle ("snake_case") konvertiert und "key" ein Raum wie "persönliche Gedanken" ist. Es gibt auch eine praktische Funktion, mit der Sie wie "dict.personal_ Thoughts" darauf zugreifen können, wenn es eine gibt.

Unten ist der Code von openBD.

src/bookinfo_from_isbn.py


import re
import json
import requests
from box import Box

OPENBD_API_URL = 'https://api.openbd.jp/v1/get?isbn={}'

HEADERS = {"content-type": "application/json"}

class BookInfo:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f'<{self.__class__.__name__}>{json.dumps(self.__dict__, indent=4, ensure_ascii=False)}'


def _format_title(title):
    #Ersetzen Sie Klammern in voller Breite und Leerzeichen in voller Breite durch Leerzeichen in halber Breite
    title = re.sub('[() ]', ' ', title).rstrip()
    #Ersetzen Sie ein oder mehrere Felder mit halber Breite durch eines
    return re.sub(' +', ' ', title)


def _format_author(author):
    #Löschen Sie die Zeichenfolge nach / geschrieben
    return re.sub('/.+', '', author)


def book_info_from_openbd(isbn):
    res = requests.get(OPENBD_API_URL.format(isbn), headers=HEADERS)
    if res.status_code == 200:
        openbd_res = Box(res.json()[0], camel_killer_box=True, default_box=True, default_box_attr='')
        if openbd_res is not None:
            open_bd_summary = openbd_res.summary
            title = _format_title(open_bd_summary.title)
            author = _format_author(open_bd_summary.author)
            return BookInfo(title=title, author=author)
    else:
        print(f'[WARNING] openBD status code was {res.status_code}', flush=True)

Da der Titel des erworbenen Buches und die Informationen des Autors mit voller und halber Breite gemischt werden, haben wir eine Funktion vorbereitet, um jedes zu korrigieren. (_format_title _ format_author) Ich habe es noch nicht geschnitten und ausprobiert, daher müssen diese Funktionen angepasst werden.

In "Box", "camel_killer_box = True", das einen Kamelfall in einen Schlangenfall umwandelt, und "default_box = True" und "default_box_attr =", selbst wenn kein Wert vorhanden ist.

Korrigieren Sie den Dateinamen und wechseln Sie in das entsprechende Verzeichnis

Stellen Sie beim Starten zunächst sicher, dass Sie einen Ordner erstellen, der nach dem Umbenennen der PDF-Datei verschoben werden soll.

src/handler/handler.py


import os
import datetime
from watchdog.events import PatternMatchingEventHandler

class Handler(PatternMatchingEventHandler):
    def __init__(self, input_path, output_path, patterns=None):
        if patterns is None:
            patterns = ['*.pdf']
        super(Handler, self).__init__(patterns=patterns,
                                      ignore_directories=True,
                                      case_sensitive=False)
        self.input_path = input_path
        # If the output_path is equal to input_path, then make a directory named with current time
        if input_path == output_path:
            self.output_path = os.path.join(self.input_path, datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))
        else:
            self.output_path = output_path
        os.makedirs(self.output_path, exist_ok=True)

        # Create tmp directory inside of output directory
        self.tmp_path = os.path.join(self.output_path, 'tmp')
        os.makedirs(self.tmp_path, exist_ok=True)

Wenn der Prozess gestartet wird, wird ein Zielordner erstellt, der mit dem heutigen Datum oder einem angegebenen Zielordner formatiert ist. Erstellen Sie dann einen tmp-Ordner im Ausgabezielordner, der bei einem Fehler abgelegt werden soll (wenn dasselbe PDF-Buch vorhanden ist, wenn keine ISBN gefunden wird, wenn keine Buchinformationen vorhanden sind). ..


src/handler/handler.py


    def __del__(self):
        # Delete the tmp directory, when the directory is empty
        tmp_files_len = len(os.listdir(self.tmp_path))
        if tmp_files_len == 0:
            os.rmdir(self.tmp_path)

        # Delete the output directory, when the directory is empty
        output_files_len = len(os.listdir(self.output_path))
        if output_files_len == 0:
            os.rmdir(self.output_path)

Wenn der Vorgang abgeschlossen ist, beschreiben Sie die Methode del, damit eine Datei im Ausgabezielordner / tmp-Ordner verbleibt und gelöscht wird, wenn sie nicht vorhanden ist.


src/handler/handler.py


import shutil
import subprocess
from src.isbn_from_pdf import get_isbn_from_pdf, NoSuchISBNException
from src.bookinfo_from_isbn import book_info_from_google, book_info_from_openbd, NoSuchBookInfoException

    def on_created(self, event):
        print('!Create Event!', flush=True)
        shell_path = os.path.join(os.path.dirname(__file__), '../../getISBN.sh')
        event_src_path = event.src_path
        cmd = f'{shell_path} {event_src_path}'
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
        try:
            if result.returncode == 0:
                # Retrieve ISBN from shell
                isbn = result.stdout.strip()
                print(f'ISBN from Shell -> {isbn}', flush=True)
                self._book_info_from_each_api(isbn, event_src_path)

            else:
                # Get ISBN from pdf barcode or text
                isbn = get_isbn_from_pdf(event_src_path)
                print(f'ISBN from Python -> {isbn}', flush=True)
                self._book_info_from_each_api(isbn, event_src_path)

        except (NoSuchISBNException, NoSuchBookInfoException) as e:
            print(e.args[0], flush=True)
            shutil.move(event_src_path, self.tmp_path)
            print(f'Move {os.path.basename(event_src_path)} to {self.tmp_path}', flush=True)

Die Methode "on_created" beschreibt den Gesamtablauf im Workflow.

Führen Sie beim Ausführen der Shell die Shell mit "subprocess.run ()" aus, um die Standardausgabe zu erhalten, erhalten Sie den Shell-Status von "result.returncode" und empfangen Sie die Standardausgabe mit "result.stdout". Kann gemacht werden

Außerdem wird beim Abrufen von Buchinformationen aus dem ISBN-Code eine spezielle Ausnahme ausgelöst.

Zusammenfassung

Vielen Dank, dass Sie so weit gelesen haben. Ich hatte Probleme mit dem Ort, an dem der Befehl und der Variablenname / Funktionsname gestartet werden sollten, aber ich habe es geschafft, ihn zur Mindestform zu machen. Derzeit wird nur PDF unterstützt, aber ich möchte epub unterstützen können. Ich möchte es auch unter Windows tun können.

Wenn es Tippfehler oder Fehler gibt, ist dies der richtige Weg! Bitte lassen Sie mich wissen, wenn Sie welche haben. Vielen Dank.

Recommended Posts

Eine Geschichte über den Versuch, einen Chot zu automatisieren, wenn Sie selbst kochen
Eine Geschichte über den Versuch, private Variablen in Python zu implementieren.
Eine Geschichte über den Versuch, mehrere Python-Versionen auszuführen (Mac Edition)
Eine Geschichte, die es aufgegeben hat, JavaScripthon unter Windows auszuführen.
Die Geschichte, den Versuch aufzugeben, mit Heroku eine Verbindung zu MySQL herzustellen
Eine Geschichte über einen Anfänger, der sich bemüht, CentOS 8 einzurichten (Verfahrensnotiz)
Eine Geschichte, die unter einem Unterschied im Betriebssystem litt, als sie versuchte, ein Papier zu implementieren
Eine Geschichte über den Versuch, den Testprozess eines 20 Jahre alten Systems in C zu verbessern
Eine Geschichte über einen Versuch, uwsgi auf einer fehlgeschlagenen EC2-Instanz zu installieren
Eine Geschichte, die vorbereitet werden musste, wenn versucht wurde, ein Django-Tutorial mit einfachen Centos7 zu erstellen
Eine Geschichte, die fehlgeschlagen ist, als versucht wurde, das Suffix mit rstrip aus einem String zu entfernen
Eine Geschichte, die beim Versuch, die Python-Version mit GCE zu aktualisieren, hängen blieb
Eine Geschichte über das Ausprobieren eines (Golang +) Python-Monorepo mit Bazel
Eine Geschichte über einen Python-Anfänger, der versucht, Google-Suchergebnisse mithilfe der API abzurufen
Eine Geschichte über das Problem, 3 Millionen ID-Daten in einer Schleife zu verarbeiten
Wenn Sie in der for-Anweisung plt.save möchten
Eine Geschichte über den Versuch, Linter mitten in einem Python (Flask) -Projekt vorzustellen
[Hinweis] Eine Geschichte über den Versuch, eine Klassenmethode mit zwei Unterbalken in der Python 3-Serie zu überschreiben.
[Django] Eine Geschichte über das Feststecken in einem Sumpf beim Versuch, einen Reißverschluss mit einem Formular zu validieren [TDD]
Eine Geschichte darüber, wie man einen relativen Pfad in Python angibt.
[Python] Hinweise beim Versuch, Numpy mit Cython zu verwenden
Eine Geschichte über den Umgang mit dem CORS-Problem
Eine Geschichte über einen Krieg, als zwei Neuankömmlinge eine App entwickelten
Eine Geschichte über einen 40-jährigen Ingenieurmanager, der "Deep Learning for ENGINEER" bestanden hat
Eine Geschichte, in der der Algorithmus zu einem lächerlichen Ergebnis kam, als er versuchte, das Problem der reisenden Verkäufer richtig zu lösen
Über den Fehler, den ich beim Versuch, Adafruit_DHT von Python auf Raspberry Pi zu verwenden, festgestellt habe
Eine Geschichte über das Hinzufügen einer REST-API zu einem mit Python erstellten Daemon
Eine Geschichte darüber, wie man in GAE / P über verstümmelte Charaktere nachdenken möchte
Eine Geschichte über den Versuch, Katsuo Isono, der nicht auf Unannehmlichkeiten reagiert, durch Verarbeitung natürlicher Sprache zu reproduzieren.
[AtCoder für Anfänger] Sprechen Sie über den Rechenaufwand, den Sie grob wissen möchten
Eine Geschichte, in der ein Anfänger beim Versuch, eine Vim 8.2 + Python 3.8.2 + Lua-Plug-In-Umgebung unter Ubuntu 18.04.4 LTS zu erstellen, nicht weiterkommt
Eine erfrischende Geschichte über Slice in Python
Eine launische Geschichte über Slice in Python
Eine Geschichte, die von Azure Pipelines abhängig ist
UnicodeEncodeError beim Versuch, Radon auszuführen
Die Geschichte der Verwendung von Python reduziert
Dinge, auf die Sie beim Erstellen einer Python-Umgebung auf einem Mac achten sollten
Eine Geschichte, die mich süchtig nach dem Versuch machte, LightFM unter Amazon Linux zu installieren
Eine Geschichte, die ich süchtig danach war, eine Video-URL mit Tweepy zu bekommen
[Memorandum] Eine Geschichte über das Ausprobieren des OpenCV-Tutorials (Gesichtserkennung) in einer Windows-Umgebung
Eine Geschichte über einen Anfänger im Deep Learning, der versucht, Gitarren mit CNN zu klassifizieren
Ich erhalte einen UnicodeDecodeError, wenn ich versuche, mit Python sqlalchemy eine Verbindung zu Oracle herzustellen
Ein Hinweis bei der Suche nach einer Alternative zu Pandas, die für ein bewegliches Fenster rollen