转自:代码分析Python requests库中文编码问题

  Python reqeusts在作为代理爬虫节点抓取不同字符集网站时遇到的一些问题总结. 简单说就是中文乱码的问题.   如果单纯的抓取微博,微信,电商,那么字符集charset很容易就确认,你甚至可以单方面把encoding给固定住。 但作为舆情数据来说,他每天要抓取几十万个不同网站的敏感数据,所以这就需要我们更好确认字符集编码,避免中文的乱码情况.

  我们首先看这个例子. 你会发现一些有意思的事情.

In [9]: r = requests.get('http://cn.python-requests.org/en/latest/')

In [10]: r.encoding
Out[10]: 'ISO-8859-1' In [11]: type(r.text)
Out[11]: unicode In [12]: type(r.content)
Out[12]: str In [13]: r.apparent_encoding
Out[13]: 'utf-8' In [14]: chardet.detect(r.content)
Out[14]: {'confidence': 0.99, 'encoding': 'utf-8'}

第一个问题是,为什么会有ISO-8859-1这样的字符集编码?

  iso-8859是什么?  他又被叫做Latin-1或“西欧语言” .  对于我来说,这属于requests的一个bug,在requests库的github里可以看到不只是中国人提交了这个issue.  但官方的回复说是按照http rfc设计的。

下面通过查看requests源代码,看这问题是如何造成的 !

  requests会从服务器返回的响应头的 Content-Type 去获取字符集编码,如果content-type有charset字段那么requests才能正确识别编码,否则就使用默认的 ISO-8859-1. 一般那些不规范的页面往往有这样的问题.

In [52]: r.headers
Out[52]: {'content-length': '', 'via': 'BJ-H-NX-116(EXPIRED), http/1.1 BJ-UNI-1-JCS-116 ( [cHs f ])', 'ser': '3.81', 'content-encoding': 'gzip', 'age': '', 'expires': 'Fri, 19 Feb 2016 07:36:25 GMT', 'vary': 'Accept-Encoding', 'server': 'JDWS', 'last-modified': 'Fri, 19 Feb 2016 07:35:25 GMT', 'connection': 'keep-alive', 'cache-control': 'max-age=60', 'date': 'Fri, 19 Feb 2016 07:35:31 GMT', 'content-type': 'text/html;'}

文件: requests.utils.py

def get_encoding_from_headers(headers):
"""通过headers头部的dict中获取编码格式""" 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的返回结果对象里有个apparent_encoding函数, apparent_encoding通过调用chardet.detect()来识别文本编码. 但是需要注意的是,这有些消耗计算资源.
至于为毛,可以看看chardet的源码实现.

@property
def apparent_encoding(self):
"""使用chardet来计算编码"""
return chardet.detect(self.content)['encoding']

第三个问题,requests的text() 跟 content() 有什么区别?

  requests在获取网络资源后,我们可以通过两种模式查看内容。 一个是r.text,另一个是r.content,那他们之间有什么区别呢?

  分析requests的源代码发现,r.text返回的是处理过的Unicode型的数据,而使用r.content返回的是bytes型的原始数据。也就是说,r.content相对于r.text来说节省了计算资源,r.content是把内容bytes返回. 而r.text是decode成Unicode. 如果headers没有charset字符集的化,text()会调用chardet来计算字符集,这又是消耗cpu的事情.

通过看requests代码来分析text() content()的区别.

文件: requests.models.py

@property
def apparent_encoding(self):
"""The apparent encoding, provided by the chardet library"""
return chardet.detect(self.content)['encoding'] @property
def content(self):
"""Content of the response, in bytes.""" if self._content is False:
# Read the contents.
try:
if self._content_consumed:
raise RuntimeError(
'The content for this response was already consumed') if self.status_code == 0:
self._content = None
else:
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes() except AttributeError:
self._content = None self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
# since we exhausted the data.
return self._content @property
def text(self):
"""Content of the response, in unicode.
If Response.encoding is None, encoding will be guessed using
``chardet``.
The encoding of the response content is determined based solely on HTTP
headers, following RFC 2616 to the letter. If you can take advantage of
non-HTTP knowledge to make a better guess at the encoding, you should
set ``r.encoding`` appropriately before accessing this property.
""" # Try charset from content-type
content = None
encoding = self.encoding if not self.content:
return str('') # 当为空的时候会使用chardet来猜测编码.
if self.encoding is None:
encoding = self.apparent_encoding # Decode unicode from given encoding.
try:
content = str(self.content, encoding, errors='replace')
except (LookupError, TypeError):
# A LookupError is raised if the encoding was not found which could
# indicate a misspelling or similar mistake.
#
# A TypeError can be raised if encoding is None
#
# So we try blindly encoding.
content = str(self.content, errors='replace')

解决方案:

对于requests中文乱码解决方法有这么几种.

1.由于content是HTTP相应的原始字节串,可以根据headers头部的charset把content decode为unicode,前提别是ISO-8859-1编码.

In [96]: r.encoding
Out[96]: 'gbk' In [98]: print r.content.decode(r.encoding)[200:300]
="keywords" content="Python数据分析与挖掘实战,,机械工业出版社,9787111521235,,在线购买,折扣,打折"/>

另外有一种特别粗暴方式,就是直接根据chardet的结果来encode成utf-8格式.

#http://xiaorui.cc

In [22]: r  = requests.get('http://item.jd.com/1012551875.html')

In [23]: print r.content
KeyboardInterrupt In [23]: r.apparent_encoding
Out[23]: 'GB2312' In [24]: r.encoding
Out[24]: 'gbk' In [25]: r.content.decode(r.encoding).encode('utf-8')
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-25-918324cdc053> in <module>()
----> 1 r.content.decode(r.apparent_encoding).encode('utf-8') UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 49882-49883: illegal multibyte sequence In [27]: r.content.decode(r.apparent_encoding,'replace').encode('utf-8')

如果在确定使用text,并已经得知该站的字符集编码时,可以使用 r.encoding = ‘xxx’ 模式, 当你指定编码后,requests在text时会根据你设定的字符集编码进行转换.

>>> import requests
>>> r = requests.get('https://up.xiaorui.cc')
>>> r.text
>>> r.encoding
'gbk'
>>> r.encoding = 'utf-8'

2.使用正则从html中的meta中获取

根据我抓几十万的网站的经验,大多数网站还是很规范的,如果headers头部没有charset,那么就从html的meta中抽取.

In [78]: s
Out[78]: ' <meta http-equiv="Content-Type" content="text/html; charset=gbk"' In [79]: b = re.compile("<meta.*content=.*charset=(?P<charset>[^;\s]+)", flags=re.I) In [80]: b.search(s).group(1)
Out[80]: 'gbk"'

python requests的utils.py里已经有个完善的从html中获取meta charset的函数. 说白了还是一对的正则表达式.

In [32]: requests.utils.get_encodings_from_content(r.content)
Out[32]: ['gbk']

文件: utils.py

def get_encodings_from_content(content):
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') return (charset_re.findall(content) +
pragma_re.findall(content) +
xml_re.findall(content))

最后,针对requests中文乱码的问题总结:

统一编码,要不都成utf-8, 要不就用unicode做中间码 !

国内的站点一般是utf-8、gbk、gb2312  , 当requests的encoding是这些字符集编码后,是可以直接decode成unicode.

但当你判断出encoding是 ISO-8859-1 时,可以结合re正则和chardet判断出他的真实编码. 可以把这逻辑封装补丁引入进来.

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.x解决了这编码问题,如果你还是python2.6 2.7,那么还需要用上面的方法解决中文乱码的问题.

END.

关于requests库中文编码问题的更多相关文章

  1. Python学习--- requests库中文编码问题

    为什么会有ISO-8859-1这样的字符集编码 requests会从服务器返回的响应头的 Content-Type 去获取字符集编码,如果content-type有charset字段那么request ...

  2. python的Requests库的使用

    Requests模块: Requests 是用Python语言编写,基于 urllib,采用 Apache2 Licensed 开源协议的 HTTP 库.它比 urllib 更加方便,可以节约我们大量 ...

  3. Python爬虫小白入门(二)requests库

    一.前言 为什么要先说Requests库呢,因为这是个功能很强大的网络请求库,可以实现跟浏览器一样发送各种HTTP请求来获取网站的数据.网络上的模块.库.包指的都是同一种东西,所以后文中可能会在不同地 ...

  4. Requests库上传文件时UnicodeDecodeError: 'ascii' codec can't decode byte错误解析

    在使用Request上传文件的时候碰到如下错误提示: 2013-12-20 20:51:09,235 __main__ ERROR 'ascii' codec can't decode byte 0x ...

  5. Requests库的几种请求 - 通过API操作Github

    本文内容来源:https://www.dataquest.io/mission/117/working-with-apis 本文的数据来源:https://en.wikipedia.org/wiki/ ...

  6. python脚本实例002- 利用requests库实现应用登录

    #! /usr/bin/python # coding:utf-8 #导入requests库 import requests #获取会话 s = requests.session() #创建登录数据 ...

  7. 大概看了一天python request源码。写下python requests库发送 get,post请求大概过程。

    python requests库发送请求时,比如get请求,大概过程. 一.发起get请求过程:调用requests.get(url,**kwargs)-->request('get', url ...

  8. python WEB接口自动化测试之requests库详解

    由于web接口自动化测试需要用到python的第三方库--requests库,运用requests库可以模拟发送http请求,再结合unittest测试框架,就能完成web接口自动化测试. 所以笔者今 ...

  9. python爬虫从入门到放弃(四)之 Requests库的基本使用

    什么是Requests Requests是用python语言基于urllib编写的,采用的是Apache2 Licensed开源协议的HTTP库如果你看过上篇文章关于urllib库的使用,你会发现,其 ...

随机推荐

  1. SQL: 某个时间段范围内,产品有价格,且求平均数

    select ID,AVG(fPrice) as avgPrice from Price where Hp_Date >='2017-07-04' and Hp_Date <='2017- ...

  2. [zz]LyX中文问题

    http://www.cnblogs.com/biaoyu/archive/2012/04/28/2475318.html LyX是一款极为优秀的所见即所得的文档处理软件,与MS Word相比,其排版 ...

  3. centos7 源码安装redis

    安装3.x [root@node1 ~]# yum install wget gcc-c++ make [root@node1 ~]# wget http://download.redis.io/re ...

  4. kafka partition(分区)与 group(转)

    原文  https://www.cnblogs.com/liuwei6/p/6900686.html 一. 1.原理图 2.原理描述 一个topic 可以配置几个partition,produce发送 ...

  5. TensorFlow NormLization

    local_response_normalization local_response_normalization出现在论文”ImageNet Classification with deep Con ...

  6. laravel中消息通知功能

    以laravel5.5为例子,这个功能laravel自带的有: 1.生成表文件的migration文件,再migrate一下在数据库里生成表.命令为:php artisan notifications ...

  7. yum梳理

  8. JDK1.7 的 HashMap

    HashMap是一个用于存储key-value的键值对集合,每个键值对都是一个Entry.这些键值对分散存储在一个数组中,这个数组就是HashMap的主干. HashMap每个初始值都为null. 1 ...

  9. jmeter 连接 MySql

    1.连接 mysql 驱动包安装下载 1)首先需要安装 mysql 驱动包,下载地址:https://dev.mysql.com/downloads/connector/j/5.1.html 2)下载 ...

  10. 简单定时器的Java实现

    这两个类使用起来非常方便,可以完成我们对定时器的绝大多数需求 Timer类是用来执行任务的类,它接受一个TimerTask做参数 Timer有两种执行任务的模式,最常用的是schedule,它可以以两 ...