My Tech Life

Memo by a Japanese Software Developer in his late 50s.

pythonで.mboxを読む(改善版)

通常のmailbox取得では、ファイルの全体をパース指定するようで、

とても時間がかかることは以前の記事に書いた。

 

import mailbox

mbox = mailbox.mbox('example.mbox')

for message in mbox:
    print("Subject:", message['subject'])

mbox.close()

 

逐次的にデータを読み込み、出力するように改善したく、

ChatGPTに聞いてみたが、出力されたソースコードでは、

messageオブジェクトが正しく作られない。

 

デバッグした結果、

 

                message = email.message_from_bytes(b'\n'.join(lines), policy=default)

 

入力となるlinesの各要素に、すでに「\r\n」(改行)が入ってしまっており、

その状態でさらに「\n」(改行)でjoin()してしまっているため、

1行ごとに空行ができてしまい、1行目でメールヘッダが完結してしまうことが判明。

そのため、以下のように修正した。

 

            # ***** modification starts here *****
            #lines.append(line)
            lines.append(line.rstrip(b'\r\n'))
            # ***** modification ends here *****

 

これは「メールヘッダは最初の空行まで続く」ということを知っていて、

初めて分かるバグ。

 

以下、修正版の全体のコード。

読んだメールから内容を表示するので、とても機敏に動作する。

このソースをもとに、さらに調査を進めよう。

 

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

mboxfilename = 'category_social.mbox'

import email
from email.policy import default
from email.header import decode_header

class MboxReader:
    def __init__(self, filename):
        self.handle = open(filename, 'rb')

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.handle.close()

    def __iter__(self):
        return self

    def __next__(self):
        lines = [ ]
        while True:
            line = self.handle.readline()
            if line == b'' or line.startswith(b'From '):
                message = email.message_from_bytes(b'\n'.join(lines), policy=default)
                if message:
                    subject = message['subject']
                    if subject:
                        decoded_subject = decode_header(subject)[0]
                        if decoded_subject[1]:
                            subject = decoded_subject[0].decode(decoded_subject[1])
                    date = message['Date']
                    payload = message.get_payload()
                    if isinstance(payload, list):
                        payload = payload[0].get_payload()  # Handle multipart messages
                    return subject, payload, date
                if line == b'':
                    raise StopIteration
                lines = []
                continue
            # ***** modification starts here *****
            #lines.append(line)
            lines.append(line.rstrip(b'\r\n'))
            # ***** modification ends here *****

with MboxReader(mboxfilename) as mbox:
    for i, (subject, payload, date) in enumerate(mbox):
        #if i > 100: break

        #print("Subject:", subject)
        print(i, date, subject)
        #print("Body:", payload)