【问题标题】:How can I get an email message's text content using Python?如何使用 Python 获取电子邮件的文本内容?
【发布时间】:2023-04-06 19:18:01
【问题描述】:

鉴于 Python 2.6 中的 RFC822 消息,我如何才能获得 正确 文本/纯内容部分?基本上,我想要的算法是这样的:

message = email.message_from_string(raw_message)
if has_mime_part(message, "text/plain"):
    mime_part = get_mime_part(message, "text/plain")
    text_content = decode_mime_part(mime_part)
elif has_mime_part(message, "text/html"):
    mime_part = get_mime_part(message, "text/html")
    html = decode_mime_part(mime_part)
    text_content = render_html_to_plaintext(html)
else:
    # fallback
    text_content = str(message)
return text_content

在这些东西中,我有get_mime_parthas_mime_part,但我不太确定如何从 MIME 部分获取解码的文本。我可以使用get_payload() 获取编码 文本,但是如果我尝试使用get_payload() 方法的decode 参数(请参阅the doc),我在调用它时会出错文本/纯文本部分:

File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/
email/message.py", line 189, in get_payload
    raise TypeError('Expected list, got %s' % type(self._payload))
TypeError: Expected list, got <type 'str'>

另外,我不知道如何获取 HTML 并将其呈现为尽可能接近的文本。

【问题讨论】:

标签: python email mime rfc822


【解决方案1】:

补充@Jarret Hardie 的出色回答:

我个人喜欢将这种数据结构转换为以后可以重用的字典,因此content_type 是键,payload 是值:

import email

[...]

email_message = {
    part.get_content_type(): part.get_payload()
    for part in email.message_from_bytes(raw_email).walk()
}

print(email_message["text/plain"])

【讨论】:

    【解决方案2】:

    扁平比嵌套更好;)

    from email.mime.multipart import MIMEMultipart
    assert isinstance(msg, MIMEMultipart)
    
    for _ in [k.get_payload() for k in msg.walk() if k.get_content_type() == 'text/plain']:
        print _
    

    【讨论】:

    • 这会盲目地提取所有“text/plain”部分,而不注意哪个是“正确的”。
    • @tripleee 一般我们使用一个plain,一个html部分和几个图像部分。就算有不止普通的部分,你又怎么知道哪一个是对的呢?
    • 在典型情况下,顶级multipart/alternative 只有一部分是text/plain,即那一部分。在更一般的情况下,我认为没有一个正确答案,因为这取决于您的申请目的和收件人的偏好。
    • 平心而论,接受的答案有同样的问题。
    【解决方案3】:

    在多部分电子邮件中,email.message.Message.get_payload() 返回一个列表,其中每个部分都有一个项目。最简单的方法是遍历消息并获取每个部分的有效负载:

    import email
    msg = email.message_from_string(raw_message)
    for part in msg.walk():
        # each part is a either non-multipart, or another multipart message
        # that contains further parts... Message is organized like a tree
        if part.get_content_type() == 'text/plain':
            print part.get_payload() # prints the raw text
    

    对于非多部分消息,无需进行所有遍历。无论 content_type 是什么,您都可以直接进入 get_payload()。

    msg = email.message_from_string(raw_message)
    msg.get_payload()
    

    如果内容被编码,则需要将None作为第一个参数传递给get_payload(),然后是True(解码标志是第二个参数)。例如,假设我的电子邮件包含 MS Word 文档附件:

    msg = email.message_from_string(raw_message)
    for part in msg.walk():
        if part.get_content_type() == 'application/msword':
            name = part.get_param('name') or 'MyDoc.doc'
            f = open(name, 'wb')
            f.write(part.get_payload(None, True)) # You need None as the first param
                                                  # because part.is_multipart() 
                                                  # is False
            f.close()
    

    至于获得 HTML 部分的合理纯文本近似值,我发现 html2text 工作得非常好。

    【讨论】:

    • 这是一个很好的解释......它完全涵盖了我已经得到的;如前所述,我可以定位并提取零件的裸机负载。但是,如果已解码,我可以解码该部分,如果没有可用的 text/plain 部分,我也不能将 text/html 部分渲染为文本。
    • (重读——抱歉,咖啡没了!)好吧,你已经解决了我的 HTML 到文本的问题 :)
    • 我的错……当我回答时,昨晚显然没有足够的咖啡。我已经修改了答案,希望能满足您的需要。
    • Cool.. 如何检查零件是否已编码?在哪里可以看到部件的 Content-Transfer-Encoding 属性?
    • 其实用part.get("content-transfer-encoding"),因为它只是一个header。不是内容类型标头的一部分。另外,你可以用part.get_payload(decode=True)代替part.get_payload(None, True),我觉得这样更清楚一些。
    猜你喜欢
    • 2020-03-18
    • 2016-04-30
    • 1970-01-01
    • 1970-01-01
    • 2012-05-29
    • 2011-04-09
    • 2017-09-21
    • 2022-07-23
    • 1970-01-01
    相关资源
    最近更新 更多