【问题标题】:Python : How to parse the Body from a raw email , given that raw email does not have a "Body" tag or anythingPython:鉴于原始电子邮件没有“正文”标签或任何东西,如何从原始电子邮件中解析正文
【发布时间】:2013-07-26 07:27:24
【问题描述】:

似乎很容易得到

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']

?

【问题讨论】:

  • 请注意,Python 3.6+ 通过即将推出的默认解析策略提供便捷的 get_body() 函数,正如@Doctor J 的较新答案中所述,并注意 Todor Minakov 的答案比 falsetru 的答案更稳健

标签: python email python-2.7 mod-wsgi wsgi


【解决方案1】:

要非常积极地使用实际的电子邮件正文(但是,仍然有可能您没有解析正确的部分),您必须跳过附件,并专注于纯文本或 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 电子邮件的美妙世界存在许多“错误地”查找邮件正文的陷阱。 在最简单的情况下,它位于唯一的“文本/纯文本”部分中,get_payload() 非常诱人,但我们并不生活在一个简单的世界中——它通常被多部分/替代、相关、混合等内容所包围。维基百科对它进行了严格的描述 - 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

希望这会有所帮助。

附:我的观点是不要轻易接近电子邮件——当你最不期待的时候它会咬人:)

【讨论】:

  • 感谢您提供这个详尽的示例并详细说明警告 - 与公认的答案相反。我认为这是一种更好/更安全的方法。
  • 啊,很好! .get_payload(decode=True) 而不仅仅是 .get_payload() 让生活变得更轻松,谢谢!
  • 我只从 .get_payload(decode=True) 中寻找正文。有什么办法吗?
【解决方案2】:

使用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()

【讨论】:

  • 其他答案在更健壮和利用更新的 get_body() 功能方面做得更好。
  • @nealmcb,当我回答时没有get_body ;) 似乎它从 Python 3.6 开始出现。顺便说一句,这个问题被标记为python-2.7,你不能使用get_body
  • 好点!当然,随着 Python 2 的生命周期结束一年多,我们可以假设对现代解决方案更感兴趣。但也请注意,正如 Todor 所描述的,许多电子邮件的结构很复杂,因此更通用的方法是个好主意,而您的“...”不是很具体。
【解决方案3】:

有很好的package 可用于使用适当的文档解析电子邮件内容。

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

【讨论】:

  • 库很棒,但我必须创建自己的类,该类继承自 MailParser 并覆盖 body 方法,因为它将电子邮件正文的部分与 连接起来"\n--- mail_boundary ---\n" 这对我来说并不理想。
  • 嗨@avram,你能分享一下你写的课程吗?
  • 我设法将结果拆分到“\n--- mail_boundary ---\n”。
  • @AmeyPNaik 在这里我做了一个快速的 github 要点:gist.github.com/aleksaa01/ccd371869f3a3c7b3e47822d5d78ccdf
  • @AmeyPNaik 在他们的documentation 中说:mail-parser 可以解析 Outlook 电子邮件格式 (.msg)。要使用此功能,您需要安装 libemail-outlook-message-perl 包
【解决方案4】:

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 factory:

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 作为策略传递,因为它不是默认...

【讨论】:

  • 为什么email.policy.default 不是默认值?好像应该这样。
  • @PartialOrder 向后兼容性。它成为默认值,您现在应该已经使用它了。
  • 这是非常有用和鼓舞人心的,但让我困惑了一段时间。 lambda 不会立即显示缺少“email.policy”的导入,我猜如果您明确访问消息,则不会咨询工厂,例如通过mbox.get_message(0) 人们还可以注意到更明确的make_EmailMessage 工厂函数方法stackoverflow.com/a/57550079/507544
【解决方案5】:

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...

祝你好运。

【讨论】:

    【解决方案6】:

    如果 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()
    

    【讨论】:

      【解决方案7】:

      这是我每次都能使用的代码(适用于 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
      

      【讨论】:

      • 也许说明这是针对 Windows 上的 Outlook,而不是针对真正的电子邮件。
      猜你喜欢
      • 2020-07-20
      • 2013-07-26
      • 2016-06-22
      • 1970-01-01
      • 2010-09-05
      • 1970-01-01
      • 2019-03-12
      • 2013-07-12
      • 1970-01-01
      相关资源
      最近更新 更多