爬蟲編碼問題詳解 (requests)

平時我們使用 requests 時, 通過兩種方法拿到響應的內容:

import requests
response = requests.get('https://example.com')
a = response.content  # type: bytes
b = response.text  # type: str

其中 response.text 是我們常用的.

requests 的作者在註釋中這樣寫道:
在這裏插入圖片描述
意思是說, response.text 是以 response.content (字節碼) 和 encoding (編碼) 爲根據, 將字節碼轉換成 str 返還給我們的.

換句話說就是, response.text 自動幫我們把從服務器獲得的字節流轉換成 str 返還給我們, 爲我們減去了各種網頁編碼帶來的 “亂碼” 的問題.

那麼爲什麼 response.text 仍然有時候會出現亂碼的問題呢?

這就是本文要解釋的問題, 接下來我會一個例子說明.


這是一個有 “問題” 的網頁: http://www.most.gov.cn/ztzl/gjkxjsjldh/jldh2002/zrj/zrjml.htm

在瀏覽器中打開它, 顯示的是正常的:
在這裏插入圖片描述
而通過 response.text 查看就會出現亂碼.

首先, requests 在計算 response.text 的時候, 會先看響應的 http header 中是否提供了 encoding 信息. 我們通過 response.encoding 可以拿到這個值. 可以看到, 確實是有的, 是 “ISO-8859-1”.

按理來說, 網頁已經告訴我們, 它用的是 “ISO-8859-1” 編碼, 於是 requests 就信以爲真, 拿 “ISO-8859-1” 來解析字節流, 轉換爲字符串, 於是我們就看到 response.text 的亂碼了 - 問題就出在, 網頁告訴了爬蟲一個錯誤的編碼信息.

那麼我們提出新的思路, 如果我們不使用 http header 提供的 encoding, 而採用其他方法獲知 encoding 有沒有可能呢?

requests 確實有此方法, 不過是在 http header 提供的 encoding 爲 None 時纔會用. 它通過 chardet (一個 Python 第三方模塊, 用來預測文件編碼) 來檢測網頁最可能用的是哪種編碼. 注意這裏是 “可能用的”, 因爲 chardet 的原理就是用各種編碼來試, 直到找出一個看起來不像是亂碼的情況.

我們通過 response.apparent_encoding 可以拿到這個值. 可以看到, chardet 預測的編碼是 “GB2312”.

這裏給一個小提示, GB2312 的字符集實在是太小了, 我們最好使用它的超集 GBK 來替代, 以避免某些生僻字解碼不出來:

# result = response.content.decode(encoding='GB2312')  # abandoned
result = response.content.decode(encoding='GBK')  # suggest

另外還要注意的是, 在解碼的過程中, 可能會遇到字符集以外的未知標識導致解碼中斷並報錯, 添加一個 errors 參數可以避免此問題:

result = response.content.decode(encoding='GBK', errors="ignore")
# result = response.content.decode(encoding='GBK', errors="replace")

errors='ignore' 的效果如下:
在這裏插入圖片描述
erros='replace' 的效果如下:
在這裏插入圖片描述
總結一下, response.text 造成亂碼的原因不外乎兩種: 要麼是網頁提供了錯誤的編碼 (比如上例中的網頁, 明明是中文網頁卻提供了一個純西文字符集 “ISO-8859-1”), 要麼是 chardet 預測的編碼不當 (比如取了某個編碼的子集, 導致大量生僻字顯示爲亂碼).

(另外想說一句, 對於 pdf, jpg 等二進制文件 url, response.encoding, response.apparent_encoding 給出的結果都是 None.)

瞭解了這些以後, 我們就有辦法自己動手, 解決亂碼了.

import requests


def get_text(resp):
    # 優先使用 chardet 預測的 encoding, 其次使用 http header 提供的 encoding
    source_encoding = resp.apparent_encoding or resp.encoding
    if source_encoding is None:
        # 說明是二進制文件, 比如 pdf, jpg 之類的
        raise Exception
    elif source_encoding == 'GB2312':
        source_encoding = 'GBK'
    return resp.content.decode(source_encoding, errors="ignore")


# 測試 "問題" 網頁
url = 'http://www.most.gov.cn/ztzl/gjkxjsjldh/jldh2002/zrj/zrjml.htm'
response = requests.get(url)
text = get_text(response)
# | text = response.text  # 不用這個了

# 保存爲文件
with open('result.html', 'w', encoding='utf-8') as f:
    f.write(text)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章