nyanpyou Note

主な目的は調べたり作ったりしたプログラミング備忘録(予定)

Pythonでメール(.eml)を読み込む

前置き

前回Pythonでメールの送信をする方法についてまとめたが、今回は電子メールを読み込む方法についての話。
ただし、こちらについても前回同様、公式ドキュメントを読んでも理解しきれないところが多々あった。わかる範囲でのメモ書きとする。

本題

メーラーThunderbirdOutlookを使うと、メールファイルを様々な形式でエクスポートできるが、その中にEML形式(拡張子.eml)がある。こいつをPythonで読み込みたい。
Pythonで読み込むことが出来れば、届いたメールを一括でエクスポートした後、自由にフィルターをかけて分類したり、Pythonからのメール送信と合わせて自動的に返信したり、融通が利く。
ありがたいことにPythonの標準パッケージでemlファイルは読み込めるため、これらを用いる。しかし、必要な情報を取り出す為に少々手間がかかる。

import sys
import email
from email.header import decode_header

# "rb"でバイナリファイル読み込み
email_file = open("読み込みたいemlファイルのパス", "rb")
msg = email.message_from_bytes(email_file.read())

# ヘッダーから情報を取得する
for fragment, encoding in email.header.decode_header(msg.get("From")):
    if type(fragment) is bytes:
        if encoding:
            print(fragment.decode(encoding)) 
        else:
            print(fragment.decode("ASCII"))
    elif type(fragment) is str:
        print(fragment)

# 本文を取得する
for part in msg.walk():
    if part.get_content_maintype() == "multipart":
        continue
    if part.get_filename() is None:
        charset = str(part.get_content_charset())
        if charset:
            body = part.get_payload(decode=True).decode(charset, errors="replace")
        else:
            body = part.get_payload(decode=True)

print(body)

ヘッダーから情報を取得する部分について

  • Messageオブジェクトに対して、get(項目名)で指定された名前を持つ値を取得し、decode_headerでデコードする。
  • 項目名にはSubject, To, Fromなどが指定できるが、デコードが必要ない項目もあるので注意(例:Date)
  • decode_headerは(デコードされた文字列,その文字コード)のリストを返す
#例
email.header.decode_header(msg.get("From"))
>[(b'byte文字列', 'utf-8'), (b' <送信者のアドレス>', None)]
  • byte文字列ではあるが、encodingがNoneの時はASCIIでデコードするよう指示している(多分)
    何故ASCIIを指定しているか不明。この部分は参考にしたコードそのままだったはずだが、参考元がどこであったか忘れてしまった…。改めて公式ドキュメントと照らし合わせて確認すると、別にこの部分は無くて良さそうだが、未検証。

本文を取得する部分について

  • Messageオブジェクトに対してwalk()を実行すると、そのMessageオブジェクト内の要素を全て取り出せる(ヘッダーとか本文とか)。ジェネレーターが返ってくるため、取り出す際にはfor文を使う。取り出したものも、同様にMessageオブジェクトである。
    (ジェネレーターについてはほぼ全く知らないので要勉強)
  • walk()で取り出したMessageオブジェクトに対して、get_content_type()を実行すると、content-typeが返ってくる。
#例
for part in msg.walk():
    print(type(part))
>multipart/mixed
>text/plain
>application/pdf
  • 同様にget_content_type()を実行すると、主たるcontent-type(上記例の左半分)が取得できる。よって、これを使ってmultipartの部分は無視する。
  • get_filename()でファイル名が取得できる。添付ファイルには名前が存在し、本文には存在しないため、ファイル名がNoneの部分は本文だと判断する。
  • get_payload(decode=True)を本文のMessageオブジェクトに適用することで、utf-8などの文字コードの羅列に変換し(多分)、decode()で日本語に戻す。

終わりに

電子メールの形式は複雑で、文字コードについてもあまり理解がないため(見ただけではどの文字コードなのか予想もできない)、読み込んだemlファイルの中身を段階的に追って行っても、今どういう形式で書かれたものが見えているのかいまいちピンと来ず苦労した。プログラムチョットデキルと言えるまでの道は険しい。

参考元

www.slideshare.net

docs.python.org

docs.python.org

www.yamanjo.net

ascii.jp