使用email.parser.Parser创建邮件消息时的Unicode编码错误问题排查与解决
两种邮件创建方式的Unicode处理差异原因及解决方法
差异原因
咱们先拆解下两种方式的核心区别:
用
email.message.EmailMessage()直接构建消息时,这个类的设计就是原生支持Unicode的。不管是设置头部(比如msg['Subject'])还是调用set_content(),它都会自动按照email.policy.default的规则处理:- 头部里的Unicode字符会被自动编码成RFC 2047格式(比如
déjeuner会变成=?utf-8?q?d=C3=A9jeuner?=),完全符合邮件标准对头部的编码要求; - 正文内容会自动设置
Content-Type: text/plain; charset="utf-8"和对应的传输编码,smtplib发送时能直接识别并正确处理。
- 头部里的Unicode字符会被自动编码成RFC 2047格式(比如
而用
email.parser.Parser().parsestr()解析字符串的方式,问题出在你传入的是包含原生Unicode字符的字符串。Parser的parsestr方法会把整个消息当作“已经解码完成的文本”,不会自动对头部的Unicode字符做RFC 2047编码。当smtplib的send_message()尝试把消息转换为字节流发送时,它默认会用ASCII编码处理头部,遇到à、é这类非ASCII字符自然就抛出UnicodeEncodeError了。
解决方法
最靠谱的解决方式是用BytesParser解析字节流,因为邮件在传输层本质就是字节流,这种方式更贴合邮件的实际格式:
msgsource = """\ From: sender@example.com To: recipient@example.com Subject: Ayons asperges pour le déjeuner Cela ressemble à un excellent recipie déjeuner. """ # 将字符串转为UTF-8编码的字节流 msg_bytes = msgsource.encode('utf-8') # 使用BytesParser解析字节流,而非Parser解析字符串 msg = email.parser.BytesParser(policy=email.policy.default).parsebytes(msg_bytes)
这样处理后,BytesParser会正确识别字节流的UTF-8编码,自动把头部的Unicode字符转为符合标准的RFC 2047格式,正文也会正确设置编码信息,smtplib发送时就不会再报错了。
如果你确实需要用字符串解析,也可以在解析后手动为消息补全编码处理,但这种方式不如字节流解析直接:
msg = email.parser.Parser(policy=email.policy.default).parsestr(msgsource) # 手动重置正文的编码设置 msg.set_content(msg.get_content(), charset='utf-8') # 手动重新编码所有头部,触发自动转RFC 2047格式的逻辑 for header_name in ['Subject', 'From', 'To']: value = msg[header_name] del msg[header_name] msg[header_name] = value
不过这种方式需要额外处理头部,不如第一种方法简洁可靠。
内容的提问来源于stack exchange,提问作者VPfB
相关产品推荐
相关产品推荐

