如果 http 服务器返回 Content-type: text/* 没有编码,Response.text 总是将其解码为 'ISO-8859-1' 文本。
它可能在 RFC2616/3.7.1 中有效,但这在 2013 年的现实生活中是错误的。
我用中文制作了示例页面:
http://lavr.github.io/python-emails/tests/requests/some-utf8-text.html
所有浏览器都正确呈现此页面。
但 reguests.get 返回无效文本。
这是使用该网址的简单测试:
https://gist.github.com/lavr/6572927
感谢这个@lavr!
这是请求的深思熟虑的设计决策。 我们一直在遵循规范,除非我们发现自己处于规范与现实世界行为如此大相径庭的位置,以至于它成为一个问题(例如,在对 POST 的 302 响应之后的 GET)。
如果上游服务器知道正确的编码是什么,它应该发出信号。 否则,我们将遵循规范所说的。 =)
如果您认为规范默认值不好,我强烈建议您参与 HTTP/2.0 的 RFC 流程,以便更改此默认值。 =)
@Lukasa所说的 + 事实是,如果从标头中检索到的编码不存在,我们将依靠字谜来猜测编码。 由于字符很少,charade 不会返回任何明确的信息,因为它使用统计数据来猜测正确的编码是什么。
坦率地说,年份没有区别,也不会改变规格。
如果你知道你期望的是什么编码,你也可以像这样自己解码:
text = str(r.content, '<ENCODING>', errors='replace')
就我而言,请求没有任何问题,这也不是字谜中的错误。 由于@Lukasa似乎同意我的
@lavr (/cc @sigmavirus24),比这更简单,您可以简单地自己提供编码。
>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'
然后,正常进行。
@kennethreitz令人失望。 为什么我们要让人们这么容易? =P
绝对地 :)
主要用于日本网站。 他们都在他们的编码上撒谎。
@西格玛病毒24
请注意,utils.get_encoding_from_headers 总是返回'ISO-8859-1',并且charade 没有机会被调用。
所以错误是:我们期望使用 charade 来猜测编码,但事实并非如此。
上面的补丁修复了一个错误,但仍遵循 RFC。
请考虑审查它。
@lavr抱歉,我们没有说清楚。 我们_不_期望在这种情况下调用 charade。 RFC 非常清楚:如果您不指定字符集,并且 MIME 类型为text/*
,则必须假定编码为 ISO-8859-1。 这意味着“不要猜测”。 =)
@lavr :只需将r.encoding
为None
,它就会按您的预期工作(我认为)。
或者做r.encoding = r.apparent_encoding
。
甚至更好。
在r.encoding = None
和r.encoding = r.apparent_encoding
我们丢失了服务器字符集信息。
我认为,完全忽略服务器标头并不是一个好的解决方案。
正确的解决方案是这样的:
r = requests.get(...)
params = cgi.parse_header(r.headers.get('content-type'))[0]
server_encoding = ('charset' in params) and params['charset'].strip("'\"") or None
r.encoding = server_encoding or r.apparent_encoding
text = r.text
看起来很奇怪:(
或者这样做:
r = requests.get(...)
if r.encoding is None or r.encoding == 'ISO-8859-1':
r.encoding = r.apparent_encoding
我不这么认为:)
条件r.encoding is None
没有意义,因为对于 content-type=text/*,r.encoding 永远不可能是 None。
r.encoding == 'ISO-8859-1'
...是什么意思? 服务器发送字符集='ISO-8859-1' 或服务器未发送字符集? 如果首先,我不应该猜测字符集。
@lavr我正在讨论非文本基础。 您可以使用以下条件来排除charset
可能性:
r.encoding == 'ISO-8859-1' and not 'ISO-8859-1' in r.headers.get('Content-Type', '')
@卢卡萨
嗯,我可以使用这个黑客。
东欧和亚洲的每个人都可以使用它。
但是如果我们在请求中修复它呢? ;)
如果请求可以在没有字符集的情况下诚实地设置enconding=None
响应呢?
正如我们多次讨论过的,Requests 完全遵循 HTTP 规范。 目前的行为并没有错。 =)
它对您的用例没有帮助的事实是另一回事。 =)
好了,讨论到此就够了。 感谢您的反馈。
更新的 HTTP 1.1 废弃了 ISO-8859-1 默认字符集: http :
我们已经在#2086 中跟踪了这一点。 =)
它可能关心的人,这里是一个兼容性补丁
使用以下代码创建文件requests_patch.py
并导入它,那么问题应该解决了。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import chardet
def monkey_patch():
prop = requests.models.Response.content
def content(self):
_content = prop.fget(self)
if self.encoding == 'ISO-8859-1':
encodings = requests.utils.get_encodings_from_content(_content)
if encodings:
self.encoding = encodings[0]
else:
self.encoding = chardet.detect(_content)['encoding']
if self.encoding:
_content = _content.decode(self.encoding, 'replace').encode('utf8', 'replace')
self._content = _content
self.encoding = 'utf8'
return _content
requests.models.Response.content = property(content)
monkey_patch()
@lavr (/cc @sigmavirus24),比这更简单,您可以简单地自己提供编码。
>>> r = requests.get('http://irresponsible-server/') >>> r.encoding = 'utf-8'
然后,正常进行。
谢谢你! 关于如何在一行中做到这一点的任何想法?
最有用的评论
@lavr (/cc @sigmavirus24),比这更简单,您可以简单地自己提供编码。
然后,正常进行。