Python IMAP消息获取UnicodeDecodeError';utf-8';编解码器可以';破译

Python IMAP消息获取UnicodeDecodeError';utf-8';编解码器可以';破译,python,python-3.x,email,utf-8,imap,Python,Python 3.x,Email,Utf 8,Imap,经过5个小时的努力,是时候寻求帮助了。筛选了所有与此相关的stackoverflow问题,但找不到答案 该代码是一个gmail解析器——适用于大多数电子邮件,但有些电子邮件会导致UnicodeDecodeError。问题是“raw_email.decode('utf-8')”,但更改它(参见注释)会导致下面的另一个问题 # Source: https://stackoverflow.com/questions/7314942/python-imaplib-to-get-gmail-inbox-s

经过5个小时的努力,是时候寻求帮助了。筛选了所有与此相关的stackoverflow问题,但找不到答案

该代码是一个gmail解析器——适用于大多数电子邮件,但有些电子邮件会导致UnicodeDecodeError。问题是“raw_email.decode('utf-8')”,但更改它(参见注释)会导致下面的另一个问题

# Source: https://stackoverflow.com/questions/7314942/python-imaplib-to-get-gmail-inbox-subjects-titles-and-sender-name

import datetime
import time
import email
import imaplib
import mailbox
from vars import *
import re                   # to remove links from str
import string


EMAIL_ACCOUNT = 'gmail_login'
PASSWORD = 'gmail_psswd'

mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login(EMAIL_ACCOUNT, PASSWORD)
mail.list()
mail.select('inbox')
result, data = mail.uid('search', None, "ALL") # (ALL/UNSEEN)

id_list = data[0].split()
email_rev = reversed(id_list)             # Returns a type list.reverseiterator, which is not list
email_list = list(email_rev)
i = len(email_list)

todays_date = time.strftime("%m/%d/%Y")

for x in range(i):
    latest_email_uid = email_list[x]
    result, email_data = mail.uid('fetch', latest_email_uid, '(RFC822)')
    raw_email = email_data[0][1]                                 # Returns a byte
    raw_email_str = raw_email.decode('utf-8')                    # Returns a str
    #raw_email_str = base64.b64decode(raw_email_str1)      # Tried this but didn't work.
    #raw_email_str = raw_email.decode('utf-8', errors='ignore')  # Tried this but caused a TypeError down where var subject is created because something there is expecting a str or byte-like 
    email_message = email.message_from_string(raw_email_str)

    date_tuple = email.utils.parsedate_tz(email_message['Date'])           
    date_short = f'{date_tuple[1]}/{date_tuple[2]}/{date_tuple[0]}'

    # Header Details
    if date_short == '12/23/2019':
        #if date_tuple:
        #    local_date = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
        #    local_message_date = "%s" %(str(local_date.strftime("%a, %d %b %Y %H:%M:%S")))
        email_from = str(email.header.make_header(email.header.decode_header(email_message['From'])))
        subject = str(email.header.make_header(email.header.decode_header(email_message['Subject'])))
        #print(subject)
        if email_from.find('restaurants@uber.com') != -1:
            print('yay')

        # Body details
        if email_from.find('restaurants@uber.com') != -1 and subject.find('Payment Summary') != -1:
            for part in email_message.walk():
                if part.get_content_type() == "text/plain":
                    body = part.get_payload(decode=True)
                    body = body.decode("utf-8")             # Convert byte to str
                    body = body.replace("\r\n", " ")
                    text = re.sub(r'\w+:\/{2}[\d\w-]+(\.[\d\w-]+)*(?:(?:\/[^\s/]*))*', '', body)           # removes url links
                    text2 = text.translate(str.maketrans('', '', string.punctuation))
                    body_list = re.sub("[^\w]", " ",  text2).split()

                    print(body_list)
                    print(date_short)

                else:
                    continue

下面是一个示例,说明如何使用python标准库中的
imapclient
email.*
模块检索和读取邮件部分:

from imapclient import IMAPClient
import email
from email import policy


def walk_parts(part, level=0):
    print(' ' * 4 * level + part.get_content_type())
    # do something with part content (applies encoding by default)
    # part.get_content()
    if part.is_multipart():
        for part in part.get_payload():
            get_parts(part, level + 1)


# context manager ensures the session is cleaned up
with IMAPClient(host="your_mail_host") as client:
    client.login('user', 'password')

    # select some folder
    client.select_folder('INBOX')

    # do something with folder, e.g. search & grab unseen mails
    messages = client.search('UNSEEN')
    for uid, message_data in client.fetch(messages, 'RFC822').items():
        email_message = email.message_from_bytes(
            message_data[b'RFC822'], policy=policy.default)
        print(uid, email_message.get('From'), email_message.get('Subject'))

    # alternatively search for specific mails
    msgs = client.search(['SUBJECT', 'some subject'])

    #
    # do something with a specific mail:
    #

    # fetch a single mail with UID 12345
    raw_mails = client.fetch([12345], 'RFC822')

    # parse the mail (very expensive for big mails with attachments!)
    mail = email.message_from_bytes(
        raw_mails[12345][b'RFC822'], policy=policy.default)

    # Now you have a python object representation of the mail and can dig
    # into it. Since a mail can be composed of several subparts we have
    # to walk the subparts.

    # walk all parts at once
    for part in mail.walk():
        # do something with that part
        print(part.get_content_type())
    # or recurse yourself into sub parts until you find the interesting part
    walk_parts(mail)

有关详细信息,请参阅文档。在这里,您可以找到所有需要的位来读取邮件信息。

使用“ISO 8859-1”而不是“utf-8”

可以让您的生活更轻松,您可能想看看。它处理的是最低级的东西,非常容易使用。除非您实现邮件和imap RFC的所有边缘情况(包括不同邮件消息部分的不同编码等),否则您上面的代码永远不会可靠工作。谢谢@jerch!惊人的资源和它的工作!但是,它没有显示如何提取电子邮件的正文,这正是我想要解析的。是的,imapclient会停下来帮助处理消息本身(原因很简单,一个大的附件会惩罚解析,这是不必要的)。要可靠地解析原始邮件消息,请参阅stdlib模块,如
email.message
email.parser
()。遗憾的是,邮件消息可能很复杂(由于部分逻辑具有不同的编码和MIMEType),您必须通过文档来涵盖这些方面。谢谢@Jerch,您是否能够详细说明代码的外观?我尝试过各种形式的smtplib、imaplib、imapclient和电子邮件库,但都没有成功。我使用imaplib时非常接近,但无法找出问题所在。任何指导都会有帮助!尝试使用高级库:已解析所有内容。对于发件人声明错误内容传输编码的许多实际邮件,此操作仍可能失败。历史上,许多客户机声明了
“us ascii”
,但还是发送了一些未声明的8位编码;如今,许多人可能声称“utf-8”,但实际上却使用了其他东西。没错,但情况总是这样——如果某个东西表示为XY,但表示为Z,则会出现更大的问题(需要更复杂的恢复策略,而且不能如此简单地设计蓝图)。