python字符串編碼的問題在爬蟲過程中無論是輸出到文件中還是輸出到DB數(shù)據(jù)庫(kù)中感覺是始終繞不過去的一道坎浸踩,所以趁這次碰到的問題 來做一個(gè)學(xué)習(xí)記錄
什么是Unicode
Unicode
是計(jì)算機(jī)可以支持這個(gè)星球上多種語言的密碼武器。在Unicode
之前,用的都是ASCII
滩援。ASCII碼非常簡(jiǎn)單炉擅,每個(gè)英文字符都是以7位二進(jìn)制數(shù)的方式存貯在計(jì)算機(jī)內(nèi),其范圍是32~126清女。當(dāng)用戶在文件中鍵入一個(gè)大寫字符A時(shí)钱烟,計(jì)算機(jī)會(huì)把A的ASCII
碼值65
寫入磁盤,然后當(dāng)計(jì)算機(jī)讀取該文件時(shí)嫡丙,它首先會(huì)把65轉(zhuǎn)化成字符A然后顯示在屏幕上拴袭。
ASCII編碼的文件小巧易讀。一個(gè)程序只需簡(jiǎn)單地把文件的每個(gè)字節(jié)讀出來曙博,把對(duì)應(yīng)的數(shù)字轉(zhuǎn)換成字符顯示出來就可以了拥刻。但是ASCII
編碼也有其局限性,就算后來擴(kuò)展到用8位二進(jìn)制的方式來存貯父泳,這個(gè)對(duì)于需要成千上萬的字符的非歐洲語系的語言來說仍然太少.
例如:處理中文顯然一個(gè)字節(jié)是不夠的般哼,至少需要兩個(gè)字節(jié),而且還不能和ASCII
編碼沖突,這樣就使中國(guó)制定了GB2312
編碼惠窄,全世界有上百種語言蒸眠,日本把日文編到Shift_JIS
里,韓國(guó)把韓文編到Euc-kr
里杆融,各國(guó)有各國(guó)的標(biāo)準(zhǔn)楞卡,就會(huì)不可避免地出現(xiàn)沖突,結(jié)果就是擒贸,在多語言混合的文本中臀晃,顯示出來會(huì)有亂碼
。
Unicode
這個(gè)時(shí)候就應(yīng)運(yùn)而生介劫,通過使用一個(gè)或多個(gè)字節(jié)來表示一個(gè)字符的方法來突破ASCII
的限制徽惋。在這樣的機(jī)制下,Unicode
可以表示超過90000個(gè)字符座韵。
新的問題又出現(xiàn)了:如果統(tǒng)一成Unicode
編碼险绘,亂碼問題從此消失了踢京。但是,如果你寫的文本基本上全部是英文的話宦棺,用Unicode
編碼比ASCII
編碼需要多一倍的存儲(chǔ)空間瓣距,在存儲(chǔ)和傳輸上就十分不劃算。
所以代咸,本著節(jié)約的精神蹈丸,又出現(xiàn)了把Unicode編碼轉(zhuǎn)化為“可變長(zhǎng)編碼”
的UTF-8
編碼。UTF-8
編碼把一個(gè)Unicode
字符根據(jù)不同的數(shù)字大小編碼成1-6個(gè)字節(jié)呐芥,常用的英文字母被編碼成1個(gè)字節(jié)逻杖,漢字通常是3個(gè)字節(jié),只有很生僻的字符才會(huì)被編碼成4-6個(gè)字節(jié)思瘟。如果你要傳輸?shù)奈谋景罅坑⑽淖址┌伲?code>UTF-8編碼就能節(jié)省空間。
總結(jié)一下現(xiàn)在計(jì)算機(jī)系統(tǒng)通用的字符編碼工作方式:
在計(jì)算機(jī)內(nèi)存中滨攻,統(tǒng)一使用Unicode編碼够话,當(dāng)需要保存到硬盤或者需要傳輸?shù)臅r(shí)候,就轉(zhuǎn)換為UTF-8編碼光绕。
在 Python 中 Unicode
被視為是一種中間碼女嘲,如果要在不同的編碼間進(jìn)行轉(zhuǎn)化,通常是先將字符串解碼(decode
)成 Unicode
編碼奇钞,再?gòu)?Unicode 編碼(encode
)成另一種編碼:
-
decode
: 的作用是將其他編碼的字符串轉(zhuǎn)換成Unicode
編碼澡为,例如:name.decode(“GB2312”)
,表示將GB2312
編碼的字符串 name 轉(zhuǎn)換成Unicode
編碼 -
encode
: 的作用是將Unicode
編碼轉(zhuǎn)換成其他編碼的字符串例如景埃,例如:name.encode(”GB2312“)
媒至,表示Unicode
編碼的字符串 name 轉(zhuǎn)換成GB2312
編碼
我們會(huì)在很多 Python 的源碼文件的頭部看到如下的聲明:
# coding:utf-8
這表示聲明源代碼中的文本編碼為UTF-8
,也就是告訴 Python 解釋器將文件中的文本視為 UTF-8
編碼的字符串谷徙,因此聲明的編碼應(yīng)該與文件的編碼保持一致拒啰。在代碼中我們通常會(huì)處理一些其他來源的文本,比如網(wǎng)絡(luò)完慧,它們的編碼不一定也是 UTF-8
的谋旦,因此就要進(jìn)行編碼轉(zhuǎn)換。
Python 試圖在字節(jié)串和字符串之間以不為人所察覺的方式進(jìn)行轉(zhuǎn)化屈尼。
在不同的轉(zhuǎn)換中册着,在條件允許的情況下,Python 會(huì)試圖在字節(jié)串
和 unicode
字符串直接進(jìn)行轉(zhuǎn)換脾歧。例如將字節(jié)串
和 unicode
字節(jié)串連接到一起時(shí)甲捏。但是不使用 encoding 就在不同類型之間進(jìn)行轉(zhuǎn)換是沒有意義的。所以 Python 依賴一個(gè) 默認(rèn)編碼鞭执,該編碼由 sys.setdefaultencoding()
指定司顿。在大多數(shù)平臺(tái)上芒粹,默認(rèn)的是 ASCII
編碼。但對(duì)于所有轉(zhuǎn)換大溜,使用這種編碼幾乎都是錯(cuò)誤的化漆。如果不手動(dòng)指定編碼就調(diào)用 str()
或 unicode()
,或是函數(shù)以字符串作為參數(shù)钦奋,但傳遞的是其他類型的參數(shù)時(shí)座云,都會(huì)使用這個(gè)默認(rèn)編碼。這就是很多時(shí)候出現(xiàn) UnicodeEncodeError 和 UnicodeDecodeError
錯(cuò)誤的原因锨苏,也就是字符串對(duì)象互相轉(zhuǎn)化時(shí)沒有指定字符編碼疙教。
例如,如果對(duì) unicode
和 str
類型通過 +
拼接時(shí)伞租,輸出結(jié)果是 unicode
類型,相當(dāng)于先將 str 類型的字符串通過 decode()
方法解碼成unicode
再拼接限佩。此時(shí)如果解碼時(shí)沒有明確指明編碼類型葵诈,可能會(huì)出現(xiàn)錯(cuò)誤。
解決這個(gè)問題的一個(gè)辦法是祟同,代碼開頭就調(diào)用 sys.setdefaultencoding()
將默認(rèn)的編碼設(shè)置為真正會(huì)用到的編碼作喘。但這樣僅僅是將問題隱藏起來,雖然這樣剛開始能解決一些文本處理問題晕城。但缺乏實(shí)際可行性泞坦,因?yàn)樵S多應(yīng)用,特別是網(wǎng)絡(luò)應(yīng)用砖顷,在不同的地方會(huì)使用不同的文本編碼贰锁。
下面是一些處理 Python 中字符編碼的建議:
- 所有文本字符串都應(yīng)該是
unicode
類型,而不是str
類型滤蝠。 - 若要將字節(jié)串解碼成字符串豌熄,需要使用正確的解碼,即
var.decode(encoding)
物咳,如:var.decode(‘utf-8’)
锣险;將文本字符串編碼成字節(jié),使用var.encode(encoding)
览闰。 - 永遠(yuǎn)不要對(duì)
unicode
字符串使用str()
芯肤,也不要在不指定編碼的情況下就對(duì)字節(jié)串使用unicode()
。 - 當(dāng)應(yīng)用從外部讀取數(shù)據(jù)時(shí)压鉴,應(yīng)將其視為字節(jié)串崖咨,即 str 類型的,接著調(diào)用
.decode()
將其解釋成文本晴弃。同樣掩幢,在將文本發(fā)送到外部時(shí)逊拍,總是對(duì)文本調(diào)用.encode()
。 - 對(duì)標(biāo)準(zhǔn)流進(jìn)行操作時(shí)际邻,可以改變環(huán)境變量
PYTHONIOENCODING
的值來設(shè)置標(biāo)準(zhǔn)流的默認(rèn)編碼芯丧,sys.stdin.encoding
和sys.stdout.encoding
的值為期望的編碼。
參考資料
廖雪峰的官方網(wǎng)站-字符串和編碼
Unicode HOWTO
http://www.tuicool.com/articles/2MVRVv7
https://gist.github.com/x7hub/178c87f323fbad57ff91
http://python.jobbole.com/86578/