解析Python requests响应内容编码规则
1.发现问题
我们在使用requests发送请求时,响应的内容有时候会出现乱码的情况,下面我举一个例子:
import requests
r = requests.get('http://www.baidu.com')
print(r.text) # 打印发现内容为乱码
我们可以使用r.encoding来查看编码解析text时我们的字符集编码是什么:
print(r.encoding)
打印结果:
然后我们在通过r.text查看到HTML本身的字符集编码是utf-8,所以这里才会出现乱码的情况
2.解决问题
2.1方式一
我们通过r.text查看到HTML或XML文本自身的字符集编码,然后通过r.encoding设置就行了
import requests
r = requests.get('http://www.baidu.com')
r.encoding = 'utf-8'
print(r.text) # 此时发现打印的内容就不是乱码了
2.2方式二
我们可以通过r.apparent_encoding去拿到字符集编码(由 charset_normalizer 或 chardet 库提供的表观编码)。
import requests
r = requests.get('http://www.baidu.com')
r.encoding = r.apparent_encoding
print(r.text) # 此时发现打印的内容就不是乱码了
3.分析问题
3.1 requests响应内容编码规则
我这里用的Pycharm,所以大家看步骤就好,有些快捷键可能不同的IDE不同。
- 按两下Shift键,打开搜索
- 搜索框输入utils,点击Classes
- 选择requests下的utils,并点击
- Ctrl+F,搜索输入:get_encoding_from_headers
这个函数就是requests的字符集编码规则获取方法,接下来我们分析一下里面的步骤(我用注释的方式来写):
def get_encoding_from_headers(headers):
"""Returns encodings from given HTTP Header Dict.
:param headers: dictionary to extract encoding from.
:rtype: str
"""
# 我们通过传入的请求头来拿到content-type信息,一般响应如果有指定字符集都会跟在content-type,例如:content-type: application/x-www-form-urlencoded; charset=UTF-8
content_type = headers.get('content-type')
# 这一步主要是判断是否有返content_type,如果没有就直接返回None
if not content_type:
return None
#这里主要是使用内置方法_parse_content_type_header将content_type和里面的charset分割开
content_type, params = _parse_content_type_header(content_type)
# 判断params里面有没有charset,也就是有的网站做的好的会跟上charset,跟上的话会直接返回跟上的字符集编码,我们上面请求的例子中你可以打印headers中的content-type,会发现没有charset
if 'charset' in params:
return params['charset'].strip("'\"")
# 这里就是requests针对没有写charset的情况,给对应文本类型返回默认字符集,这就是为什么我们上面的例子使用r.encoding打印出的结果是ISO-8859-1
if 'text' in content_type:
return 'ISO-8859-1'
# 这里就是针对没写charset,返回内容是json格式的时候,requests默认使用utf-8,所以一般使用json格式返回的内容用requests请求不容易乱码
if 'application/json' in content_type:
# Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset
return 'utf-8'
小结:
这里我们把为什么上面会出现乱码的问题重新分析了一下,因为访问后,它返回的content-type里面是没有写charset的,所以requests默认给了‘ISO-8859-1’编码,但它本身又是‘utf-8’所以会导致乱码。但是json格式的返回缺不容易出现乱码,因为json格式默认的就是‘utf-8’
3.2 r.apparent_encoding如何获取文本编码
- 首先通过Ctrl+单击,点击r.apparent_encoding的apparent_encoding
- 进入models.py模块
- 这里看不出什么,所以我们在深入一点,Ctrl+单击chardet.detect的detect
接下来我们分析一下里面的步骤(我用注释的方式来写):
def detect(byte_str: bytes) -> Dict[str, Optional[Union[str, float]]]:
"""
chardet legacy method
Detect the encoding of the given byte string. It should be mostly backward-compatible.
Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it)
This function is deprecated and should be used to migrate your project easily, consult the documentation for
further information. Not planned for removal.
:param byte_str: The byte sequence to examine.
"""
#这里是判断你传递来的字节字符串如果不是bytearray, bytes就抛出错误
if not isinstance(byte_str, (bytearray, bytes)):
raise TypeError( # pragma: nocover
"Expected object of type bytes or bytearray, got: "
"{0}".format(type(byte_str))
)
#如果传递的是bytearray类型,就转换成bytes
if isinstance(byte_str, bytearray):
byte_str = bytes(byte_str)
# 这里就是使用根据传入的字节字符串去匹配它最有可能的字符集
r = from_bytes(byte_str).best()
# 根据返回的内容回显到encoding
encoding = r.encoding if r is not None else None
language = r.language if r is not None and r.language != "Unknown" else ""
confidence = 1.0 - r.chaos if r is not None else None
# Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process
# but chardet does return 'utf-8-sig' and it is a valid codec name.
if r is not None and encoding == "utf_8" and r.bom:
encoding += "_sig"
return {
# 这里就是用了一个三目,如果encoding不在CHARDET_CORRESPONDENCE这个字典里,就用原来的encoding,如果在字典里,就从字典里取出规范的名称(就是大写),你可以Ctrl+单击,点进去看有那些字符集编码
"encoding": encoding
if encoding not in CHARDET_CORRESPONDENCE
else CHARDET_CORRESPONDENCE[encoding],
"language": language,
"confidence": confidence,
}
4.总结
分析到这里差不多就结束了,如果上面文章有什么不对的,请评论区高手指出我改正。
文章仅个人学习笔记,欢迎大家一起交流沟通学习。