最近小白写python爬虫,又遇到编码问题,真的难受!!!之前遇到编码问题照着网上最后也能解决,不过每次写完感觉对编码转换还不是很明白。为此想起了自己之前买的一本《流畅的Python》,里面就有一章是讲述文本和字节序列,看了一小节顿时茅塞顿开。因此重新梳理了一遍自己对编码的理解,如有错误,请告诉我,谢谢~!本文部分内容来自这本书。
(即iso8859_1) 一种重要的编码,是其他编码的基础。
环境是windows 10,python3.6.1
>>> s = 'Montr�al'
>>> s.encode('utf8') 一
b'Montr\xef\xbf\xbdal'
>>> s.encode('utf-16')
b'\xff\xfeM\x00o\x00n\x00t\x00r\x00\xfd\xffa\x00l\x00'
>>> s.encode('cp1252') 二
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "D:\Users\Rosefinch\AppData\Local\Programs\Python\Python36-32\lib\encodings\cp125
2.py", line 12, in encode
return codecs.charmap_encode(input,errors,encoding_table)
UnicodeEncodeError: 'charmap' codec can't encode character '\ufffd' in position 5: chara
cter maps to <undefined>
>>> s.encode('iso8859_1') 三
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'latin-1' codec can't encode character '\ufffd' in position 5: ordin
al not in range(256)
>>> s.encode('cp437') 四
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "D:\Users\Rosefinch\AppData\Local\Programs\Python\Python36-32\lib\encodings\cp437
.py", line 12, in encode
return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character '\ufffd' in position 5: chara
cter maps to <undefined>
>>> s.encode('cp437',errors="ignore") 五
b'Montral'
>>> s.encode('cp437',errors="replace") 六
b'Montr?al'
>>> s.encode('cp437',errors="xmlcharrefreplace") 七
b'Montr�al'
>>> b = b'Montr\xe9al' 一
>>> b.decode('cp1252') 二
'Montréal'
>>> b.decode('iso8859_1') 三
'Montréal'
>>> b.decode('iso8859_7') 四
'Montrιal'
>>> b.decode('utf_8') 五
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continua
tion byte
>>> b.decode('utf_8',errors='replace') 六
'Montr�al'
继续看
import sys,io
print(sys.stdin.encoding) #标准输入的编码方式
print(sys.stdout.encoding) #标准输出的编码方式
s = 'Montr�al' #字符串
print(s.encode('utf8').decode('utf8')) #以utf8编码,再以utf8编码解码
#在sublime编辑器运行
cp936
Traceback (most recent call last):
cp936
File "D:\Users\Rosefinch\Desktop\test2.py", line 8, in <module>
[Decode error - output not utf-8]
UnicodeEncodeError: 'gbk' codec can't encode character '\ufffd' in position 5: illegal multibyte sequence
#在cmd命令符下运行
utf-8
utf-8
Montr�al
由此可知,python3默认编码方式是utf-8,第三方编辑器默认编码是cp936(以windows 10操作系统默认编码是GBK为例);根据开始以什么方法编码最后也要以什么方式解码,不然你会遇到麻烦。
回到上面sublime编辑器运行出现错误,原因是sublime编辑器没有指定输出编码方式,所以它会使用区域设置(windows操作系统)中的默认编码GBK方式输出。因为你以utf8方式解码,还得以utf8编码方式输出,而不是window操作系统的默认编码GBK,所以通过更改标准输出编码方式改变。
#在sublime代码添加
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #更改标准输出编码
#在sublime运行
import sys,io
print(sys.stdin.encoding)
print(sys.stdout.encoding)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8')
s = 'Montr�al' #字符串
print(s.encode('utf8').decode('utf8')) #以utf8编码,再以utf8解码
#运行
cp936
cp936
utf8
Montr锟絘l
#在sublime运行
import sys,io
print(sys.stdin.encoding)
print(sys.stdout.encoding)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #更改标准输出的编码方式
print(sys.stdout.encoding)
s = 'Montr�al' #字符串
print(s.encode('utf8').decode('GBK')) #以utf8编码,再以GBK解码
#运行
cp936
cp936
utf8
Montr閿熺禈l
上面以GBK解码,前面不是说也要以相应编码方式输出吗?为什么utf-8也可以,在最前已经说过“utf_?”编码能处理任何字符串。
最后在《流畅的python》引入一个处理文本例子。
一个平台上的编码问题(如果在你的机器上运行,它可能会发生,也可能不会)
>>> fp = open('test.txt','w',encoding='utf-8').write('Montréal')
>>> fp
8
>>> open('test.txt').read()
'Montr茅al'
问题是:写入文件时指定了utf-8编码,但是读取文件时没有这么做,因此Python假定要使用系统默认的编码
cp936,于是文件的最后一个字节解码成了字符“茅" ,而不是 ”é“。
我是在windows10中运行上面代码的。在新版GNU/Linux或Mac OS X中运行同样的语句不会出问题,因为这几个操作系统的默认编码是UTF-8,让人误以为一切正常。如果打开文件是为了写入,但是没有指定编码参数,会使用区域设置中的默认编码,而且使用那个编码也能正确读取文件。但是,如果脚本生成文件,而字节的内容取决于平台或同一平台中的区域设置,那么就可能导致兼容设置。
因此,关于编码默认值哦最佳建议是:别依赖默认值。
仔细分析在windows中运行上面的代码,找出并修正问题。
>>> fp = open('test.txt','w',encoding='utf-8')
>>> fp #默认情况下,open函数采用文本模式,返回一个TextIoWrapper对象。
<_io.TextIOWrapper name='test.txt' mode='w' encoding='utf-8'>
>>> fp.write('Montréal')
8 #在TextIoWrapper对象上调用write方法返回写入的Unicode字符数。
>>> fp.close()
>>> import os
>>> os.stat('test.txt').st_size
9 #os.stat报告文件中有9个字节,UTF-8编码的“ é “占两个字节,\xc3\xa9。
>>> fp2 = open('test.txt')
>>> fp2 #打开文本文件时没有显式指定编码,返回一个TextIoWrapper对象,编码是区域设置中的默认值。
<_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'>
>>> fp2.encoding #TextIoWrapper对象有个encoding属性;查看它,发现这里的编码是cp936。
'cp936'
>>> fp2.read()
'Montr茅al' #在cp936编码中,0xc3字节是“ é “, 0xa9字节是版权符号。
>>> fp3 = open('test.txt',encoding='utf_8') #使用正确的编码打开那个文件
>>> fp3
<_io.TextIOWrapper name='test.txt' mode='r' encoding='utf_8'>
>>> fp3.read()
'Montréal' #结果符号预期:得到的是8个Unicode字符 'Montréal' 。
>>> fp4 = open('test.txt','rb') # 'rb' 标志指明在二进制模式中读取文件。
>>> fp4
<_io.BufferedReader name='test.txt'> # 返回的是BufferedReader对象,而不是TextIoWrapper对象。
>>> fp4.read()
b'Montr\xc3\xa9al' #读取返回的字节序列,结果与预期相符。
除非想判断编码,否则不要在二进制模式中打开文本文件;即便如此,也应该使用Chardet,而不是重新发明轮子。常规代码只应该使用二进制模式打开二进制文件,如光栅图像。