如何使用Python获取电子邮件消息的文本内容?

48

在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()获取编码文本,但如果我尝试在text/plain部分上调用get_payload()方法的decode参数(参见文档),就会出现错误:

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渲染为文本。

1
我在http://ginstrom.com/scribbles/2007/11/19/parsing-multilingual-email-with-python/找到了一个类似的问题的有用解决方案。 - beldaz
5个回答

109

在多部分电子邮件中,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

对于非多部分消息,无需进行所有遍历。可以直接使用get_payload(),而不考虑内容类型。

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

如果内容已被编码,您需要将get_payload()的第一个参数传递为None,然后是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效果非常好。


这是一个非常好的解释...恰好涵盖了我已经掌握的内容;正如所述,我可以定位和提取部件的裸负载。然而,如果该部分已被解码,我无法对其进行解码,如果没有文本/纯文本部分可用,则无法将文本/ HTML部分呈现为文本。 - Chris R
重新阅读后——抱歉,我还没喝咖啡!好吧,你解决了我的HTML转文本问题 :) - Chris R
我的错...显然昨晚回答时没有喝足够的咖啡。我已经修改了答案,希望能满足你的需求。 - Jarret Hardie
2
酷!我该如何检查部件是否已编码?在哪里可以看到部件的Content-Transfer-Encoding属性? - Chris R
使用part.get_param('content-transfer-encoding')来查看属性。 - Jarret Hardie
5
实际上,使用part.get("content-transfer-encoding"),因为它只是一个标头,而不是内容类型标头的一部分。此外,您可以使用part.get_payload(decode=True)替代part.get_payload(None, True),我认为这更清晰。 - Wodin

1
尝试使用我的IMAP库:https://github.com/ikvk/imap_tools
from imap_tools import MailBox, AND

# Get date, subject and body len of all emails from INBOX folder
with MailBox('imap.mail.com').login('test@mail.com', 'pwd') as mailbox:
    for msg in mailbox.fetch():
        print(msg.date, msg.subject, len(msg.text or msg.html))

查看 html2text: https://pypi.org/project/html2text/

而且可能 msg.text 就足够了。


1

平的比嵌套的好;)

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
@tripleee 通常我们使用一个纯文本、一个HTML部分和几个图片部分。即使有多个纯文本部分,你怎么知道哪一个是正确的呢? - guneysus
1
在典型情况下,当顶级multipart/alternative只有一个部分是text/plain时,选择该部分。在更一般的情况下,我认为没有单一正确的答案,因为它取决于您的应用程序目的和接收者的偏好。 - tripleee
4
公正地说,被接受的答案也有同样的问题。 - tripleee

0

#这是我使用应用程序密码方法的Gmail帐户。

from imap_tools import MailBox
import email
my_email = "your email"
my_pass = "app password"
mailbox =  MailBox('imap.gmail.com').login(my_email, my_pass)

for msg in mailbox.fetch('Subject "   "', charset='utf8'):
    print("Message id:",msg.uid)
    print("Message Subject:",msg.subject)
    print("Message Date:", msg.date)
    print("Message Text:", msg.text)

0
补充@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"])


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接