最近研究搜索引擎、知識圖譜和Python爬蟲比較多,中文亂碼問題再次浮現於眼前。雖然市面上講述中文編碼問題的文章數不勝數,同時以前我也講述過PHP處理數據庫服務器中文亂碼問題,但是此處還是准備簡單做下筆記。方便以后查閱和大家學習。
中文編碼問題的處理核心都是——保證所有的編碼方式一致即可,包括編譯器、數據庫、瀏覽器編碼方式等,而Python通常的處理流程是將unicode作為中間轉換碼進行過渡。先將待處理字符串用unicode函數以正確的編碼轉換為Unicode碼,在程序中統一用Unicode字符串進行操作;最后輸出時,使用encode方法,將Unicode再轉換為所需的編碼即可,同時保證編輯器服務器編碼方式一致。
PS:當然Python3除外!這篇文章比較啰嗦,畢竟是在線筆記和體會嘛,望理解~
在詳細講解概念之前,先講述我最近遇到的字符編碼的兩個問題及解決。下圖是最常見到幾個問題編碼問題:

參考資料:
詳解 python 中文編碼與處理
python字符編碼與解碼unicode、str和中文 'ascii' codec can't decode
Python的中文編碼問題-segmentfault
書籍《Python核心編程(第二版)》和《Python基礎教程(第二版)》
一. raw_input輸入str轉換unicode處理
背景:在做Python定向圖片爬蟲時,會通過raw_input輸入關鍵詞如“主播”,會爬取標題title中包含"主播"的URL,再去到具體的頁面爬取圖集。
問題:如果是自定義字符串直接通過: s=u'主播' 定義為Unicode編碼,再與同樣為Unicode編碼的title.text(下一篇文章詳細介紹該爬蟲)比較即可。但是如果需要raw_input輸入呢?而且在通過unicode或decode轉換過程中總是報錯,為什么呢?
主要問題是如何將str轉換為unicode編碼(How to convert str to unicode),默認python編碼方式ascii碼。
unicode(string[, encoding[, errors]])
>>> help(unicode) Help on class unicode in module __builtin__: class unicode(basestring) | unicode(object='') -> unicode object | unicode(string[, encoding[, errors]]) -> unicode object | | Create a new Unicode object from the given encoded string. | encoding defaults to the current default string encoding. | errors can be 'strict', 'replace' or 'ignore' and defaults to 'strict'.
舉個簡單的例子:需要判斷搜索詞key是否在title標題中。
1 # coding=utf-8 2 import sys 3 4 def getTitle(key,url): 5 #title = driver.find_element_by_xpath() 6 title = u'著名女主播Miss與傑倫直播LOL' 7 print key,type(key) 8 print title,type(title) 9 if key in title: 10 print 'YES' 11 else: 12 print 'NO' 13 14 key = raw_input("Please input a key: ") 15 print key,type(key) 16 url = 'http://www.baidu.com/' 17 getTitle(key,url)
輸出如下圖所示:
嘗試修改的方法包括:通過unicode(key,'utf-8')轉碼、key.decode('utf-8')轉碼、重置sys.defaultencoding都不行。而通過key.decode('raw_unicode_escape')轉換得到的亂碼"Ö÷²¥"(主播)。而同學的Python2.7能將str轉換成unicode編碼。
"UnicodeDecodeError: 'ascii' codec can't decode byte" 需先將str轉換為unicode編碼,但是我s.decode('utf-8')就報錯 "UnicodeDecodeError: 'utf8' codec can't decode byte"。
s = '主播' s.decode('utf-8').encode('gb18030')
最后解決方法從stackoverflow得到,一方面說明自己確實研究得不是很深,另一方面那個論壇確實更強大。參考:
python raw-input odd behavior with accents containing strings
它是將終端的輸入編碼通過decode轉換成unicode編碼
key = raw_input("Please input a key: ").decode(sys.stdin.encoding)

二. 讀取中文文件亂碼處理
此時你的爬蟲僅僅是能從raw_input中輸入進行處理或者定義unicode的字符串進行定向爬取,但是如果關鍵詞很多就需要通過讀取文件來實現。如下圖所示,是我"Python爬取百度InfoBox"這篇文章。同樣,你會遇到各種中文亂碼問題需要處理。

舉個簡單例子:通過Selenium爬取百度百科Summary第一段。
# coding=utf-8 import sys import os import urllib import time from selenium import webdriver from selenium.webdriver.common.keys import Keys import selenium.webdriver.support.ui as ui from selenium.webdriver.common.action_chains import ActionChains #driver = webdriver.PhantomJS(executable_path="G:\phantomjs-1.9.1-windows\phantomjs.exe") driver = webdriver.Firefox() wait = ui.WebDriverWait(driver,10) def getTitle(line,info): print 'Fun: ' + line,type(line) driver.get("http://baike.baidu.com/") elem_inp = driver.find_element_by_xpath("//form[@id='searchForm']/input") elem_inp.send_keys(line) elem_inp.send_keys(Keys.RETURN) elem_value = driver.find_element_by_xpath("//div[@class='lemma-summary']/div[1]").text print 'Summary ',type(elem_value) print elem_value,'\n' info.write(line.encode('utf-8')+'\n'+elem_value.encode('utf-8')+'\n') time.sleep(5) def main(): source = open("E:\\Baidu.txt",'r') info = open("E:\\BaiduSpider.txt",'w') for line in source: line = line.rstrip('\n') print 'Main: ' + line,type(line) line = unicode(line,"utf-8") getTitle(line,info) else: info.close() main()
其中TXT通常默認為ANSI編碼,代碼步驟:
1.我先把Baidu.txt修改為utf-8編碼,同時讀入通過unicode(line,'utf-8')將str轉換為unicode編碼;
2.Selenium先通過打開百度百科,在輸入關鍵詞"北京故宮"進行搜索,通過find_element_by_xpath爬取"故宮"的summary第一段內容,而且編碼方式為unicode;
3.最后文件寫操作,通過line.encode('utf-8')將unicode轉換成utf-8,否則會報錯UnicodeDecodeError: 'ascii'。
總之過程滿足:編碼=》Unicode=》處理=》utf-8或gbk

由於創建txt文件時默認是ascii格式,而文字為'utf-8'格式時會報錯。當然你也可以通過CODECS方法創建制定格式文件。
codes是COder/DECoder的首字母組合。它定義了文本跟二進制值的轉換方式,跟ASCII那種用一個字節把字符轉換成數字的方式不同,Unicode用的是多字節。這也導致了Unicode支持多種不同的編碼方式。codes支持的四種編碼方式包括:ASCII、ISO 8859-1/Latin-1、UTF-8和UTF-16。
import codecs #用codecs提供的open方法來指定打開的文件的語言編碼,它會在讀取的時候自動轉換為內部unicode info = codecs.open(baiduFile,'w','utf-8') #該方法不是io故換行是'\r\n' info.writelines(key.text+":"+elem_dic[key].text+'\r\n')
三. Unicode詳解
PS: 該部分主要參考書籍《Python核心編程(第二版)》作者Wesley J.Chun
什么是Unicode
Unicode字符串聲明通過字母"u",它用來將標准字符串或者是包含Unicode字符的字符串轉換成完全的Unicode字符串對象。Python1.6起引進Unicode字符串支持,是用來在多種雙字節字符的格式、編碼進行轉換的。
Unicode是計算機支持這個星球上多種語言的秘密武器。在Unicode之前,用的都是ASCII碼,每個英文字符都是以7位二進制數的方式存儲在計算機內,其范圍是32~126。當用戶在文件中鍵入A時,計算機會把A的ASCII碼值65寫入磁盤,然后當計算機讀取該文件時,它會首先把65轉換成字符A再顯示到屏幕上。
但是它的缺點也很明顯:對於成千上萬的字符來說,ASCII實在太少。而Unicode通過使用一個或多個字節來表示一個字符的方法,可以表示超過90,000個字符。
>>> s1 = "中文" >>> s1 '\xd6\xd0\xce\xc4' >>> print s1,type(s1) 中文 <type 'str'> >>> s2 = u"中文" >>> s2 u'\xd6\xd0\xce\xc4' >>> print s2,type(s2) ÖÐÎÄ <type 'unicode'> >>>
前面添加'u'聲明為Unicode字符串,但它實際的編碼並沒有改變。
編碼轉碼
Unicode支持多種編碼格式,這為程序員帶來了額外的負擔,每當你向一個文件寫入字符串的時候,你必須定義一個編碼(encoding參數)用於把對應的Unicode內容轉換成你定義的格式,通過encode()函數實現;相應地,當我們從這個文件讀取數據時,必須"解碼"該文件,使之成為相應的Unicode字符串對象。
str1.decode('gb2312') 解碼表示將gb2312編碼字符串轉換成unicode編碼
str2.encode('gb2312') 編碼表示將unicode編碼的字符串轉換成gb2312編碼
>>> s = '中文' >>> s '\xd6\xd0\xce\xc4' >>> print s,type(s) 中文 <type 'str'> >>> s.decode('gb2312') u'\u4e2d\u6587' >>> print s.decode('gb2312'),type(s.decode('gb2312')) 中文 <type 'unicode'> >>> len(s) 4 >>> len(s.decode('gb2312')) 2 >>> t = u'中文' >>> t u'\xd6\xd0\xce\xc4' >>> len(t) 4 >>> print t,type(t) ÖÐÎÄ <type 'unicode'> >>>
前綴'u'表示字符串是一個Unicode串,僅僅是一個聲明。
Unicode實際應用
1.程序中出現字符串時一定要加個前綴u
2.不要用str()函數,而是用unicode()代替
3.不要用過時的string模塊——如果給它的是非ASCII字符,它會把一切搞砸
4.不到必要時不要再程序里面編解碼Unicode字符。只在你要寫入文件或數據庫或網絡時,才調用encode()函數;相應地,只在需要把數據讀回來時才調用decode()函數
5.由於pickle模塊只支持ASCII字符串,盡量避免基於文本的pickle操作
6.假設構建一個用數據庫來讀寫Unicode數據的Web應用,必須保持以下對Unicode的支持
· 數據庫服務器(MySQL、PostgreSQL、SQL Server等)
· 數據庫適配器(MySQLLdb等)
· Web開發框架(mod_python、cgi、Zope、Django等)
數據庫方面確保每張表都用UTF-8編碼,適配器如果不支持Unicode如MySQLdb,則必須在connect()方法里面用一個特殊的關鍵字use_unicode來確保得到的查詢結果是Unicode字符串。mod_python開啟對Unicode的支持即可,只要在request對象里面把text-encoding設為“utf-8”就OK了。同時瀏覽器也注意下。
總結:使用應用程序完全支持Unicode,兼容其他的語言本身就是一個工程。它需要詳細的考慮、計划。所有涉及的軟件、系統都需要檢查,包括Python的標准庫和其他要用到的第三方擴展模塊。你甚至需要組件一個經驗豐富的團隊來專門負責國家化(I18N)問題。
四. 常用處理方法總結
源自:http://xianglong.me/article/learn-python-1-chinese-encoding/
結合我遇到的兩個問題,歸納了以下幾點。常見中文編碼問題解決方法包括:
1.遵循PEP0263原則,聲明編碼格式
在PEP 0263--Defining Python Source Code Encodings中提出了對Python編碼問題的最基本的解決方法:在Python源碼文件中聲明編碼格式,最常見的聲明方式:
#!/usr/bin/python # -*- coding: <encoding name> -*-
根據這個聲明,Python會嘗試將文件中的字符編碼轉為encoding編碼,它可以是任意一種Python支持的格式,一般都會使用utf-8\gbk的編碼格式。並且它盡可能的將指定地編碼直接寫成Unicode文本。
注意,coding:encoding只是告訴Python文件使用了encoding格式的編碼,但是編輯器可能會以自己的方式存儲.py文件,因此最后文件保存的時候還需要編碼中選指定的ecoding才行。
2.字符串變量賦值時添加前綴u,使用 u'中文' 替代 '中文'
str1 = '中文' str2 = u'中文'
Python中有以上兩種聲明字符串變量的方式,它們的主要區別是編碼格式的不同,其中tr1的編碼格式和Python文件聲明的編碼格式一致,而str2的編碼格式則是Unicode。
如果你要聲明的字符串變量中存在非ASCII的字符,那么最好使用str2的聲明格式,這樣你就可以不需要執行decode,直接對字符串進行操作,可以避免一些出現異常的情況。
3.重置默認編碼
Python中出現這么多編碼問題的根本原因是Python 2.x的默認編碼格式是ASCII,所以你也可以通過以下的方式修改默認的編碼格式:sys.getdefaultencoding()默認是'ascii'編碼。
#設置編碼utf-8 import sys reload(sys) sys.setdefaultencoding('utf-8') #顯示當前默認編碼方式 print sys.getdefaultencoding()
這種方法是可以解決部分編碼問題,但是同時也會引入很多其他問題,得不償失,不建議使用這種方式。
其原理: 首先, 這個就是Python語言本身的問題。因為在Python 2.x的語法中, 默認的str並不是真正意義上我們理解的字符串, 而是一個byte數組, 或者可以理解成一個純ascii碼字符組成的字符串, 與Python 3中的bytes類型的變量對應; 而真正意義上通用的字符串則是unicode類型的變量, 它則與Python 3中的str變量對應。本來應該用作byte數組的類型, 卻被用來做字符串用, 這種看似奇葩的設定是Python 2一直被人詬病的東西, 不過也沒有辦法, 為了與之前的程序保持兼容.。
在Python 2中作為兩種字符串類型, str與unicode之間就需要各種轉換的方式。首先是一種顯式轉換的方式, 就是encode和decode兩種方法。在這里這兩貨的意思很容易被搞反, 科學的調用方式是:
str --- decode方法 ---> unicode
unicode --- encode方法 ---> str
4.終極原則:decode early, unicode everywhere, encode late
Decode early:盡早decode, 將文件中的內容轉化成unicode再進行下一步處理
Unicode everywhere:程序內部處理都用unicode,比如字符串拼接、替換、比較等操作
Encode late:最后encode回所需的encoding, 例如把最終結果寫進結果文件
按照這個原則處理Python的字符串,基本上可以解決所有的編碼問題(只要你的代碼和Python環境沒有問題)。前面講述的兩個問題解決實質也是這樣,只是有些取巧即可。
5.使用decode().encode()方法
網頁采集時,代碼指定#coding:utf-8,如果網頁的編碼為gbk需要這樣處理:
html = html.decode('gbk').encode('utf-8')
6.輸入變量raw_input中文編碼
將終端的輸入編碼str通過decode轉換成unicode編碼,再使用unicode處理:
key = raw_input("Please input a key: ").decode(sys.stdin.encoding)
7.文件讀寫操作
由於默認的txt文件為ANSI編碼,讀取時通過unicode轉碼,經過“編碼=》Unicode=》處理=》utf-8或gbk ”順序即可。同時文件輸出時encode('utf-8')轉換txt為UTF-8格式。終極代碼:
info = codecs.open(baiduFile,'w','utf-8')
8.升級Python 2.x到3.x
最后一個方法:升級Python 2.x,使用Python 3.x版本。這樣說主要是為了吐槽Python 2.x的編碼設計問題。當然,升級到Python 3.x肯定可以解決大部分因為編碼產生的異常問題。畢竟Python 3.x版本對字符串這部分還是做了相當大的改進的。
在Python 3.0之后的版本中,所有的字符串都是使用Unicode編碼的字符串序列,同時還有以下幾個改進:
· 默認編碼格式改為unicode
· 所有的Python內置模塊都支持unicode
· 不再支持u'中文'的語法格式
所以,對於Python 3.x來說,編碼問題已經不再是個大的問題,基本上很少遇到上述的幾個異常。
總結
最后希望文章對你有所幫助,尤其是你剛好遇到這個問題的,由於是結合最近做的東西,所以文章比較雜亂,但如果你剛好需要,確實能解決你的問題的。
紀伯倫曾說過:“你無法同時擁有青春和關於青春的知識;因為青春忙於生計,沒有余暇去求知;而知識忙於尋求自我,無法享受生活。”
同樣現在找工作的我,無法在擁有扎實基礎知識的同時又兼顧深度的項目理解,但我更傾向於分享知識,因為它就是尋求自我,就是享受生活,就是編程之樂~
(By:Eastmount 2015-10-1 晚上11點 http://blog.csdn.net/eastmount/)