在寫(xiě)爬蟲(chóng)爬取網(wǎng)頁(yè)信息時(shí),發(fā)生了以下錯(cuò)誤:
UnicodeEncodeError: 'gbk' codec can't encode character '\ufffd'
意思大致是Unicode編碼錯(cuò)誤,gbk編解碼器不能編碼\ufffd
字符。
爬蟲(chóng)程序爬取的是課程信息,包含中文。使用requests庫(kù)訪問(wèn)網(wǎng)頁(yè),使用BeautifulSoup庫(kù)解析網(wǎng)頁(yè),用get_text()
方法獲取標(biāo)簽內(nèi)的文本信息。
python版本為3.5,在cmd控制臺(tái)中運(yùn)行python腳本。
代碼大致如下:
import requestsfrom bs4 import BeautifulSoupr = requests.get(url,cookies=cookies,headers=headers)soup = BeautifulSoup(r.text,"lxml")lesson_data_list = soup.find_all(id="xjs_table")[0].find_all("tr")[1:]for lesson_data in lesson_data_list: td = lesson_data.find_all("td") name = td[0].get_text() credit = td[1].get_text() teacher = td[2].get_text() time = td[5].get_text() total = td[11].get_text() print(name,credit,teacher,time,total)
cmd默認(rèn)編碼是GBK,字符\ufffd
不能編碼為GBK。
查閱Unicode編碼表或者使用Python自帶的集成開(kāi)發(fā)環(huán)境IDLE(可以輸出),可知\ufffd
其實(shí)是?字符。
Python3的字符串以Unicode編碼,也就是解析網(wǎng)頁(yè)時(shí),需要將字符串從網(wǎng)頁(yè)原來(lái)的編碼格式轉(zhuǎn)化為Unicode編碼格式。
出現(xiàn)?字符的原因:從某編碼向Unicode編碼轉(zhuǎn)化時(shí),如果沒(méi)有對(duì)應(yīng)的字符,得到的將是Unicode的代碼\ufffd
,也就是?字符。
在寫(xiě)爬蟲(chóng)解析網(wǎng)頁(yè)時(shí)出現(xiàn)?字符,往往是因?yàn)闆](méi)有注意原網(wǎng)頁(yè)的編碼格式,全按照默認(rèn)編碼UTF-8轉(zhuǎn)化,導(dǎo)致有的字符轉(zhuǎn)化失敗。
在本問(wèn)題中,是由于在網(wǎng)頁(yè)上包含珺
、玥
這樣的中文,由于珺
、玥
是生僻字沒(méi)有相應(yīng)的UTF-8編碼,所以以默認(rèn)UTF-8編碼格式轉(zhuǎn)化為Unicode時(shí),沒(méi)有對(duì)應(yīng)的字符,轉(zhuǎn)化出錯(cuò),得到\ufffd
。又因?yàn)樵赾md運(yùn)行,因此報(bào)錯(cuò)UnicodeEncodeError: 'gbk' codec can't encode character '\ufffd'
。
寫(xiě)爬蟲(chóng)解析網(wǎng)頁(yè)時(shí),要注意原網(wǎng)頁(yè)的編碼格式和壓縮格式(Gzip等)
查看原網(wǎng)頁(yè)的編碼格式,為’gb2312’。
r = requests.get(url,cookies=cookies,headers=headers)# 指定網(wǎng)頁(yè)的編碼格式r.encoding = 'gbk'# r.encoding = 'gb2312' 仍然會(huì)報(bào)錯(cuò)soup = BeautifulSoup(r.text,"lxml")
最后再次在cmd運(yùn)行代碼,珺
、玥
這樣的中文也成功顯示。
注意:本解決辦法適用于與本問(wèn)題描述完全相同的問(wèn)題,因?yàn)榭赡苁窍嗤腻e(cuò)誤原因?qū)е碌?。其它?bào)錯(cuò)信息可能與本問(wèn)題的情況不同,比如報(bào)錯(cuò)的為‘gbk’ codec can’t encode character ‘\xa0’,爬蟲(chóng)在寫(xiě)入文件時(shí)報(bào)錯(cuò)。
以下是問(wèn)題的逐步分析解決過(guò)程。
對(duì)于Unicode字符,需要print出來(lái)的話,由于本地系統(tǒng)是Windows中的cmd,默認(rèn)codepage是CP936,即GBK的編碼,所以python解釋器需要先將上述的Unicode字符編碼為GBK,然后再在cmd中顯示出來(lái)。但是由于該Unicode字符串中包含一些GBK中無(wú)法顯示的字符,導(dǎo)致此時(shí)提示'gbk' codec can’t encode
的錯(cuò)誤的。
分析:這個(gè)解釋確實(shí)符合報(bào)錯(cuò)信息'gbk' codec can't encode character '\ufffd'
的意思。
驗(yàn)證:編寫(xiě)python代碼
print('\ufffd')
在cmd下運(yùn)行,出現(xiàn)相同的報(bào)錯(cuò)信息'gbk' codec can't encode character '\ufffd'
解決辦法1:改變標(biāo)準(zhǔn)輸出的默認(rèn)編碼
在程序的開(kāi)始加入以下代碼
import sysimport iosys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
由于我輸出的包含中文,所以使encoding='gb18030'
,這段代碼的作用就是把標(biāo)準(zhǔn)輸出的默認(rèn)編碼修改為gb18030,也就是與cmd顯示編碼GBK相同。
效果:運(yùn)行原來(lái)的爬蟲(chóng)代碼后,沒(méi)有了報(bào)錯(cuò),但原先報(bào)錯(cuò)的輸出位置顯示為??
,中文仍沒(méi)有正確顯示。
解決辦法2:輸出時(shí)忽略無(wú)法編碼的字符
在對(duì)Unicode字符編碼時(shí),添加ignore參數(shù),可以忽略無(wú)法編碼的字符
print 你的字符串.encode(“GBK“, ‘ignore’).decode
這句代碼的作用為,把字符串以GBK編碼,并忽略無(wú)法編碼的字符,再以GBK解碼,再輸出。
效果:在解決辦法1的效果基礎(chǔ)上,??
不再顯示,但中文仍沒(méi)有正確顯示。
\ufffd
本身就沒(méi)有對(duì)應(yīng)的Unicode編碼,所以要在cmd中不報(bào)錯(cuò)輸出,只能把輸出編碼改為GBK或者在輸出時(shí)忽略無(wú)法編碼的字符,這樣就算可以不報(bào)錯(cuò)輸出,也不能正確顯示\ufffd
字符。cmd的顯示編碼是GBK,要想正確顯示\ufffd
字符,就要使用支持多種輸出編碼的運(yùn)行環(huán)境。Python自帶的集成開(kāi)發(fā)環(huán)境IDLE就對(duì)GBK、UTF-8、Unicode都支持。其實(shí),Pycharm、jupyter notebook、sublimeREPL配置的python運(yùn)行環(huán)境也支持多種輸出編碼。
IDLE打開(kāi)方法:在python的安裝目錄下,打開(kāi)lib/idlelib/idle.bat
,就進(jìn)入了IDLE環(huán)境
運(yùn)行代碼
print('\ufffd')
輸出為: ?
\ufffd
字符已經(jīng)正確顯示了。 以上分析的原因和解決辦法都是從最后顯示輸出的角度去考慮的,雖然一些方法不再報(bào)錯(cuò),但仍然顯示不正確,并沒(méi)有真正解決。然后在IDLE中正確顯示了字符,證明了\ufffd
就是?字符。
從以上解決辦法可以進(jìn)一步驗(yàn)證和推測(cè)一些問(wèn)題:
問(wèn)題
本來(lái)應(yīng)該顯示的漢字是什么
?是從哪來(lái)的
推測(cè)
對(duì)照原網(wǎng)頁(yè),發(fā)現(xiàn)是珺
和玥
字沒(méi)有正確顯示,而變成了\fffd
?字符。這說(shuō)明從一開(kāi)始解析網(wǎng)頁(yè),珺
和玥
就被解析成了?字符。
驗(yàn)證
如果從報(bào)錯(cuò)信息找問(wèn)題,報(bào)錯(cuò)原因是部分Unicode字符不能正確轉(zhuǎn)碼成GBK,所以無(wú)法顯示。根據(jù)推測(cè),原網(wǎng)頁(yè)上的珺
和玥
字沒(méi)有正確顯示,而運(yùn)行以下代碼:
str = '珺玥'str = str.encode('unicode_escape')print(str)# 輸出 b'\\u73fa\\u73a5'
可知珺
和玥
的Unicode分別為73fa
和73a5
。而不是fffd
。
經(jīng)驗(yàn)證,print('\u73a5')
是可以輸出的玥
字的。這說(shuō)明并不是玥
的Unicode轉(zhuǎn)碼為gbk失敗,而是解析玥
得到的Unicode都不正確,變成了\ufffd
?字符。
使用爬蟲(chóng) ?
作為關(guān)鍵字搜索,就找到了出現(xiàn)?字符的原因:
從某編碼向Unicode編碼轉(zhuǎn)化時(shí),如果沒(méi)有對(duì)應(yīng)的字符,得到的將是Unicode的代碼“\uffffd”,也就是?這個(gè)字符。這個(gè)是你的爬蟲(chóng)根本不識(shí)別原網(wǎng)頁(yè)的編碼格式(ASCII或者GB2312等)和壓縮格式(Gzip等),全都無(wú)腦轉(zhuǎn)成UTF-8字符串導(dǎo)致的,出現(xiàn)這個(gè)字符說(shuō)明轉(zhuǎn)換失敗,數(shù)據(jù)已經(jīng)丟失了,這個(gè)字符本身并沒(méi)什么實(shí)際意義。
出自鏈接:在處理一些爬下來(lái)的網(wǎng)頁(yè)時(shí),經(jīng)常發(fā)現(xiàn)會(huì)存在?這個(gè)字符
在進(jìn)一步分析后,重新檢查解析過(guò)程,顯然是編碼問(wèn)題。
最后發(fā)現(xiàn)確實(shí)是因?yàn)闆](méi)有注意到網(wǎng)頁(yè)編碼問(wèn)題,網(wǎng)頁(yè)編碼是gb2312,而用requests請(qǐng)求的網(wǎng)頁(yè),如果不聲明encoding值,默認(rèn)是用UTF-8解析的。UTF-8編碼支持大部分中文字,但不支持珺
、玥
這樣的生僻字,這樣轉(zhuǎn)成UTF-8就轉(zhuǎn)化失敗。
所以要按照gb2312編碼向Unicode編碼轉(zhuǎn)化:
r = requests.get(url,cookies=cookies,headers=headers)# 指定網(wǎng)頁(yè)的編碼格式r.encoding = 'gbk'# r.encoding = 'gb2312' 仍然會(huì)報(bào)錯(cuò)soup = BeautifulSoup(r.text,"lxml")
最后再次在cmd運(yùn)行代碼,珺
、玥
這樣的中文也成功顯示。
最終總結(jié),是由于在網(wǎng)頁(yè)上包含珺
、玥
這樣的中文,由于珺
、玥
是生僻字沒(méi)有相應(yīng)的UTF-8編碼,所以以默認(rèn)UTF-8編碼格式轉(zhuǎn)化為Unicode時(shí),沒(méi)有對(duì)應(yīng)的字符,轉(zhuǎn)化出錯(cuò),得到\ufffd
。又因?yàn)樵赾md運(yùn)行,因此報(bào)錯(cuò)UnicodeEncodeError: 'gbk' codec can't encode character '\ufffd'
。
聯(lián)系客服