Python Requests编码问题解决方案

在使用python requests时,有时会出现编码错误的问题,导致的主要原因是编码识别出错了。当获取到的内容出现乱码时,最常出现的错误是将编码识别成了ISO-8859-1。

ISO-8859-1是什么?

ISO 8859-1,正式编号为ISO/IEC 8859-1:1998,又称Latin-1或“西欧语言”,是国际标准化组织内ISO/IEC 8859的第一个8位字符集。它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入96个字母及符号,藉以供使用附加符号的拉丁字母语言使用。

为什么会被识别成ISO-8859-1?

通过出错的页面,我们发现在HTML中已经指明了页面具体的编码还是会出错。


具体原因是?通过查看源码,可以发现默认的编码识别比较简单,直接从响应头文件的Content-Type里获取,如果存在charset,则可以正确识别,如果不存在charset但是存在text就认为是ISO-8859-1,见Libsite-packagesrequestsutils.py。

def get_encoding_from_headers(headers):
    """Returns encodings from given HTTP Header Dict.
 
    :param headers: dictionary to extract encoding from.
    """
    content_type = headers.get('content-type')
 
    if not content_type:
        return None
 
    content_type, params = cgi.parse_header(content_type)
 
    if 'charset' in params:
        return params['charset'].strip("'"")
 
    if 'text' in content_type:
        return 'ISO-8859-1'

可以看到,requests会先去找HTTP头部的“Content-Type”字段,如果没有,就调用“charade”来猜测编码。而默认的编码格式正是“ISO-8859-1”。代码中并没有去获取HTML页面中指定的编码格式,关于这样的处理,很多人也在官方的issues中提交了 吐槽
,但是作者回复是严格http协议标准写这个库的,HTTP协议 RFC 2616中提到,如果HTTP响应中Content-Type字段没有指定charset,则默认页面是’ISO-8859-1’编码。

When you receive a response, Requests makes a guess at the encoding to use for decoding the response when you call the Response.text method. Requests will first check for an encoding in the HTTP header, and if none is present, will use charade to attempt to guess the encoding.
The only time Requests will not do this is if no explicit charset is present in the HTTP headers and the Content-Type header contains text. In this situation, RFC 2616 specifies that the default charset must be ISO-8859-1. Requests follows the specification in this case. If you require a different encoding, you can manually set the Response.encoding property, or use the raw Response.content.

乱码解决方案

如果你知道明确的编码,解决方案非常简单,只需指定具体的编码即可。

resp = requests.get(url)
resp.encoding = 'utf-8'
print(resp.text)

resp = requests.get(url)
print(resp.text.encode(resp.encoding).decode('utf-8'))

当不清楚具体编码的情况下,如何获取准确的页面编码?

其实Requests提供了从内容获取编码,只是在默认中没有使用,见Libsite-packagesrequestsutils.py:

def get_encodings_from_content(content):
    """Returns encodings from given content string.
 
    :param content: bytestring to extract encodings from.
    """
    warnings.warn((
        'In requests 3.0, get_encodings_from_content will be removed. For '
        'more information, please see the discussion on issue #2266. (This'
        ' warning should only appear once.)'),
        DeprecationWarning)
 
    charset_re = re.compile(r']', flags=re.I)
    pragma_re = re.compile(r']', flags=re.I)
    xml_re = re.compile(r'^]')
 
    return (charset_re.findall(content) +
            pragma_re.findall(content) +
            xml_re.findall(content))

另外还提供了使用chardet的编码检测,见models.py:

    @property
    def apparent_encoding(self):
        """The apparent encoding, provided by the chardet library."""
        return chardet.detect(self.content)['encoding']

针对这三种方式,识别编码的速率及需要的资源不同。

import requests
import cProfile
 
r = requests.get("https://www.biaodianfu.com")
def charset_type():
    char_type = requests.utils.get_encoding_from_headers(r.headers)
    return(char_type)
 
def charset_content():
    charset_content = requests.utils.get_encodings_from_content(r.text)
    return charset_content[0]
 
def charset_det():
    charset_det = r.apparent_encoding
    return charset_det
 
if __name__ == '__main__':
    cProfile.run("charset_type()")
    cProfile.run("charset_content()")
cProfile.run("charset_det()")

执行结果

charset_type()                                   25 function calls in 0.000 seconds
charset_content()       2137 function calls (2095 primitive calls) in 0.007 seconds
charset_det()       292729 function calls (292710 primitive calls) in 0.551 seconds

最终解决方案,Python2,创建一个requests_patch.py文件,并在代码使用前import它,问题即可解决:

import requests
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 = self.apparent_encoding
            _content = _content.decode(self.encoding, 'replace').encode('utf8', 'replace')
            self._content = _content
        return _content
    requests.models.Response.content = property(content)
monkey_patch()

上述代码在Python3 下执行会报如下错误:TypeError: cannot use a string pattern on a bytes-like object,主要原因是python3中.content返回的非字符串数据。想在Python下获取正确的编码,具体代码如下:

import requests
 
def charsets(res):
    _charset = requests.utils.get_encoding_from_headers(res.headers)
    if _charset == 'ISO-8859-1':
        __charset = requests.utils.get_encodings_from_content(res.text)
        if __charset:
            _charset = __charset[0]
        else:
            _charset = res.apparent_encoding
 
    return _charset
稿源:标点符 (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » Python Requests编码问题解决方案

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录