Pythonで添付ファイル付きメールを受信して業務処理を自動化する

Python

Pythonで添付ファイル付きメールを受信するには?

Pythonには標準でpoplibライブラリでメール受信する機能が備わっており簡単にメールを処理することが可能となっています。筆者はこの機能を利用してメールの添付ファイルを顧客ごとに振り分けして電子帳簿保存の前処理を行っています。

本記事では添付ファイル付きメールを受信してメール本文とメール添付ファイルを保存する処理を紹介します。※POP over SSLでメール受信する例となります。

サンプルコード

次に、メール受信のサンプルコードを紹介します。
関数の下にはテスト用のコードも記載していますので参考にしてください。

import poplib                           # POP3プロトコルを使ってメールサーバーからメールを取得
from email.parser import BytesParser    # メールの内容を解析するためのモジュール
from email.policy import default        # メールの処理ポリシーを定義。defaultは標準のポリシーを指す。
import os                               # ファイル処理用
from datetime import datetime           # 日付処理用
import re                               # 正規表現処理用

def sanitize_filename(filename):
    """
    ファイル名に使用できない文字を安全な文字に置換する(サニタイズする)。
    
    Args:
        filename (str): サニタイズする前のファイル名。
    
    Returns:
        str: サニタイズ後のファイル名。
    """
    # ファイル名に使用できない文字をアンダースコアに置換
    return re.sub(r'[\\/*?:"<>|]', '_', filename)

def save_email(msg, folder_path):
    """
    メールの内容をテキストファイルに保存する。
    
    Args:
        msg (email.message.EmailMessage): メールオブジェクト。
        folder_path (str): 保存先フォルダのパス。
        index (int): メールのインデックス番号。
    """
    # メールの件名を取得し、ファイル名に使用。件名がない場合は無題とする
    subject = msg.get('Subject', 'subjectless')

    # メールの件名が存在するかどうかを確認
    if subject:
        # 件名が存在する場合、サニタイズしてファイル名に使用
        subject_filename = sanitize_filename(subject)
    else:
        # 件名が存在しない場合、「subjectless.txt」とする。
        subject_filename = f'subjectless'

    # ファイルのフルパスを設定する
    file_path = os.path.join(folder_path, f'{subject_filename}.txt')
    
    # メールの内容をファイルに書き込み
    with open(file_path, 'w', encoding='utf-8') as f:
        # メールのヘッダを書き込み
        for header, value in msg.items():
            f.write(f"{header}: {value}\n")
        
        # ヘッダと本文の間に空行を挿入
        f.write("\n")  

        # メールの本文を書き込み
        if msg.is_multipart():
            # マルチパートのメールの場合、各パートを処理
            for part in msg.walk():
                # 最初のテキストパートの内容を書き込み
                if part.get_content_type() == 'text/plain':
                    emailbody = part.get_payload(decode=True).decode(part.get_content_charset(), 'ignore')
                    f.write(emailbody)
                    break  # 最初のテキストパートのみを処理
        else:
            # シングルパートのメールの場合、直接内容を書き込み
            emailbody = msg.get_payload(decode=True).decode('utf-8', 'ignore')
            f.write(emailbody)

def pop_mail(
    pop_server, 
    email_user, 
    email_password, 
    base_download_folder,
    delete_mail=False
):
    """
    POP3を使ってメールを受信し、メールと添付ファイルをダウンロードする。
    
    Args:
        pop_server (str): POPサーバのアドレス。
        email_user (str): メールのユーザー名。
        email_password (str): メールのパスワード。
        base_download_folder (str): ダウンロードの基本フォルダパス。
        delete_mail (bool): ダウンロード後にメールを削除するかどうか。
    """
    mail = poplib.POP3_SSL(pop_server)
    mail.user(email_user)
    mail.pass_(email_password)

    # メールボックス内のメッセージ数を取得
    num_messages = len(mail.list()[1])

    # メッセージの個数分処理
    for i in range(num_messages):
        # 各メッセージを取得
        response, lines, octets = mail.retr(i + 1)
        msg_bytes = b'\r\n'.join(lines)
        msg = BytesParser(policy=default).parsebytes(msg_bytes)

        # ダウンロードするメールのためのフォルダを作成
        folder_name = datetime.now().strftime('%Y%m%d_%H%M%S_') + str(i)
        folder_path = os.path.join(base_download_folder, folder_name)
        os.makedirs(folder_path, exist_ok=True)

        # メール本文の内容を保存
        save_email(msg, folder_path)

        # メールがマルチパートの場合、添付ファイルを処理
        if msg.is_multipart():
            for part in msg.walk():
                # コンテントディスポジションを確認(ファイルをWEBページとして表示するか、ダウンロードさせるかを指定するためのheader)
                content_disposition = part.get("Content-Disposition", None)
                if content_disposition:
                    # 添付ファイルまたはインライン画像の場合、ファイルをダウンロード 
                    if any(dispo in content_disposition for dispo in ['attachment', 'inline']) or part.get_content_maintype() == 'image':
                        # 添付ファイルのファイル名を取得
                        filename = part.get_filename()
                        
                        if filename:
                            # ファイル名のサニタイズ&保存先フルパス作成、
                            filepath = os.path.join(folder_path, sanitize_filename(filename))
                            # ファイル書き込み
                            with open(filepath, 'wb') as f:
                                f.write(part.get_payload(decode=True))
                            print(f"添付ファイルをダウンロードしました: {filename}")

        # メールを受信後に削除するオプションが有効の場合、メールを削除
        if delete_mail:
            mail.dele(i + 1)
            print(f"メールを削除しました: {i + 1}")

        print(f"メールを保存しました: {folder_path}")

    mail.quit()



##############################################################
# 関数のテスト
if __name__ == "__main__":

    # テスト用のパラメータを設定
    pop_server = "pop.commufa.jp"   # POPサーバのアドレスを設定
    email_user = "popuser"         # メールのユーザー名を設定
    email_password = "poppassword"     # メールのパスワードを設定
    download_folder = "C:\\Users\\wkusr\\Documents\\mail"  # ダウンロードフォルダのパスを設定

    # ベースとなるダウンロードフォルダが存在しない場合は作成
    if not os.path.exists(download_folder):
        os.makedirs(download_folder)

    # 関数のテスト実行 サーバからメールを削除しない。
    pop_mail(pop_server, email_user, email_password, download_folder, False)
    # 関数のテスト実行 サーバからメールを削除する。
    # pop_mail(pop_server, email_user, email_password, download_folder, True)


サンプルを実行する前にpop_serverからdownload_folderのパラメータを環境に合わせて設定してください。筆者はcommufaを利用しているので、commufaユーザーの方はpopサーバはそのまま利用できると思います。

##############################################################
# 関数のテスト
if __name__ == "__main__":

    # テスト用のパラメータを設定
    pop_server = "pop.commufa.jp"   # POPサーバのアドレスを設定
    email_user = "popuser"         # メールのユーザー名を設定
    email_password = "poppassword"     # メールのパスワードを設定
    download_folder = "C:\\Users\\wkusr\\Documents\\mail"  # ダウンロードフォルダのパスを設定

実行結果

メール処理状況によりますが以下のようにコンソールへ出力されます。筆者の例の場合は5通のメールを受信した結果となります。

添付ファイルをダウンロードしました: 2024-02-01 114927.pdf
添付ファイルをダウンロードしました: 2024-02-01 114612.pdf
添付ファイルをダウンロードしました: 2024-02-01 115015.pdf
添付ファイルをダウンロードしました: 2024-02-01 114943.pdf
添付ファイルをダウンロードしました: 2024-02-01 115001.pdf
添付ファイルをダウンロードしました: 2024-02-01 121717.pdf
メールを保存しました: C:\Users\wkusr\Documents\mail\20240207_113022_0
添付ファイルをダウンロードしました: 2024-02-01 114927.pdf
添付ファイルをダウンロードしました: 2024-02-01 114612.pdf
添付ファイルをダウンロードしました: 2024-02-01 115015.pdf
添付ファイルをダウンロードしました: 2024-02-01 114943.pdf
添付ファイルをダウンロードしました: 2024-02-01 115001.pdf
添付ファイルをダウンロードしました: 2024-02-01 121717.pdf
メールを保存しました: C:\Users\wkusr\Documents\mail\20240207_113022_1
添付ファイルをダウンロードしました: order.pdf
メールを保存しました: C:\Users\wkusr\Documents\mail\20240207_113024_2
メールを保存しました: C:\Users\wkusr\Documents\mail\20240207_113024_3
メールを保存しました: C:\Users\wkusr\Documents\mail\20240207_113024_4

自動的に保存先へメールごとのフォルダが作成され、フォルダ名には受信した日時と時分秒と受信処理した連番が付きます。

1通目のフォルダを開くと以下のようになります、ファイル名は「Subject + .txt」として20240201オーダー1回目.txtがメール本文となりそれ以外はメール添付ファイルです。
※添付ファイルがないメールの場合にはフォルダの中に本文のtxtファイルができます。

20240201オーダー1回目.txtの中身は以下のとおりです。メールヘッダ情報と本文が記載されています(モザイク部分はメールヘッダ情報)

From:のメールアドレスから顧客を判断し添付ファイルが存在する場合には該当の顧客専用フォルダに配置をして電子帳簿保存の前処理として利用する等が考えられます。

解説

メールを削除する場合

pop_mail関数の第5引数をTrueとすることでサーバーからメールが削除されます。

# 関数のテスト実行 サーバからメールを削除する。
pop_mail(pop_server, email_user, email_password, download_folder, True)

ポート番号が995ではない場合。

以下のコメント行にポート番号を設定することで対処可能です。

mail = poplib.POP3_SSL(pop_server)
# mail = poplib.POP3_SSL(pop_server, 995)

固定パラメータについて

popサーバや、popユーザやpopパスワードはプログラムで書く場合は固定値がほとんどだと考えられます、この場合はiniファイル等の読み込みを行うことでオンコード記述を削減できますので検討してみてください。

最後に

以上がPythonで添付ファイル付きメール受信を行う方法です。

筆者のように電子帳簿保存の前捌きで利用すること以外にも自動受信バッチを組み込みして業務効率化できるシチュエーションがあると思います。

例えば以下の用途で利用できると考えられます。

  1. 契約書や請求書の受け取りと処理。
  2. 注文確認や予約確認の自動返信。
  3. 発注確認や配送ステータスの更新通知。
  4. キャンペーン応募メールからのデータ収集と分析。
  5. 不審なメールの自動検出と隔離。
  6. 顧客からの問い合わせや予約確認メールを利用した顧客情報の更新。
  7. 顧客からの特別な要望やフィードバックへの迅速な対応。

本記事を参考にサンプルコードを書き換えして業務効率化を検討してみてください。

また、メール送信の記事もありますのでこちらも参照ください。

コメント

タイトルとURLをコピーしました