放假的电话

Python unicode, str以及字符编码

在网页上使用Python抓取信息时,特别是中文的网页,常常遇到编码问题,导致字符串不能正常print或者保存。虽然每次遇到问题都可以通过迅速google解决一下,但我觉得有必要在此梳理一下,希望下次不会再遇到同样的麻烦。

我使用的是Python 2.7,在3.x里文件的默认编码方式似乎已经改成了utf-8

字符集

要讲这个问题,首先来说一说字符集。所谓的字符集,简单来说就是一个系统能表示的所有字符,或者说,是“词汇量”。比如,大家所熟知的ASCII字符集,每个字符都使用1个字节来表示,一共包含256个字符。对于普通的英文输入,这么大的字符集已经足够,包含了一般会用到的字母以及符号。但是,对于其他语言,比如说中文,ASCII是完全不能胜任的,因为ASCII完全没有定义中文字符。基于这个原因,世界上出现了很多其他字符集,比如中文的GB2312, 日文的JIS X 0208, 以及不能不提的unicode。

Unicode

Unicode是一个非常大的字符集,通过4个字节来保存一个字符,理论上可以包含2^32个不同的字符。它至今仍在发展之中,不断有新的字符被加入Unicode字符集。

字符编码

讲完字符集,来讲讲字符编码。众所周知,在计算机里,所有的东西最终都以’01’比特来存储。假设现在我们有一个字符—“我”,这个“我”字应该在计算机里用什么样的”01”序列来存储呢?决定这个的,就是字符编码!同样的字符,在不同的字符编码方式下,有可能变成不同的’01’序列。同理,同样的’01’序列,如果用不同的字符编码来解码,也可能会得到不同的东西。例如,对于unicode字符集,常见的有utf-8, utf-16, utf-32三种编码方式,同样的字符“你好”,在三种不同的编码方式下,可能会变成完全不相同的几个’01’序列。

1
2
3
4
s = u'你好'
print repr(s.encode('utf-8')) # 输出 \xe4\xbd\xa0\xe5\xa5\xbd
print repr(s.encode('utf-16')) # 输出 \xff\xfe`O}Y
print repr(s.encode('utf-32')) # 输出 \xff\xfe\x00\x00`O\x00\x00}Y\x00\x00

encode & decode

在Python,常见的跟字符串相关的对象有两种。一是以u开头的unicode,例如u’你好’,另一种是str对象。在我们谈论encode和decode的时候,特别要注意,encode只能使用在unicode对象上,表示对其进行编码,例如:

1
2
s = u'你好'
print s.encode('utf-8')

decode只能使用在str对象上,以此获得一个unicode对象。注意,在调用decode时,必须保证使用的是原来的encode的编码,否则会报错。例如:

1
2
3
s = u'你好' # unicode 你好
t = s.encode('utf-16') # t是一个str对象,通过对s进行utf-16的编码获得
print t.decode('utf-8') # Error!无法进行decode,因为指定的编码方式错误

py文件的编码

默认的.py文件使用的是ACSII编码,因此如果你在文件中使用了中文,一般都会报错。正确的处理方法是在文件开头指定编码方式,例如添加:

1
# -*- coding: utf-8 -*-

这里,其实起作用的只有#,coding: utf-8,所以,写成这样也是没有问题的:

1
# coding: utf-8

实践

说了这么多,来看2个例子,比较实际。

.py文件的编码

1
2
3
4
# -*- coding: utf-8 -*-
s = u'你好'
print s.encode('utf-8') # 输出你好
print s.encode('gbk') # 没有输出。print试图使用utf-8来打印s.encode('gbk'),因为编码方式不相同,导致无法正确打印

如果改成这样就没有问题

1
2
3
# -*- coding: gbk -*-
s = u'你好'
print s.encode('gbk')

文件读写

读写文件的时候,要注意,文件保存的是’01’序列,也就是经过编码后的字符。由于编码方式不同,如果读取时没有正确解码,就会出现问题。举一个例子:
example.txt使用的是utf-16 BE(16bit big-endian)编码,保存的是汉字“你好”

1
2
3
4
5
6
# -*- coding: utf-8 -*-
with open('example.txt','r') as f:
for line in f.readlines():
print line.decode("utf-16 BE").encode('utf-8') # 输出“你好”
print type(line.decode('utf-16 BE')) # 输出 type unicode
print line.decode('utf-16').encode('utf-8') # 输出恏絙,也就是所谓的“乱码”

在上面的例子里,通过正确的解码并使用指定的utf-8编码,可以正确读取文件中的内容。一旦使用错误的编码方式,就会出现乱码。