PDF化したFAXの概要をOCR(Python + Tesseract)で読み取ってメール送信する

6 min read読了の目安(約5600字

TL;DR;

駆逐してやる!!......一枚残らず!!(FAXを)

訳:受信したFAX(PDF)を手動で振り分けたり、PDFファイルの2重管理を止めたかった=DXしたかった

背景

  • FAXは注文書、請求書、その他の迷惑広告などを受信する
  • 会社宛に届いたFAXは複合機の機能によりネットワーク上のフォルダ(\\SHARED-FOLDER\fax)にPDFで保存される
  • PDFが生成されると、監視アプリケーションによりPDFが自動で開かれる
  • 宛先がわかる場合、各社員のフォルダに手動でドラッグ&ドロップする
  • 注文書などのPDFは複製する運用(FAXの原本を誤って削除することを防ぐ)

目標

  • FAXを受信したら(PDF化されたら)その旨をメールで通知する
  • メールには送信元、ドキュメントの種類、OCRで抽出したテキスト情報を載せる
  • メールにはPDF化されたFAXを添付する
  • コンソールには簡素なログを記録する


PDFを開くことなく送信元やFAXの種類がわかるので情報の取捨選択が捗る。転送も楽ちん。メール送信することで原本のPDFがメールサーバに保存されるので誤操作による消滅も防止。

環境構築

24時間稼働するWindows10に以下をインストールする。

この記事を執筆時点のPopplerの最新バージョンは0.68.0だがうまく動作しなかったので、サンプルコードでは0.67.0を使用している。

Pythonのインストールを終えたら、コマンドプロンプトで以下をインストールする。

python -m pip install pdf2image
python -m pip install pyocr
python -m pip install watchdog

ソースコードなど

GitHub

Pythonスクリプトとpoppler-0.67.0フォルダ、imgフォルダを同じ階層に置く。
\\path_to\\\pdf_to_email.pyで監視開始。\\\\SHARED-FOLDER\\faxにPDFが生成されたら発火。

import os, sys
from pathlib import Path
from pdf2image import convert_from_path
from PIL import Image
import pyocr
import pyocr.builders
import re
import smtplib
import email
from email.mime.text import MIMEText
from email.utils import formatdate
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from os.path import basename
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
import time
import datetime

path_tesseract = "C:\\Program Files\\Tesseract-OCR"
poppler_dir = Path(__file__).parent.absolute() / "poppler-0.67.0/bin"
MAIL_ADDRESS = "youraddress@gmail.com"
PASSWORD = "password"
from_address = MAIL_ADDRESS
to_address = "info@company.co.jp"
wdDirectory = '\\\\SHARED-FOLDER\\fax'

# poppler/binを環境変数PATHに追加する
os.environ["PATH"] += os.pathsep + str(poppler_dir)

class ChangeHandler(FileSystemEventHandler):

    # 作成された時のイベント
    def on_created(self, event):
        print(datetime.datetime.now().strftime('%Y年%m月%d日 %H:%M:%S') + ":新しいファイルを検出しました。")
        file_path = event.src_path
        file_name = os.path.basename(file_path)
        root, ext = os.path.splitext(file_path)
        if ext == ".pdf":
            print("PDFファイルです。OCR処理を開始します")
            converPDFAndSendEMail(file_path)
        else:
            print("PDFではありません。")

def converPDFAndSendEMail(pdf_file_path):
    pdf_path = Path(pdf_file_path)

    print("パス: " + pdf_file_path)

    # PDF -> Image に変換(150dpi)
    pages = convert_from_path(str(pdf_path), 150)

    # 画像ファイルを1ページずつ保存
    image_dir = Path(wdDirectory + "\\img")
    for i, page in enumerate(pages):
        file_name = pdf_path.stem + "_{:02d}".format(i + 1) + ".png"
        image_path = image_dir / file_name
        page.save(str(image_path), "PNG")

    if path_tesseract not in os.environ["PATH"].split(os.pathsep):
        os.environ["PATH"] += os.pathsep + path_tesseract

    # OCRエンジンの取得
    tools = pyocr.get_available_tools()
    tool = tools[0]

    img_org = Image.open(image_path)

    # OCR実行
    builder = pyocr.builders.TextBuilder()
    result = tool.image_to_string(img_org, lang="jpn", builder=builder)
    result = re.sub(r"\n+", "", result)
    result = re.sub('([あ-んア-ン一-龥ー、。]) +((?=[あ-んア-ン一-龥ー、。]))',
          r'\1\2', result)

    type = "注文書" if len(re.findall('注文書|発注書|注文申し上げます', result)) > 0 else "不明"

    print("種類: " + type)

    sender = "不明"
    if len(re.findall('会社名1|会社名2|会社名3', result)) > 0:
        if len(re.findall('会社名1', result)) > 0:
            sender = "会社名1"
        elif len(re.findall('会社名2', result)) > 0:
            sender = "会社名2"
        elif len(re.findall('会社名3', result)) > 0:
            sender = "会社名3"

    print("送信元: " + sender)

    smtpobj = smtplib.SMTP('smtp.gmail.com', 587)
    smtpobj.ehlo()
    smtpobj.starttls()
    smtpobj.ehlo()
    smtpobj.login(MAIL_ADDRESS, PASSWORD)

    body_msg = "送信元:" + sender + "\n種類:" + type + "\nファイル名:" + basename(pdf_file_path) + "\nOCRによるテキスト全文:\n\n" + result

    msg = MIMEMultipart()
    msg['Subject'] = "Faxを受信しました(" + type + ")"
    msg['From'] = from_address
    msg['To'] = to_address
    msg['Date'] = formatdate()
    msg.attach(MIMEText(body_msg))
    msg.preamble = 'Failed jobs list. Please see attachment'

    fp = open(pdf_file_path,'rb')
    att = email.mime.application.MIMEApplication(fp.read(),_subtype="pdf")
    fp.close()
    att.add_header('Content-Disposition','attachment',filename=basename(pdf_file_path))
    msg.attach(att)

    smtpobj.sendmail(from_address, to_address, msg.as_string())
    smtpobj.close()
    print(datetime.datetime.now().strftime('%Y年%m月%d日 %H:%M:%S') + ":" + to_address + " へのメール送信を完了しました。")

observer = Observer()
observer.schedule(ChangeHandler(), wdDirectory, recursive=False)
observer.start()

print("Faxフォルダの監視を開始しました")
print("Watching " + wdDirectory)

while True:
    time.sleep(5)

Python初心者なので、おかしなところありましたら指摘してください。

注文書のテンプレートはこちらを利用。

参考

https://www.python.ambitious-engineer.com/archives/2034

https://qiita.com/mototoke/items/767f0b404bc389d997c8

https://gammasoft.jp/blog/convert-pdf-to-image-by-python/

https://gammasoft.jp/blog/ocr-by-python/

https://gammasoft.jp/blog/tesseract-ocr-install-on-windows/

おまけ

https://twitter.com/moriwaka/status/1352588905141342214?s=20