python编码及requests乱码问题

1.字符编码简介

1.1. ASCII
ASCII(American Standard Code for Information Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节可以表示256个不同的字符,可以表示所有的英文字符和许多的控制符号。不过ASCII只用到了其中的一半(\x80以下),这也是MBCS得以实现的基础。

1.2. MBCS
然而计算机世界里很快就有了其他语言,单字节的ASCII已无法满足需求。后来每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使用了多字节来表示字符,如GBxxx、BIGxxx等等,他们的规则是,如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。

这里,IBM发明了一个叫Code Page的概念,将这些编码都收入囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使用CP936表示GBK。

MBCS(Multi-Byte Character Set)是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某一种特定的编码,Windows里根据你设定的区域不同,MBCS指代不同的编码,而Linux里无法使用MBCS作为编码。在Windows中你看不到MBCS这几个字符,因为微软为了更加洋气,使用了ANSI来吓唬人,记事本的另存为对话框里编码ANSI就是MBCS。同时,在简体中文Windows默认的区域设定里,指代GBK。

1.3. Unicode
后来,有人开始觉得太多编码导致世界变得过于复杂了,让人脑袋疼,于是大家坐在一起拍脑袋想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。

最初的Unicode标准UCS-2使用两个字节表示一个字符,所以你常常可以听到Unicode使用两个字节表示一个字符的说法。但过了不久有人觉得256*256太少了,还是不够用,于是出现了UCS-4标准,它使用4个字节表示一个字符,不过我们用的最多的仍然是UCS-2。

UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已,比如"汉"这个字的码位是6C49。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。

一开始这事很简单,直接使用UCS的码位来保存,这就是UTF-16,比如,"汉"直接使用\x6C\x49保存(UTF-16-BE),或是倒过来使用\x49\x6C保存(UTF-16-LE)。但用着用着美国人觉得自己吃了大亏,以前英文字母只需要一个字节就能保存了,现在大锅饭一吃变成了两个字节,空间消耗大了一倍……于是UTF-8横空出世。

UTF-8是一种很别扭的编码,具体表现在他是变长的,并且兼容ASCII,ASCII字符使用1字节表示。然而这里省了的必定是从别的地方抠出来的,你肯定也听说过UTF-8里中文字符使用3个字节来保存吧?4个字节保存的字符更是在泪奔……(具体UCS-2是怎么变成UTF-8的请自行搜索)

2.python2.x 3.x编码问题

  1. 源码编码方式(.py文件的字符集),
  2. 执行编码方式(程序运行加载到内存的编码方式)
    python2.x默认执行编码方式为ascii 所以我们需要再py文件头加入一行代码来指定一个确定的执行编码方式(内存中加载的字符串使用的就是执行编码方式)
# -*- coding:utf-8 -*-

python3.x默认执行编码方式为utf-8所以无需增加此行代码

**无论是python2.7还是python3.6,执行编码方式和源码编码方式必须一致,虽然文件开头        # --coding:xxx --用于指定执行编码方式,但是也会决定python解释器读取源文件时的编码方式,如果源文件是gbk格式,但是开头xxx为utf-8,那么python解释器将会以utf-8编码读取gbk格式的文件,严重时将会导致错误,所以,一定要保证xxx和源文件的编码方式一致,即执行编码方式和源码编码方式一致。

py2中python中任意两种不同字符编码的转换都是以unicode为基石
image.png

python2.x和python3.x中bytes、str和unicode
image.png

引用链接:https://blog.csdn.net/xiaoyink/java/article/details/80850448

3.requests获取网页乱码问题

运行环境python3.6
例如:

# -*- coding: utf-8 -*
import requests
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,ko;q=0.6'}

def main(url):
    res = requests.get(url,headers=headers)
    print(res.text)

if __name__ == '__main__':
    main('https://stock.hexun.com/2019-12-16/199691973.html?from=rss')

打印的结果html中文乱码:
image.png

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

res = requests.get(url,headers=headers)
print(res.encoding)
print(res.headers)
#ISO-8859-1
#{'Server': 'nginx', 'Date': 'Tue, 21 Apr 2020 02:14:57 GMT', 'Content-Type': 'text/html', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'Expires': 'Tue, 21 Apr 2020 02:15:57 GMT', 'Cache-Control': 'max-age=60', 'X-UA-Compatible': 'IE=EmulateIE7', 'Content-Encoding': 'gzip'}

如上打印的encoding和headers结果都验证了上面的说法。
修改该问题有2中方法:

  1. 强制修改res.encoding的编码类型
    requests的返回结果对象里有个apparent_encoding函数, apparent_encoding通过调用chardet.detect()来识别文本编码. 但是需要注意的是,这有些消耗计算资源.
    因为在源码中是用的content进行识别
@property
def apparent_encoding(self):
    """使用chardet来计算编码"""
    return chardet.detect(self.content)['encoding']

chardet.detect可用于识别文本编码

>>> data = '离离原上草,一岁一枯荣'.encode('gbk')
>>> chardet.detect(data)
{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}

>>> data = '离离原上草,一岁一枯荣'.encode('utf-8')
>>> chardet.detect(data)
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}

confidence表示识别概率
  1. 采用res.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的事情.

所以直接将bytes的content传给BeautifulSoup进行解析便不用考虑编码问题,因为BeautifulSoup接收bytes或str的网页都会返回str的数据用于解析

soup = BeautifulSoup(text,'lxml')

引用文章:http://xiaorui.cc/archives/2786

posted @ 2020-04-21 19:25  古月月月胡  阅读(1940)  评论(0编辑  收藏  举报