编码
这篇博文的主题是,编码问题,老生常谈的问题了对吧?从我这一套的文章来看,前面已经提到好多次编码问题了,的确这个确实很重要,这可是难道了很多能人异士的,当你以为你学懂了,在研究爬虫时你发现你错了,还是没搞懂,爬虫研究完后,你以为你懂了,url编码又把你打回原形,然后你以为你真的懂了,你学到socket的时候,你发现,你还是没有真的理解,真实气人啊,对吧
与编码问题相关的都在这两篇博文中:
洗礼灵魂,修炼python(3)--从一个简单的print代码揭露编码问题,运行原理和语法习惯
洗礼灵魂,修炼python(57)--爬虫篇—知识补充—编码之对比不同python版本获取的数据
(注明:这是我之前的博文,前期对编码问题,可能理解的不够,我个人之前对编码问题也很困惑,所以如果有错,大家请忽略。结合本篇来看,相信你会真的理解编码问题)
那么本篇博文就深度的剖析一下这个最烦的问题
python2与python3不同的编码解码
首先我们都知道python2和python3有不同的编码规格。
在python3中只有两种数据存储类型,str,bytes
str:存储的就是Unicode,即全球都能认的编码类型
bytes:存储的就是十六进制
而由str转成bytes就叫编码
bytes转成str就叫解码
因为计算机能认识的字符集只能是二进制,那么bytes是最接近二进制的字符集,十六进制再转成二进制就方便很多了
在python2里
有两种类型:Unicode和str(bytes)
str和unicode都是类basestring的子类,str其实是字节串,它是unicode经过编码后的字节组成的序列。而unicode是一个字符串,str是unicode这个字符串经过编码(utf8,gbk等)后的字节组成的序列。unicode才是真正意义上的字符串,对字节串str使用正确的字符编码规则进行解码后获得
那么python2中又是什么鬼呢,不急,接着看
这情况看到了吧?其实我相信,如果你在爬虫方面研究的比较多,并且你是使用python2来爬的话,你早就遇到这个情况了,这是为什么呢?
在python2中,str 存储的就是bytes,首先在Python 2中,print是一个语句(而在Python 3中变成了函数),当直接print时字符串会把数据从IO中读取出来成为有编码规则的数据,所以print打印的正好是我们期待的,而放进容器类型(列表,元组,字典,集合)里时,就会看到存储时的样子。
所以在python2里,str=bytes,换句话python里只存在一种数据存储类型:bytes。Python 2 悄悄掩盖掉了 byte 到 unicode 的转换,让程序在处理 ASCII 的时候更加简单
py2编码的最大特点是Python 2 将会自动的将bytes数据解码成 unicode 字符串,所以在python2中字节数据可以和unicode型进行拼接:
也就是并没有把str和bytes严格的分开,在这一点上,python3就做得更好了,也是正确的,本来就是的,明明就是str,你存储就成了字节类型,这是搞事情啊,请看:
这段代码在python3中是成立的对吧(关于print在python2和3中的不同写法就不再赘述了),但是报错了,提示的是str()允许的参数必须是1个,而却给了两个,这里在上面我们已经知道str和bytes编码解码的功效是一样的了,那么也就是说,当转成字节类型时,python2已经默认为我们设定了一套编码规则,不需要我们手动添加编码规则了。所以这也是为什么使用python2时,时不时会报编码错误了,它已经给我们编码/解码好了,用的什么编码规则我们还要自己去拆解,不然就会出现一堆的错误,很混乱对吧?
还是那句话,在编码问题上,python3很完善,python2一堆小毛病
在python3里
有两种类型,Unicode和byte
现在你从普通文本转换成 “str” 类型后存储的是一个 unicode, “bytes” 类型存储的是 byte 串。你也可以通过一个 b 前缀来制造 byte 串。
Python 3最重要的新特性大概要算是对文本和二进制数据作了更为清晰的区分。文本总是Unicode,由str数据类型表示,二进制数据则由bytes类型表示。Python 3不会以任意隐式的方式混用str和bytes,正是这使得两者的区分特别清晰。你不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也不能将字符串传入参数为字节包的函数(反之亦然)。
例如,bytes('你好','utf8')就可以把str为‘你好’编码为十六进制。这里的utf8可以理解为编码规则或者叫编码类型,它并不是固定的,也可以是其他的,比如gbk之类,像这种utf8和gbk,以及*的big5,韩国,日本的各种字符类型,这些除了utf8以外,各个国家都有自己各自的编码类型去编码,编出来的都不是不同的类型,虽然最后都是十六进制的bytes。
请看:
像这种’\xe4\xa0‘啥的就是bytes类型,它也是十六进制的数据
那么有朋友想到了,字符串类型的数据不是自带编码吗?请看:
发现b1 和b4是一样的,那么不用说,使用其他编码规则来编码也是一样的
上面是编码,那么解码呢?这里我就同时使用decode和str转码的方法了:
也是一样的对吧?那么有朋友突发奇想了,解码时解码成其他的可以不,试试呢:
呀,啥字,看不懂啊,其实这已经乱码了,因为编码和解码不统一,这里只是刚好给解码出来了,大部分情况是解码不出来并且会报错的,比如我这解码成big5试试:
看,报错了吧?
所以要记住,编码和解码必须是同一种编码规则
Python 3 中对 Unicode 支持的最大变化就是将会没有对 byte 字节串的自动解码。如果你想要用一个 byte 字节串和一个 unicode 相链接的话,你将会得到一个错误,不管你包含的内容是什么
url编码
这个问题,在前面的某一篇博文里我已经解析过了,为了统一,放在一起了
首先,Http协议中参数的传输是"key=value"这种键值对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割。如"?key1=value1&key2=value2",这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出键和值并进行处理。
然后,url只能使用 ASCII 字符集来通过因特网进行发送,也就是说url允许的只能是英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。那么如果url中有汉字,就必须编码成为允许的字符后方可使用。
但是有个问题是,标准的国际组织并没有规定具体的编码方法,而是交给应用程序(浏览器)根据自己的一套编码方式进行编码,有点乱,而每个浏览器对同样的字符解码都是不太一样的。但这里只是指网站子文件的字符编码混乱,比如前面用的百度搜索,编码还是一样的:
火狐浏览器:
https://www.baidu.com/s?wd=%E8%83%A1%E6%AD%8C&rsv_spt=1&rsv_iqid=0xa88a849a000297f1&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&rqlang=cn&tn=monline_3_dg&rsv_enter=1&oq=a&inputT=1164&rsv_t=44f3deAD5ZhHUuZS8qctF8DYdHQl0Jc0fIHprlrxVQPAKhGaI7WQzU0%2BDYkOZ7iFCV9H&rsv_pq=bc5bcce5000040e2&rsv_sug3=10&rsv_sug1=9&rsv_sug7=100&bs=a
谷歌浏览器:
https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E8%83%A1%E6%AD%8C&oq=%25E8%2583%25A1%25E6%25AD%258C&rsv_pq=bc8022d200026160&rsv_t=cf02s%2BKYldDmROy0mQpW7gMikG0rFAkF5WE0KydGdjM1v4PH9wW87XYxuCA&rqlang=cn&rsv_enter=1&rsv_sug3=1&rsv_sug1=1&rsv_sug7=100&rsv_sug2=0&inputT=12&rsv_sug4=439
IE浏览器:
https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=%E8%83%A1%E6%AD%8C&rsv_pq=b3b7634b00004749&rsv_t=fa91EE041mGXpmDfMhWtd6QSrm%2F24pXydfxJc%2BPc5W59OuaHKoicHE4Ngwo&rqlang=cn&rsv_enter=1&rsv_sug3=4
(搜索关键词我已经标注出来)
url的编码样式是使用【%】加上代表十六进制为一个字节形式的两位字符—【0-9和A-F】来(比如%EC),详细规则:
- 对于ASCII字符,字母a在ASCII码中对应的字节是0x97,那么Url编码之后得到的就是%97,字母abc, url编码后得到的就是%97%98%99
- 对于非ASCII字符,RFC文档建议使用utf-8对其进行编码得到相应的字节,然后对每个字节执行百分号编码。如前面搜索的"胡歌"使用UTF-8编码规则字符集得到的字节为0xE8 0x83 0xA1 0xE6 0xAD 0x8C,经过Url编码之后得到%E8%83%A1%E6%AD%8C
所以url编码通常也被称为百分号编码(percent-encoding)。
说白了就是ASCII码表里面有的字符,就直接按对应字节,然后把0x改为%。如果ASCII码表里没有的,就用unicode万国码里的utf8编码规则将字符转为字节,然后把0x改为%就行。