我正在使用 Python 3.6.0b2。
我正在解析大量电子邮件。这封特定的电子邮件是一个问题,因为我无法打印电子邮件地址的显示名称。尝试打印电子邮件地址显示名称给出:
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed
下面是一段测试用例代码,展示了如何重现问题:
(venv3.6) mailripper@ip-10-0-0-112:/opt/mailripper$ cat test.py
from email import policy
from email.headerregistry import Address
from email.parser import BytesHeaderParser, BytesParser
email_bytes = b'From: =?utf-8?Q?John_Smith=2C_Prince2=C2=AE=2CPMP=C2=AE=2C_CSM=C2?=\r\n =?utf-8?Q?=AE=2C_ITIL=C2=AE=2C_ISTQB=C2=AE?= <jon.smith@example.org>\r\n'
msg = BytesHeaderParser(policy=policy.default).parsebytes(email_bytes)
print(msg['from'])
print(msg['from'].addresses[0].display_name)
这是上面代码产生的错误:
(venv3.6) mailripper@ip-10-0-0-112:/opt/mailripper$ python test.py
"John Smith, Prince2®,PMP®, CSM� �, ITIL®, ISTQB®" <jon.smith@example.org>
Traceback (most recent call last):
File "test.py", line 8, in <module>
print(msg['from'].addresses[0].display_name)
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed
这是 OSX 电子邮件客户端中显示的显示名称,它似乎能够正确解析它(这是屏幕截图,裁剪得很小):
我的目标是能够处理任何电子邮件而不会出现 unicode 错误,并且无需编写自定义 unicode 错误处理代码 - 这可能吗?
任何人都可以建议我可以做些什么来避免在显示电子邮件地址显示名称时出现 Unicode 错误吗?
最佳答案
你这里有一个棘手的问题。您的直接示例并不难:根据 RFC 2047 的规则,它是无效的. email.parser
模块有理由拒绝它。但是,电子邮件中充满了按规定无效的内容。电子邮件工具通常会努力从无效内容中挽救一些东西。您希望您的工具如何处理无效内容?
这是您的示例无效的地方。我把它缩短了一点。它的相关部分是这样的,
b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM=C2?=\r\n =?utf-8?Q?=AE=2C?= <jon@eg.org>\r\n'
这可能最初是字符串:From: John, PMP®, CSM®, <jon@eg.org>
.
这是一个 Python 字节字符串,包含一个 From:
header 作为编码词。规范是 RFC 2047, MIME Part Three: Message Header Extensions for Non-ASCII Text .
在示例中,您会看到两个序列,每个序列都是 =?utf-8?Q?
和 ?=
. RFC 2047, Section 2, "Syntax of encoded-words"告诉我们这些标记了两个 encoded-words 的开头和结尾,并且它们使用 UTF-8 字符集和 Quoted-Printable 编码。在“PMP”之后,是序列=C2=AE
。 .这编码了 2 个八位字节的 UTF-8 序列 0xC2 0xAE
, 即字符 '®'。序列 =2C
对 1 字节 UTF-8(和 ASCII)序列 0x2C 进行编码,即字符“,”。
第一个?=
之间的部分第二个=?utf-8?Q?
阅读,\r\n
.这是文字,未根据 RFC 2047 编码。它是通过插入行结尾和前导空白来延续长标题行。这也是相当合法的。
现在关注“CSM”。注意有一个序列 =C2
, 然后是第一个 ?=
这结束了第一个编码词。秒后=?utf-8?Q?
从第二个encoded-word开始,有一个序列=AE
.这是相同的 2 字节 UTF-8 序列 0xC2 0xAE
, 再次代表字符 '®'。但是,UTF-8 字符的两个八位位组在相邻的编码词 中分开。
这违反了 RFC 2047, Section 5, "Use of encoded-words in message headers" 的规则*。上面写着:
Each 'encoded-word' MUST represent an integral number of characters.
A multi-octet character may not be split across adjacent 'encoded-word's.
输入的这两个渲染中的任何一个都是有效的:
b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM=C2=AE?=\r\n =?utf-8?Q?=2C?= <jon@eg.org>\r\n'
b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM?=\r\n =?utf-8?Q?=C2=AE=2C?= <jon@eg.org>\r\n'
(这是我阅读规范时的结果。我没有运行代码来检查。)
现在,你问两个问题:
My goal is to be able to process any email without unicode errors, and without writing custom unicode error handling code - is that possible?
我的建议是“否”。如果您想处理任何电子邮件,您需要准备好处理格式不正确的电子邮件。您将需要编写自定义错误处理代码——不仅仅是针对 Unicode 问题,而是针对所有问题——以应对毫无疑问会被冲掉的奇怪东西。
Can anyone suggest what I can do to avoid getting Unicode errors when displaying email address display names?
对于这个例子,我可以看到三种方法:
看看 class
email.policy.EmailPolicy(**kw)
看看你是否能想出如何扩展它来处理这种编码不正确的内容。您将此类的亲戚传递为policy
在BytesHeaderParser(policy=policy.default).parsebytes(email_bytes)
.预处理所有标题行,查看此问题的连续编码字 结尾和开头的字节。使用您自己的代码修复它,然后将更正后的标题提供给
BytesHeaderParser()
.也许你可以写一个 regular expression这可以检测到问题。将您的电话转接至
BytesHeaderParser()
在异常处理程序中,它将仅针对失败的行尝试#2 中的修复。修复线路后,您可以尝试BytesHeaderParser()
再次。
还会有其他问题。考虑构建您的代码,以便能够在您发现需要时容纳越来越多的无效内容修复。
关于python - 'utf- 8' codec can' t 编码字符 '\udcc2' : surrogates not allowed,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43335607/