不要相信requests返回的text

举报
云享专家 发表于 2019/10/18 10:42:01 2019/10/18
【摘要】 一句函数调用,就可以获得请求结果的对象response,通过response.content 可以得到原始的二进制数据,通过response.text可以得到解码后的文本数据,解码是根据response.encoding进行的。然而,requests对这个encoding(编码)的获取是有问题的。

ython的requests库是一个非常好用的库,这应该已经是大多写过爬虫的人的共识了。它的简洁易用给我们带来很大方便。然而,它也并不是非常完美。今天我们就说说它在处理中文编码方面的不足。


requests的使用非常简单,如下:

image.png


一句函数调用,就可以获得请求结果的对象response,通过response.content 可以得到原始的二进制数据,通过response.text可以得到解码后的文本数据,解码是根据response.encoding进行的。然而,requests对这个encoding(编码)的获取是有问题的。


它获取编码的过程分为两步,不幸的是每一步都有问题:


第一步:从http返回的headers里面找编码。

这一步的代码在源文件utils.py里面是get_encoding_from_headers(headers)函数:


image.png

最后两行代码,它认为headers里面的‘Content-Type’包含‘text’就是‘ISO-8859-1’编码。这种想法是不严谨的。


我们用chrome浏览器打开最开始代码中的那个网址,这是一个中文网页:

http://epaper.sxrb.com/

在用Chrome的F12查看http响应的头,如下:

image.png

这个网站给出的Content-Type不是下面的正规格式:

Content-Type: text/html; charset=UTF-8

然后,requests的get_encoding_from_headers函数就得到了ISO-8859-1的编码,再用这个编码去解码中文,当然就会出现乱码。


第二步:如果不能从响应headers得到编码,就用chardet从二进制的content猜测

严格讲,这步出现的编码问题不是requests的,而是chardet的,就判requests一个失察之责吧。

在requests的源码models.py中定义了requests.get()返回的类Response。我们再看看其中text()的定义:

image.png

响应头找不到编码时,self.encoding就是None。它就会通过self.apparent_encoding获得编码,那就再看看这个apparent_encoding是怎么来的:

image.png

很简单,就是通过chardet检测的。问题就出现在这个chardet上面。那我们就打破砂锅问到底,去看看chardet的代码。


image.png

上图是chardet的全部源代码。其中处理国标中文编码的gb2312开头的两个文件。我们用grep再看看全部代码中含有gb的部分:

grep -i gb *py 

image.png

以上说明,chardet对国标中文编码返回的就是(只是)GB2312。那么问题就来了,国标不只是GB2312,还有GBK,GB18030编码。

(1)GB 2312 标准共收录 6763 个汉字

(2)GBK 即汉字内码扩展规范,共收入 21886 个汉字和图形符号,兼容GB2312

(3)GB 18030 与 GB 2312-1980 和 GBK 兼容,共收录汉字70244个


由此可知,三种国标中文编码的汉字个数是如下关系:

GB2312 < GBK < GB18030

如果不属于GB2312的汉字用GB2312去编解码会出现上面问题呢?我们来做个实验:

image.png

例子中的“镕”字不在GB2312中,用这个编码时就会报错,用GBK编码后的二进制数据再用GB2312解码时同样会报错,都是因为“镕”不是GB2312里面的汉字。


这时候,我们像requests那样把errors设置为replace再用GB2312解码得到的文本就会有乱码出现,“镕”字变成乱码了。


最后我们用chardet检验二进制数据的编码,得到的是GB2312,但应该是GBK或GB18030编码。当然,chardet的这个bug已经有人在github提出issues,最早是2014年的#33, 后来有#99,#168,但是不懂中文的老外一直没有merge到master。


问题弄明白了,那么建议是什么呢?在爬虫中,尤其是抓取中文网页(非英文网页)时用cchardet检验response.content,而不是直接用response.text。


cchardet是uchardet的Python绑定,后者是用C++实现的字符编码检测库,来自Mozilla组织,质量过硬,速度更快,值得信赖。

image.png



--------------------------------------------------------------

一个写Python十余年的码奴

-------------------------------------------------------------


作者:王平

十年专注于python web 开发,网络爬虫。深入理解Python语言,对Python特性深度了解.


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。