如果经常使用Python编程或者是其他语言编程,或者在前面的文章中已经多次使用Python练习网络爬虫技术,就不可避免地会遇到中文乱码的问题。中文乱码问题经常难以理解,或者治标不治本,本文就是来解决这一难题的。
下面主要讲解:什么是字符编码、Python的字符编码是什么、如何解决python中文乱码问题等。有基础的朋友可以通过章节导航选择性的阅读。
1 什么是字符编码
如果是已经学习Python爬虫或者其他技术编程一段时间了,就会发现字符编码是件让人头疼的事。下面先看下经常性报错提示:
ValueError:Expected a bytes object, not a unicode object
UnicodeDecodeError:'cp950'codec can't decode byte 0x96 in position 10:illegal multibyte sequence
或者是爬过程中的乱码:
还有这样的:
当我们遇到这些内容时,可能第一时间就会去百度或者谷歌搜索解决办法,运气好的,很快就能找到解决方法。运气差的,往往看到是各种文章互相搬,目前内容搬运太狂了,有关问题的解决方法千钧一律,重点还不能有效解决问题。
哪怕是解决了这个问题,但往往是只知其表不知其里,不知道为什么这个方法能解决这个问题。下面就会详细说下字符编码错误是如何发生的。
1.1 机器编码、ASCII 和 GB2312
计算机机器语言
(为了方便看,下面二进制都采用了中间空格)
1、计算机最初设计是8比特(bit)作为一个字节(byte)。
2、计算机是二进制,由0和1组成,一个字节就是0000 0000(表示一个字符:0)。
3、也就说从0000 0000到1111 1111总共就有256个字符,例如0100 0001表示字符:A。
下面我们看图加深理解:
ASCII
在最初发明计算机的时候美国人就同时定制了属于他们自己的编码表,通过不同的二进制与英文字符或者其他特殊字符,这种编码就称为ASCII码,ASCII码总共有128个字符编码。例如上面的数字0就是0000 0000,大写A就是0100 0001,小写b就是0110 0010。
GB2312
那他们有自己的编码了,咱们也要有啊,但是咱们文字博大精深常用汉字就有六千多个中文。上面咱也说过8位数的二进制只有256种组合,对于英文字符128来说绰绰有余,但是对我们来说就远远不够。那怎么办呢?
发展线1:一个8位数字节表示一个字符,有256种组合,那咱们加大点量,双字节编码(前面一个高字节+后面一个低字节就能造出几千个简体中文了)这就是咱们中国的 GB2312简体中文编码 方式。
发展线2:很快就发现文字实在太多了,很多偏僻文字打不出来,那就只能继续加码了,这就是 GBK 了,涵盖了全部GB2312的内容。
发展线3:常用文字、偏僻字都有了,但是后来少数民族也是咱中国的一份子啊,随着网络的普及,他们也要联网进入大家庭,这时编码只能继续拓展啊,GBK就变成了 GB18030。
多语言
当然除了咱们中国以外,其他国家都定制了各自的编码方式,例如日文:Shift_JIS,韩语:ks_c_5601-1987,俄语:windows-1251等。
问题来了,每个国家都有自己的编码方式,当在多语言文本中就有可能出现乱码。这又怎么办呢?
1.2 通用的编码方式
Unicode编码
Unicode有个很好听的名字:统一码、万国码、单一码...
Unicode的创造就是为了解决上面不同国家/地区跨语言文本转换和处理的难题。
Unicode给不同语言中的每个字符设定了统一并且唯一的二进制编码,目前大概有100万多个。
Unicode和GB2312有点像,都是采用固定的双字节模式,对于ASCII来说就是在其前面加0,例如字符A,ASCII码标识:0100 0001,那Unicode码标识:0000 0000 0100 0001,大家看出有什么规律了吧。
Unicode很厉害,这就把世界难题解决了,但是人总是在进步的,很快就发现不是任何时候都需要双字节来表示字符,例如上面说到的大写字符A,这相比ASCII来说就是浪费存储空间。那有没有一种更灵活的统一编码方式?
UTF-8编码
答案是有的:UTF-8,最大特点是长度可变,可使用1-4字节表示一个字符。
相比ASCII,英文被UTF-8编码为1个字节。
相比GB2312,中文被UTF-8编码为3个字节。
下面看下ASCII、Unicode、UTF-8的对比图:
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 00000000 01000001 | 01000001 |
中 | 01001110 00101101 | 11100100 10111000 10101101 |
然后再整理下上面的编码关系:
2 Python的字符编码
上面讲解了各种编码之间的关系,咱们看下python的编码方式,下面都是以Pyhon3来讲解。在python3中字符串有str和bytes两种类型。然后str字符串使用的是Unicode编码。bytes字符串使用将Unicode转成某种类型的编码。
python3种默认是unicode编码,也正是因此可以让程序随意转化成某种类型编码,就会容易遇到编码问题。
下面看个简单例子:
str1 = '学习Python'
print(str1)
print(type(str1))
运行后的结果:
从上面可以看出python3字符串默认使用就是Unicode编码。
2.1 encode - Unicode编码转其他编码
encode()方法是将unicode编码转换成其他编码的字符串,例如下面转成UTF-8:
str1 = '学习Python'
str2_utf8 = str1.encode("utf-8")
print(str2_utf8)
print(type(str2_utf8))
运行后的结果:
运行后中文都被分成3个字节,由于找不到对应的ASCII编码就只能转成十六进制显示。
2.2 decode - 将其他编码解码为Unicode编码
decode()方法是将其他编码的字符串转换成Unicode编码,我们接着上面的示例,将encode的字符串转码回Unicode:
str1 = '学习Python'
str2_utf8 = str1.encode("utf-8")
print(str2_utf8)
print(type(str2_utf8))
str1_unicode = str2_utf8.decode('utf-8')
print(str1_unicode)
print(type(str1_unicode))
运行结果:
通过最后打印的内容,咱们看到中文又被转换回来了。
在实际decode解码过程中还有第二个参数报错处理,decode([encoding], [errors='strict']),默认是严格模式,报错直接中止,还有ignore可以忽略报错不影响主程序运行。
2.3 多次转码
总有人会有特别想法,例如Unicode还可以再decode吗,肯定不行。
print('学习Python'.decode('utf-8')) #直接报错 'str' object has no attribute 'decode'
那有人想知道我要想把utf-8转成其他非Unicode编码怎么办呢?
首先我们先decode成Unicode,再encode成其他编码,例如utf-8转GBK:
# 先将字符串转成utf-8编码
str1 = '学习Python'.encode('utf-8')
# 将utf-8编码转成其他编码
str2 = str1.decode('utf-8').encode('GBK')
print(str2)
print(type(str2))
运行后结果:
到此我们就把转码搞定了。
3 解决中文乱码
通过上面的学习,我们基本掌握了编码方式,转码技术,那么对后面出现的问题就很容易搞定了。一般在实际业务中,会有下面几种常见的乱码情况:
3.1 情况一:使用Requests请求网站获得内容后中文乱码
下面这个地址是仅供参考学习用:示例地址
然后我们按照正常的代码编写如下:
import requests
from bs4 import BeautifulSoup
# 地址仅供学习参考使用
url = 'https://www.eol.cn/html/en/cetwords/cet4.shtml'
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
print(soup)
运行后的结果:部分截图
对比下上面的源码截图,会发现我们直接获取回来的中文内容全是乱码了,这不是我们想要的结果。那问题出在哪里呢?
【分析】
我们先看下代码中的r到底是什么编码方式,通过在r = requests.get(url)下方增加代码:
print(r.encoding)
运行后的结果:
很明显r(网页)的编码是ISO-8859-1模式,然后我们再看下源码的编码模式是:utf-8
【解决方法】
通过分析,我们知道当前页面的编码是ISO-8859-1,但是网页实际需要的是utf-8,那我们就使用上面学习的方法转码即可。
import requests
from bs4 import BeautifulSoup
# 地址仅供学习参考使用
url = 'https://www.eol.cn/html/en/cetwords/cet4.shtml'
r = requests.get(url)
print(r.encoding)
r.encoding = 'utf-8'
print(r.encoding)
soup = BeautifulSoup(r.text, 'lxml')
print(soup)
运行结果 :
通过结果看到中文已经显示正常了。
3.2 情况二:encode或decode某个含有非法字符的字符串时报错
当我们在爬取某些网站时,无论是无意还是有意反爬,有可能该网页含有非法字符,这时我们使用str.decode('utf-8')就会得到下面的异常:
UnicodeDecodeError:‘utf-8’codec can't decode byte in position :illegal multibyte sequence
这就是咱们解码或转码的时候字符串含有非法字符导致的问题。
【解决方法】
在文中2.2也有提过 decode([encoding], [errors='strict']) 方法中的第二参数errors 默认是strict严格模式,报错立刻终止,就会导致主程序无法正常运行下去。errors有3个值,分别是:
①errors=ignore:直接忽略错误,强制转换,非法字符被直接移除。
②errors=replace:使用符号代替非法字符,会被转换成合适的ASCII字符。
③errors=xmlcharrefreplace:使用XML字符引用代替非法字符。
3.3 乱码情况三:写入或者读取文件时,文件字符串非正常的中文
在使用python读取和写入文件的时候建议一定要注明编码方式。往往导致报错都是因为编码的问题,下面我们看下案例。
3.3.1 txt文件和csv文件读取写入
我们先准备2个txt文件:
第一个命名为test_ansi.txt,创建记事本,内容为:“abc中文”,直接默认保存。
第二个命名为test_utf8.txt,直接从上面test_ansi.txt文件中点击另存为,保存时选择UTF-8编码。
然后我们尝试读取这两个文件内容:
str_ansi = open('test_ansi.txt', 'r').read()
print(str_ansi)
str_utf8 = open('test_utf8.txt', 'r').read()
print(str_utf8)
运行结果:
从结果我们看到第一个是能够正常运行的,而第二个报错:'gbk' codec can't decode byte 0xad in position 5: illegal multibyte sequence。
这是由于我们的windows系统默认使用的是简体中文版本,所以字符编码就是默认的GBK(也就是这里的ANSI),这就导致了第二个UTF-8编码读取失败。
【解决方法】
解决方法也很简单,在读取的时候添加encoding=‘相应的编码格式’,例如上面我们可以改成:
str_ansi = open('test_ansi.txt', 'r').read()
print(str_ansi)
str_utf8 = open('test_utf8.txt', 'r', encoding='utf-8').read()
print(str_utf8)
运行结果:
我们看到添加编码格式后两个读取都正常了。
写入保存报错一般如下即可:
add_content = '测试hello'
with open('test_utf8.txt', 'a+', encoding='utf-8') as f:
f.write(add_content)
f.close()
csv格式也是差不多的解决方法,这里就不再赘叙了。
3.3.2 json文件读取写入
对于json文件,当我们把带有中文的字符串数据保存到json文件时,默认会以Unicode编码处理。
import json
title = '测试json中文'
with open('test.json', 'a+', encoding='UTF-8') as f:
json.dump([title], f)
f.close()
运行结果:
会在本地生成一个test.json文件,然后我们点击test.json看下:
【解决方法】
这是因为默认使用了ASCII编码,而我们中文不在这种编码解析中,所以就会被转成16进制。那我们把默认的编码格式禁止掉,换成我们指定的不就好了,就需要用到 ensure_ascii=false 参数了。如下修改:
import json
title = '测试json中文'
with open('test.json', 'a+', encoding='UTF-8') as f:
json.dump([title], f, ensure_ascii=False)
f.close()
运行结果:
对比第一次运行的结果,我们第二次成功把中文写入json文件里面。
4 写在最后
希望大家能够通过上面的学习快速掌握并理解中文乱码的由来以及解决方法,不再像以前一样一知半解,可以通过理解怎么回事,触类旁通地解决编码问题。
以上就是作者给大家带来有关于python中文乱码的内容,常见案例目前就积累了3个,如果大家还有其他方面的中文乱码问题,欢迎下方留言,作者亲自帮你解决并把案例写进文章中供大家学习。