Python:如何解析原始电子邮件中的主体,假设原始电子邮件没有“Body”标签或其他任何东西。

104
似乎很容易获得
From
To
Subject

等等通过

import email
b = email.message_from_string(a)
bbb = b['from']
ccc = b['to']

假设"a"是原始电子邮件字符串,看起来像这样。
a = """From root@a1.local.tld Thu Jul 25 19:28:59 2013
Received: from a1.local.tld (localhost [127.0.0.1])
    by a1.local.tld (8.14.4/8.14.4) with ESMTP id r6Q2SxeQ003866
    for <ooo@a1.local.tld>; Thu, 25 Jul 2013 19:28:59 -0700
Received: (from root@localhost)
    by a1.local.tld (8.14.4/8.14.4/Submit) id r6Q2Sxbh003865;
    Thu, 25 Jul 2013 19:28:59 -0700
From: root@a1.local.tld
Subject: oooooooooooooooo
To: ooo@a1.local.tld
Cc: 
X-Originating-IP: 192.168.15.127
X-Mailer: Webmin 1.420
Message-Id: <1374805739.3861@a1>
Date: Thu, 25 Jul 2013 19:28:59 -0700 (PDT)
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="bound1374805739"

This is a multi-part message in MIME format.

--bound1374805739
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

ooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooo

--bound1374805739--"""

问题

如何使用Python获取此电子邮件的Body

到目前为止,这是我所知道的唯一代码,但我还没有测试它。

if email.is_multipart():
    for part in email.get_payload():
        print part.get_payload()
else:
    print email.get_payload()

这是正确的方法吗?

或者可能有更简单的方法,比如...

import email
b = email.message_from_string(a)
bbb = b['body']

?


2
请注意,Python 3.6+具有方便的get_body()函数,通过即将推出的默认解析策略,如@Doctor J的新回答中所述。请注意,Todor Minakov的回答比falsetru的回答更健壮。 - nealmcb
8个回答

146
为了保持高度的积极性,您需要使用实际的电子邮件正文(但仍有可能没有正确解析),跳过附件,并集中精力处理纯文本或HTML部分(根据您的需求)进行进一步处理。
如前所述,由于附件通常是text/plain或text/html部分,因此这个不太稳定的示例通过检查content-disposition头来跳过这些内容。
b = email.message_from_string(a)
body = ""

if b.is_multipart():
    for part in b.walk():
        ctype = part.get_content_type()
        cdispo = str(part.get('Content-Disposition'))

        # skip any text/plain (txt) attachments
        if ctype == 'text/plain' and 'attachment' not in cdispo:
            body = part.get_payload(decode=True)  # decode
            break
# not multipart - i.e. plain text, no attachments, keeping fingers crossed
else:
    body = b.get_payload(decode=True)

顺便提一下,walk() 函数非常出色地遍历了 MIME 部分,而 get_payload(decode=True) 函数则帮你完成解码 Base64 等操作。

背景知识 - 正如我所暗示的,MIME 邮件的奇妙世界存在许多“错误地”查找消息正文的陷阱。在最简单的情况下,它在唯一的“text/plain”部分中,并且 get_payload() 函数非常诱人,但我们并不生活在一个简单的世界中 - 它通常被包含在 multipart/alternative、related、mixed 等内容中。维基百科对此进行了详细描述 - MIME,但考虑到以下所有情况都是有效的 - 并且常见的 - 人们必须考虑到周围的安全措施:

非常常见 - 就像您在普通编辑器(Gmail,Outlook)中发送带有附件的格式化文本一样:

multipart/mixed
 |
 +- multipart/related
 |   |
 |   +- multipart/alternative
 |   |   |
 |   |   +- text/plain
 |   |   +- text/html
 |   |      
 |   +- image/png
 |
 +-- application/msexcel

相对简单 - 只是另一种表现方式:

multipart/alternative
 |
 +- text/plain
 +- text/html

无论是好是坏,这个结构也是有效的:

multipart/alternative
 |
 +- text/plain
 +- multipart/related
      |
      +- text/html
      +- image/jpeg

附言:我的意思是不要轻视电子邮件 - 当你最不希望它发生时,它会反咬一口 :)


8
感谢您提供这个详尽的例子并明确警示,与被接受的答案相比,我认为这是更好/更安全的方法。 - Simon Steinberger
2
啊,非常好!使用.get_payload(decode=True)而不是仅仅使用.get_payload()让生活变得更加轻松,谢谢! - Mark
我只想获取.get_payload(decode=True)中的正文内容。有什么方法可以实现吗? - abhijitcaps

99

使用Message.get_payload方法。

b = email.message_from_string(a)
if b.is_multipart():
    for payload in b.get_payload():
        # if payload.is_multipart(): ...
        print payload.get_payload()
else:
    print b.get_payload()

3
其他答案在更加健壮和利用新的get_body()功能方面做得更好。 - nealmcb
3
@nealmcb,当我回答时还没有get_body ;) 似乎自Python 3.6以来就出现了。顺便说一下,这个问题被标记为python-2.7,在那里你不能使用get_body - falsetru
2
好观点!当然,随着Python 2已经过去一年的生命周期,我们可以预计现代解决方案会更受欢迎。但请注意,正如Todor所描述的那样,许多电子邮件具有复杂的结构,因此更通用的方法是一个好主意,而你的“…”并不是非常具体。 - nealmcb

19

有一个非常好的可以解析带有适当文档的电子邮件内容。

import mailparser

mail = mailparser.parse_from_file(f)
mail = mailparser.parse_from_file_obj(fp)
mail = mailparser.parse_from_string(raw_mail)
mail = mailparser.parse_from_bytes(byte_mail)

使用方法:

mail.attachments: list of all attachments
mail.body
mail.to

2
库很好用,但我不得不创建一个继承自MailParser的类,并覆盖body方法,因为它使用**"\n--- mail_boundary ---\n"**连接电子邮件正文的部分,这对我来说并不理想。 - avram
嗨@avram,你能分享一下你写的那个类吗? - Amey P Naik
我成功地将结果分割为"\n--- mail_boundary ---\n"。 - Amey P Naik
3
@AmeyPNaik,这里我制作了一个快速的GitHub代码片段:https://gist.github.com/aleksaa01/ccd371869f3a3c7b3e47822d5d78ccdf - avram
1
在他们的文档中(https://pypi.org/project/mail-parser/),@AmeyPNaik说:_mail-parser可以解析Outlook邮件格式(.msg)。要使用此功能,您需要安装libemail-outlook-message-perl包。_ - Ciprian Tomoiagă

16

Python 3.6+ 提供了内置的便捷方法,可用于查找和解码纯文本正文,就像@Todor Minakov的答案中所示。您可以使用EMailMessage.get_body()get_content() 方法:

msg = email.message_from_string(s, policy=email.policy.default)
body = msg.get_body(('plain',))
if body:
    body = body.get_content()
print(body)

请注意,如果没有(明显的)纯文本主体部分,则会返回None

如果您正在从例如mbox文件中读取,则可以向邮箱构造函数提供一个EmailMessage工厂:

mbox = mailbox.mbox(mboxfile, factory=lambda f: email.message_from_binary_file(f, policy=email.policy.default), create=False)
for msg in mbox:
    ...

请注意必须将 email.policy.default 作为策略传递,因为它不是默认值...


4
为什么email.policy.default不是默认值?看起来它应该成为默认值。 - PartialOrder
@PartialOrder 向后兼容性。它将成为默认选项,您现在应该已经使用它了。 - Bergi
这非常有启发性和鼓舞人心,但让我困惑了一段时间。lambda 不会立即显示缺少 "email.policy" 的导入,我猜测如果您明确访问消息,例如通过 mbox.get_message(0),则不会咨询工厂。大家还可以注意到更明确的 make_EmailMessage 工厂函数方法,网址为 https://dev59.com/MrXna4cB1Zd3GeqPOqgp#57550079 - nealmcb
1
我得到了这个错误。请问您能告诉我出了什么问题吗?Traceback (most recent call last): File "", line 1, in AttributeError: Message instance has no attribute 'get_body'``` - user1424739

4

在python中没有b['body']。你需要使用get_payload。

if isinstance(mailEntity.get_payload(), list):
    for eachPayload in mailEntity.get_payload():
        ...do things you want...
        ...real mail body is in eachPayload.get_payload()...
else:
    ...means there is only text/plain part....
    ...use mailEntity.get_payload() to get the body...

祝你好运。


1
如果emails是pandas数据帧,而emails.message是电子邮件文本列。
## Helper functions
def get_text_from_email(msg):
    '''To get the content from email objects'''
    parts = []
    for part in msg.walk():
        if part.get_content_type() == 'text/plain':
            parts.append( part.get_payload() )
    return ''.join(parts)

def split_email_addresses(line):
    '''To separate multiple email addresses'''
    if line:
        addrs = line.split(',')
        addrs = frozenset(map(lambda x: x.strip(), addrs))
    else:
        addrs = None
    return addrs 

import email
# Parse the emails into a list email objects
messages = list(map(email.message_from_string, emails['message']))
emails.drop('message', axis=1, inplace=True)
# Get fields from parsed email objects
keys = messages[0].keys()
for key in keys:
    emails[key] = [doc[key] for doc in messages]
# Parse content from emails
emails['content'] = list(map(get_text_from_email, messages))
# Split multiple email addresses
emails['From'] = emails['From'].map(split_email_addresses)
emails['To'] = emails['To'].map(split_email_addresses)

# Extract the root of 'file' as 'user'
emails['user'] = emails['file'].map(lambda x:x.split('/')[0])
del messages

emails.head()

0

根据Doctor J的回答,进行了小更新。解析电子邮件消息的纯文本部分(如果有)。可以尝试获取html,因为仅发送HTML邮件的(坏)习惯越来越普遍。

from email import message_from_string
from email import policy

raw_string = raw_string.strip() # where raw_string is the email message (DATA)
msg = message_from_string(raw_string, policy=policy.default)
body = msg.get_body(('plain',))
if body:
    body = body.get_content()
    print(body)

在处理电子邮件数据字符串时,去除前导/尾随空格是必要的,没有它会浪费很多时间!


-3
以下是我每次都能正常工作的代码(用于Outlook电子邮件):
#to read Subjects and Body of email in a folder (or subfolder)

import win32com.client  
#import package

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")  
#create object

#get to the desired folder (MyEmail@xyz.com is my root folder)

root_folder = 
outlook.Folders['MyEmail@xyz.com'].Folders['Inbox'].Folders['SubFolderName']

#('Inbox' and 'SubFolderName' are the subfolders)

messages = root_folder.Items

for message in messages:
if message.Unread == True:    # gets only 'Unread' emails
    subject_content = message.subject
# to store subject lines of mails

    body_content = message.body
# to store Body of mails

    print(subject_content)
    print(body_content)

    message.Unread = True         # mark the mail as 'Read'
    message = messages.GetNext()  #iterate over mails

5
请明确说明这是针对Windows上的Outlook,而不是真实电子邮件。 - tripleee

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