Прочитайте большой файл .mbox с помощью Python

Я хотел бы прочитать большой файл .mbox размером 3 ГБ из резервной копии Gmail. Это работает:

import mailbox
mbox = mailbox.mbox(r"D:\All mail Including Spam and Trash.mbox")
for i, message in enumerate(mbox):
    print("from   :",message['from'])
    print("subject:",message['subject'])
    if message.is_multipart():
        content = ''.join(part.get_payload(decode=True) for part in message.get_payload())
    else:
        content = message.get_payload(decode=True)
    print("content:",content)
    print("**************************************")

    if i == 10:
        break

за исключением того, что для первых 10 сообщений требуется более 40 секунд.

Есть ли более быстрый способ доступа к большому файлу .mbox с помощью Python?


person Basj    schedule 10.01.2020    source источник
comment
Я думаю, что библиотека mailbox считывает все это в память. Нетрудно переписать простой анализатор mbox в качестве генератора (вкратце, любая строка, начинающаяся с From , запускает новое сообщение).   -  person tripleee    schedule 10.01.2020


Ответы (1)


Вот быстрая и грязная попытка реализовать генератор для чтения в mbox файле сообщение за сообщением. Я решил просто убрать информацию из разделителя From; Я предполагаю, что настоящая библиотека mailbox может предоставить больше информации, и, конечно же, она поддерживает только чтение, а не поиск или обратную запись во входной файл.

#!/usr/bin/env python3

import email
from email.policy import default

class MboxReader:
    def __init__(self, filename):
        self.handle = open(filename, 'rb')
        assert self.handle.readline().startswith(b'From ')

    def __enter__(self):
        return self

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

    def __iter__(self):
        return iter(self.__next__())

    def __next__(self):
        lines = []
        while True:
            line = self.handle.readline()
            if line == b'' or line.startswith(b'From '):
                yield email.message_from_bytes(b''.join(lines), policy=default)
                if line == b'':
                    break
                lines = []
                continue
            lines.append(line)

Применение:

with MboxReader(mboxfilename) as mbox:
    for message in mbox:
        print(message.as_string())
person tripleee    schedule 10.01.2020
comment
Мне пришлось гуглить подробности о менеджерах контекста и итерируемых объектах, так что в нем все еще могут быть ошибки. Приветствуется обратная связь. - person tripleee; 10.01.2020
comment
Спасибо за ваш ответ, это работает! Кстати, кажется, вы ответили stackoverflow.com/questions/59681576/ одновременно (разве ответ не message.as_string()? если да, не стесняйтесь опубликовать эту команду там) - person Basj; 10.01.2020
comment
PS @tripleee: message.as_string() у меня иногда выдает ошибки: KeyError: 'content-transfer-encoding'. Интересно, обрабатывалось ли это специально в исходном модуле mailbox, который я использовал? Вот полная трассировка: pastebin.com/K2xKCSKG - person Basj; 10.01.2020
comment
Похоже на ошибку в модуле email. Можете ли вы поделиться (примерно) сообщением, которое вызывает эту трассировку? - person tripleee; 10.01.2020
comment
У меня будет более глубокий взгляд на это, и я поделюсь им в эти выходные. В очередной раз благодарим за помощь! - person Basj; 10.01.2020
comment
Если вам нужен только источник сообщений без инкапсуляции объекта email, вы можете удалить import и просто yield(b''.join(lines)) - person tripleee; 10.01.2020
comment
Я удивлен, что этого нет в модуле стандартной библиотеки почтового ящика. Большая часть стандартной библиотеки Python удобна для итераторов. Спасибо за предоставление! - person Jim Pivarski; 20.05.2021