gb2312 requests乱码_不要相信requests返回的text
本文投稿自公眾號:猿人學python
Python的requests庫是一個非常好用的庫,這應該已經是大多寫過爬蟲的人的共識了。它的簡潔易用給我們帶來很大方便。然而,它也并不是非常完美。今天我們就說說它在處理中文編碼方面的不足。
requests的使用非常簡單,如下:
一句函數調用,就可以獲得請求結果的對象response,通過response.content 可以得到原始的二進制數據,通過response.text可以得到解碼后的文本數據,解碼是根據response.encoding進行的。然而,requests對這個encoding(編碼)的獲取是有問題的。
它獲取編碼的過程分為兩步,不幸的是每一步都有問題:
第一步:從http返回的headers里面找編碼。
這一步的代碼在源文件utils.py里面是get_encoding_from_headers(headers)函數:
最后兩行代碼,它認為headers里面的‘Content-Type’包含‘text’就是‘ISO-8859-1’編碼。這種想法是不嚴謹的。
我們用chrome瀏覽器打開最開始代碼中的那個網址,這是一個中文網頁:
http://epaper.sxrb.com/
在用Chrome的F12查看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()的定義:
響應頭找不到編碼時,self.encoding就是None。它就會通過self.apparent_encoding獲得編碼,那就再看看這個apparent_encoding是怎么來的:
很簡單,就是通過chardet檢測的。問題就出現在這個chardet上面。那我們就打破砂鍋問到底,去看看chardet的代碼。
上圖是chardet的全部源代碼。其中處理國標中文編碼的gb2312開頭的兩個文件。我們用grep再看看全部代碼中含有gb的部分:
grep -i gb *py?
以上說明,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解碼時同樣會報錯,都是因為“镕”不是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組織,質量過硬,速度更快,值得信賴。
望學有所獲 !每天進步一點點 !
總結
以上是生活随笔為你收集整理的gb2312 requests乱码_不要相信requests返回的text的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 厨房地面瓷砖用什么规格的好?
- 下一篇: fedora21 mysql_在fedo