python urllib 返回两个 0xfffd 字符而不是预期的 Unicode 字符
我在抓取一个网站,网站的编码是unicode:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
为此我使用了urllib:
html = unicode(urllib.openurl(url).read(), 'utf-8')
有时候,一些unicode字符会被随机替换成两个0xFFFD字符。我猜是因为一个用4个字节编码的字符被破坏了,然后被替换成两个0xFFFD,分别对应两个2字节的部分。这样的替换概率很低,大概是每50万个字符中才会有一个被替换,差不多是这个意思。看起来只有非ascii字符会出现这种问题。
当我在浏览器中打开同样的页面时,并没有观察到这些替换。
我该如何找出问题出在哪里?是服务器发送了损坏的字节,还是客户端没能正确读取它们?
@John Machin:
- Python版本是2.7.1
- 在所有浏览器和大多数http客户端(比如libcurl、wget、python)中都能观察到这个问题
- 其实我之前说错了 - 有时候这些错误会在浏览器中显示出来,呈现为两个带问号的菱形字符。
- 这些错误是随机的。有时候会出现,有时候不会。页面越大,出现错误的几率就越大。
所以我觉得这个错误是在网页服务器那边产生的。我打算就忽略它们。
3 个回答
浏览器会做很多神奇的事情来修正无效的字符。你可以试着查看一下通过 .read()
返回的原始字节,看看源页面是否有无效字符。例如,有时候页面会用latin-1这种编码来表示带重音的字母,而其他字符则可能用utf-8编码。
有可能服务器和客户端在重复之前的错误。场景是:有人用 cp1252
编码了一些文本(这个编码常常出问题)。然后他们用 UTF-8 解码,并选择了“替换”选项,这样就引入了 u"\ufffd" 这个字符,最后再把结果编码回去。
比如,原始文本是:Seine Füße sind wund.
>>> original = "Seine F\xfc\xdfe sind wund." # encoded in cp1252
>>> ucode1 = original.decode('utf8', 'replace')
>>> ucode1
u'Seine F\ufffd\ufffde sind wund.'
>>> input_to_server = ucode1.encode('utf8')
>>> input_to_server
'Seine F\xef\xbf\xbd\xef\xbf\xbde sind wund.'
>>> observed_result = unicode(input_to_server, 'utf8')
>>> observed_result
u'Seine F\ufffd\ufffde sind wund.'
>>>
无论如何,如果你不提供更多信息,所有的回答都只能是猜测,比如:
你用的是哪个版本的 Python(请具体说明,比如 2.7.1)
你用哪个浏览器查看这个网址?“查看文本”显示了什么?
你说“在浏览器中渲染同一页面时没有看到这些替换。”好吧,你的代码输出是
rhubarb \ufffd\ufffd artichoke
,而在浏览器中你看到的是rhubarb PLEASE-TELL-US-WHAT-YOU-SEE-HERE artichoke
——请告诉我们更多信息。有没有什么特别的原因让你不能透露你遇到问题的网址?
我看到有两种可能性。
- 如果在调用
.read()
时,最后只返回了一个多字节序列的一部分,那么这部分不完整的序列会被替换成 U+FFFD。你可以查看 关于 UTF-8 编码的解释。通过检查编码字符的第一个字节中前导的 1 位数,可以很容易地检测到不完整的字节序列。这样你就可以解码除了不完整序列之外的所有内容,然后在下一次读取时继续。对于 U+007F 及以下的字符,它们只用一个字节编码,所以这个问题不会影响到这些字符。 - 可能文档中有
charset=utf-8
的声明,但实际上并没有用 UTF-8 编码。那么任何不符合有效 UTF-8 序列的字节都会被替换成 U+FFFD。
如果你能提供一些从服务器获取的示例字节序列,以及每次读取操作后得到的字符字符串,我们就能更准确地判断发生了什么。