[Python除錯] 'gbk' codec can't encode character xxx in position的錯誤解決&編碼與解碼的思考探究
錯誤出現
使用request
模組爬取網頁,將頁面原始檔res.text
儲存到檔案get.html
時,
import request
res = requests.get('http://weibo.com')
with open(r'd:\get.html', 'w') as f:
f.write(res.text)
發生瞭如下錯誤:
Traceback (most recent call last):
File "E:/Project/Study/study.py", line 16, in <module>
f.write(res.text)
UnicodeEncodeError: 'gbk' codec can't encode character '\xca' in position 349: illegal multibyte sequence
這是一個編碼錯誤,翻譯為:
gbk編解碼器不能對位置349的字元’\xca’編碼:不合法的多位元組流
encode
指編碼,是指將Unicode字元按照編碼規則編碼為位元組流
例如將中文的"好"字編碼為UTF-8和GBK
char = '好'
print(char.encode('utf-8'))
print(char.encode('gbk'))
#輸出
b'\xe5\xa5\xbd' #得到位元組流(Utf-8將漢字編碼為3個位元組)
b'\xba\xc3' #gbk將漢字編碼為2個位元組
意味著GBK編碼器不能對字元’\xca’編碼(Python3中能顯示的字元都是Unicode字元),
錯誤等價於以下:
char = '\xca'
char.encode('gbk')
#輸出
UnicodeEncodeError: 'gbk' codec can't encode character '\xca' in position 0: illegal multibyte sequence
錯誤分析
UnicodeEncodeError: ‘gbk’ codec can’t encode character ‘\xca’ in position 349: illegal multibyte sequence
對於,首先要找出出錯的位置character '\xca' in position 349
text_arr=res.text.split('\n') #將原始碼分行
length=i=0 #length是每行長度的累加,i是找到的行數
while True:
i=i+1
length+=len(arr[i])
if length>349 : #找出比349大的位置行在第幾行
print(i)
break
#輸出
11
所以出錯位置在text_arr[10]
,即原始碼第11行
>>>text_arr[10]
window.use_fp = "1" == "1"; // ÊÇ·ñ²É¼¯É豸ָÎÆ¡£
找到出錯的字元位置
import re
>>>re.search('\xca',text_arr[10])
<re.Match object; span=(35, 36), match='Ê'>
>>>'Ê'=='\xca'
True
於是,確定不能編碼的字元為該行第35個字元Ê
先不管這個字元,首先思考下為什麼原始碼res.text
為什麼會出現亂碼?
猜測res.text
不是用Unicode編碼的,但是嘗試用Unicode編碼,所以顯示亂碼
' window.use_fp = "1" == "1"; // ÊÇ·ñ²É¼¯É豸ָÎÆ¡£'
根據request
模組的說明,修改原始碼的編碼方式res.encoding
為源網頁<meta>
標籤中charset
的屬性
>>> re.search('charset=(.*?)[;"]',res.text).group(1) #查詢charset
'gb2312'
>>> res.encoding
'ISO-8859-1'
>>> res.encoding='gb2312'
再次剛才顯示出錯的行,發現亂碼已經修復,能夠成功編碼並儲存
>>> res.text.split('\n')[10] #改變編碼後的原來那行
' window.use_fp = "1" == "1"; // 是否採集裝置指紋。' #顯示為Unicode字元,亂碼修復了
#儲存
>>> with open(r'd:\get.html', 'w') as f:
try:
f.write(res.text)
except :
print("儲存失敗")
raise
else :
print("儲存成功")
儲存成功
進一步思考
再一次理清思路:
以下是儲存為Unicode
的源網頁1.html
,假如爬取該網頁res=request.get("1.html")
<html>
<meta content="text/html; charset=gb2312">
' window.use_fp = "1" == "1"; // 是否採集裝置指紋。'
</html>
res.content
儲存著從源網頁獲取的Unicode字串使用了gb2312
編碼後的位元組流
>>> res.content
b' window.use_fp = "1" == "1"; // \xca\xc7\xb7\xf1\xb2\xc9\xbc\xaf\xc9\xe8\xb1\xb8\xd6\xb8\xce\xc6\xa1\xa3'
res.encoding
表示從content
解碼(decode)到text
時使用的編碼
>>> res.encoding
'ISO-8859-1' #一開始失敗的編碼,後面修改為'gbk'才成功
res.text
儲存著解碼後的字串
>>> res.text
window.use_fp = "1" == "1"; // ÊÇ·ñ²É¼¯É豸ָÎÆ¡£
所以,如果開啟檔案get.html
,使用預設編碼模式gbk
,將res.text
寫入時,gbk編譯器gbk codec
會將res.text
使用f.encoding(預設 cp936,即gbk)
編碼為位元組流,然後輸出到檔案(檔案儲存的都是位元組流).
編碼encode
與解碼decode
是一個可逆過程,所以大多數情況下(除了擴充套件情況,例如gbk是gb2312的擴充套件,所以兩者共同擁有的字元不影響), 以一種模式編碼字串所得到的位元組流只能由相同的編碼來解碼得到該字串.
因為gbk codec
只能編碼 使用res.encoding=gbk
解碼得到的res.text
,所以在編碼 使用了res.encoding='ISO-8859-1'
解碼得到的res.text
會提示題目所示的錯誤.
所以剛剛的解決方案實際上是設定 res.encoding=gbk
,使res.content
解碼為gbk
編碼的res.text
,這樣gbk codec
在對res.text
編碼時,兩個模式都是gbk
,就不會出錯了.
另外一個可行解決方案是:以二進位制開啟檔案,gbk codec
直接儲存由gbk
編碼的位元組流.
res = requests.get('http://weibo.com')
with open(r'd:\get.html', 'wb') as f:
f.write(res.content)
檔案以gbk
編碼模式開啟,可以寫入使用gb2312
編碼的位元組流res.content