1. 程式人生 > >python爬蟲中文亂碼問題(request方式爬取)

python爬蟲中文亂碼問題(request方式爬取)

req = requests.get(url)返回的是類物件

其包括的屬性有:

req.encoding:返回編碼方式

req.text:text返回的是處理過的Unicode型的資料

req.content:content返回的是bytes型的原始資料

content是把內容bytes返回. 而text是decode成Unicode. 如果headers沒有charset字符集的化,text()會呼叫chardet來計算字符集

也就是說text是解碼完的資料,呼叫req.text()就不需要解碼了,問題經常出現解碼中的型別碼是否正確,這個下面在說,先明白text()和content()的區別

假設對獲取的內容最終都編碼成‘utf-8’的型別text()和content的處理方式如下所示:

requests.get(url).text.encode('utf8','ignore')  #如果req.text()自動解碼正確,直接編碼,自動解碼下面介紹

requests.get(url).content.decode('gbk','ignore').encoding('utf-8','ignore') #假設原始碼是gbk,則需要先解碼在編碼成utf-8


1. 遇到的中文亂碼問題
1.1 簡單的開始
    使用requests來拔取網站內容十分方便,一個最簡單的程式碼段只需要2-3行程式碼就行。

  1. url = 'http//www.pythonscraping.com/'
  2. req = requests.get(url)
  3. print(req.text)
  4. tree = html.fromstring(req.text)
  5. print(tree.xpath("//h1[@class='title']/text()"))

1.2 麻煩的開始
    本來當時的想法是寫一些基礎模組,方便之後開發的時候呼叫,減少重複性工作。為了保證程式碼在任何情況下都不會出現bug,所以想著用同樣的程式碼爬取中文網站獲取裡面的文字
    修改上面程式碼中的兩行程式碼:

  1. url = 'http://sports.sina.com.cn/g/premierleague/index.shtml'
  2. print(tree.xpath("//span[@class='sec_blk_title']/text()"))

    執行程式可以發現,在語句print(req.text)輸出的內容中,中文字型已經是亂碼了。最後的結果輸出是['?????©è§\x86é?\x91', '??\x80?\x9c\x9f?\x9b\x9eé??']
2 亂碼解決辦法
2.1 試錯
    由於之前爬取csdn上一個網頁沒有出現亂碼問題,但是在sina體育網站上出現了亂碼,所以當時以為不是編碼問題,以為是文件壓縮問題。因為csdn獲取的頁面header裡沒有“Content-Encodings”屬性,但是sina體育獲取的頁面header有“Content-Encodings”屬性--“Content-Encoding: gzip”。
    在網上查看了多個相關問題的解決方案:
    1. http://stackoverflow.com/questions/3122145/zlib-error-error-3-while-decompressing-incorrect-header-check
    2. http://blog.csdn.net/pxf1234567/article/details/42006697
    3. http://blog.csdn.net/bytxl/article/details/21278249

總結:參考上述文獻,結果還是沒有解決問題,但是就考慮是不是方向錯了。不過這部分工作也沒有白做,很多網站返回資料都會有壓縮問題,之後的工作中也能用上。

2.2 亂碼終極解決辦法
    後來查閱官方文件中response-content相關內容,說明了Requests會自動解碼來自伺服器的內容。Requests會基於HTTP頭部對響應的編碼作出有根據的推測,前提是響應文件的HTTP headers裡面沒有相關字符集說明。官方文件還說明了,如果你建立了自己的編碼,並使用codecs 模組進行註冊,你就可以輕鬆地使用這個解碼器名稱作為 r.encoding 的值, 然後由Requests來為你處理編碼。(自己沒有使用codecs模組,所以這裡不貼程式碼了,不過按官方的說法使用codecs模組是最簡單的一種方式。)
    另一份官方文件片段明確說了reponse編碼處理方式:
        Requests遵循RFC標準,編碼使用ISO-8859-1 。
        只有當HTTP頭部不存在明確指定的字符集,並且 Content-Type 頭部欄位包含 text 值之時, Requests才不去猜測編碼方式。

    現在直接上實驗結果,在原始程式碼中新增以下程式碼片段:

點選(此處)摺疊或開啟

  1. print(req.headers['content-type'])
  2. print(req.encoding)
  3. print(req.apparent_encoding)
  4. print(requests.utils.get_encodings_from_content(page_content.text))

輸出結果分別是:
    text/html
    ISO-8859-1#response內容的編碼
    utf-8#response headers裡設定的編碼
    ['utf-8']#response返回的html header標籤裡設定的編碼
    返回的內容是採用‘ISO-8859-1’,所以出現了亂碼,而實際上我們應該採用‘utf-8’編碼
        
    總結:當response編碼是‘ISO-8859-1’,我們應該首先查詢response header設定的編碼;如果此編碼不存在,檢視返回的Html的header設定的編碼,程式碼如下:

點選(此處)摺疊或開啟

  1. if req.encoding == 'ISO-8859-1':
  2.     encodings = requests.utils.get_encodings_from_content(req.text)
  3.     if encodings:
  4.         encoding = encodings[0]
  5.     else:
  6.         encoding = req.apparent_encoding
  7. else:
  8.      encoding = req.encoding
  9. encode_content = req.content.decode(encoding, 'ignore').encode('utf-8', 'ignore')