[Python] 中文編碼問題:raw_input輸入、文件讀取、變量比較等str、unicode、utf-8轉換問題

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


參考資料:
        詳解 python 中文編碼與處理
        python字符編碼與解碼unicode、str和中文 'ascii' codec can't decode
        Python的中文編碼問題-segmentfault
        書籍《Python核心編程(第二版)》和《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標題中。web

 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)segmentfault



 

二. 讀取中文文件亂碼處理

        此時你的爬蟲僅僅是能從raw_input中輸入進行處理或者定義unicode的字符串進行定向爬取,可是若是關鍵詞不少就須要經過讀取文件來實現。以下圖所示,是我"Python爬取百度InfoBox"這篇文章。一樣,你會遇到各類中文亂碼問題須要處理。windows

        舉個簡單例子:經過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-一、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/

相關文章
相關標籤/搜索