不要相信requests編碼後返回的text

veelion發表於2019-01-05

Python的requests庫是一個非常好用的庫,這應該已經是大多寫過爬蟲的人的共識了。它的簡潔易用給我們帶來很大方便。然而,它也並不是非常完美。今天我們就說說它在處理中文編碼方面的不足。

requests encode error

requests的使用非常簡單,如下:
reequest的使用

一句函式呼叫,就可以獲得請求結果的物件response,透過response.content 可以得到原始的二進位制資料,透過response.text可以得到解碼後的文字資料,解碼是根據response.encoding進行的。然而,requests對這個encoding(編碼)的獲取是有問題的。

它獲取編碼的過程分為兩步,不幸的是每一步都有問題:

第一步:從http返回的headers裡面找編碼。

這一步的程式碼在原始檔utils.py裡面是get_encoding_from_headers(headers)函式:

headers裡面找編碼

最後兩行程式碼,它認為headers裡面的‘Content-Type’包含‘text’就是‘ISO-8859-1’編碼。這種想法是不嚴謹的

我們用chrome瀏覽器開啟最開始程式碼中的那個網址,這是一箇中文網頁:

http://epaper.sxrb.com/

在用Chrome的F12檢視http響應的頭,如下:

http響應的頭

這個網站給出的Content-Type不是下面的正規格式:

Content-Type: text/html; charset=UTF-8

然後,requests的get_encoding_from_headers函式就得到了ISO-8859-1的編碼,再用這個編碼去解碼中文,當然就會出現亂碼。

第二步:如果不能從響應headers得到編碼,就用chardet從二進位制的content猜測

嚴格講,這步出現的編碼問題不是requests的,而是chardet的,就判requests一個失察之責吧。

在requests的原始碼models.py中定義了requests.get()返回的類Response。我們再看看其中text()的定義:

text()定義

響應頭找不到編碼時,self.encoding就是None。它就會透過self.apparent_encoding獲得編碼,那就再看看這個apparent_encoding是怎麼來的:

apparent_encoding的定義

很簡單,就是透過chardet檢測的。問題就出現在這個chardet上面。那我們就打破砂鍋問到底,去看看chardet的程式碼。

chardet檔案

上圖是chardet的全部原始碼。其中處理國標中文編碼的gb2312開頭的兩個檔案。我們用grep再看看全部程式碼中含有gb的部分:

grep -i gb *py

含有GB的程式碼

以上說明,chardet對國標中文編碼返回的就是(只是)GB2312。那麼問題就來了,國標不只是GB2312,還有GBK,GB18030編碼。

(1)GB 2312 標準共收錄 6763 個漢字

(2)GBK 即漢字內碼擴充套件規範,共收入 21886 個漢字和圖形符號,相容GB2312

(3)GB 18030 與 GB 2312-1980 和 GBK 相容,共收錄漢字70244個

由此可知,三種國標中文編碼的漢字個數是如下關係:

GB2312 < GBK < GB18030

如果不屬於GB2312的漢字用GB2312去編解碼會出現上面問題呢?我們來做個實驗:

GB2312與GBK

例子中的“鎔”字不在GB2312中,用這個編碼時就會報錯,用GBK編碼後的二進位制資料再用GB2312解碼時同樣會報錯,都是因為“鎔”不是GB2312裡面的漢字。

這時候,我們像requests那樣把errors設定為replace再用GB2312解碼得到的文字就會有亂碼出現,“鎔”字變成亂碼了。

最後我們用chardet檢驗二進位制資料的編碼,得到的是GB2312,但應該是GBK或GB18030編碼。當然,chardet的這個bug已經有人在github提出issues,最早是2014年的#33, 後來有#99,#168,但是不懂中文的老外一直沒有merge到master。

問題弄明白了,那麼建議是什麼呢?在爬蟲中,尤其是抓取中文網頁(非英文網頁)時用cchardet檢驗response.content,而不是直接用response.text。

cchardet是uchardet的Python繫結,後者是用C++實現的字元編碼檢測庫,來自Mozilla組織,質量過硬,速度更快,值得信賴。

uchardet介紹

猿人學banner宣傳圖

我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。

***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***

相關文章