1 回答
你有一个Mojibake编码。编码的字节是 UTF-8 字节的 Latin-1 解释:
>>> from urllib.parse import quote
>>> text = 'huà 話 用'
>>> quote(text)
'hu%C3%A0%20%E8%A9%B1%20%E7%94%A8'
>>> quote(text.encode('utf8').decode('latin1'))
'hu%C3%83%C2%A0%20%C3%A8%C2%A9%C2%B1%20%C3%A7%C2%94%C2%A8'
您可以通过再次手动编码为 Latin-1,然后从 UTF-8 解码来反转该过程:
>>> unquote('hu%C3%83%C2%A0%20%C3%A8%C2%A9%C2%B1%20%C3%A7%C2%94%C2%A8').encode('latin1').decode('utf8')
'huà 話 用'
或者您可以使用该ftfy
库自动修复错误的编码(ftfy
通常会做得更好,尤其是当 Mojibake 中涉及 Windows 代码页时):
>>> from ftfy import fix_text
>>> fix_text(unquote('hu%C3%83%C2%A0%20%C3%A8%C2%A9%C2%B1%20%C3%A7%C2%94%C2%A8'))
'huà 話 用'
您说的是 URL 的来源:
离开服务器的位置标头是可以的;它被编码为 UTF-8
那是你的问题,就在那里。HTTP 标头始终编码为 Latin-1 (*)。服务器必须将 Location 标头设置为完全百分比编码的 URL,以便所有 UTF-8 字节都表示为%HH
转义序列。这些只是 ASCII 字符,完美地保存在 Latin-1 上下文中。
如果您的服务器将标头作为未转义的 UTF-8 字节发送,则 HTTP 客户端(包括requests
)会将其解码为 Latin-1,而不是导致您观察到的确切 Mojibake 问题。并且由于 URL 包含无效的 URL 字符,requests
将 Mojibake 结果转义为百分比编码版本。
(*)实际上,Location
标头应该是符合absoluteURI
RFC2396的,它始终是 ASCII(7 位)干净数据,但由于其他一些 HTTP 标头允许“描述性”文本,Latin-1 (ISO-8859-1) 是接受标头数据的默认编码。请参阅TEXT
HTTP/1.1 RFC 的 2.2 节中的规则,并且在解码任何标头中的非 ASCII 数据时,最终解码标头的http.client
模块在这方面遵循此 RFC。仅当按照Message Header Extensions RFC 2047 进行requests
包装时,您才能提供非拉丁 1 数据,但这不适用于标头。Location