摘要:從零開始寫爬蟲,初學(xué)者的速成指南!
字符編解碼是爬蟲里必學(xué)的一項(xiàng)知識,在我們的爬蟲生涯中早晚會(huì)爬到亂碼的網(wǎng)頁,與其遇到時(shí)驚慌失措,不如早學(xué)早好,徹底避免亂碼問題。
字符編碼簡介
什么是字符集
在介紹字符編碼之前,我們先了解下什么是字符集。
字符(Character)是各種文字和符號的總稱,包括各國家文字、標(biāo)點(diǎn)符號、圖形符號、數(shù)字等。字符集(Character set)是多個(gè)字符的集合,字符集種類較多,每個(gè)字符集包含的字符個(gè)數(shù)不同,常見字符集:ASCII字符集、GBK字符集、Unicode字符集等。
什么是字符編碼
字符編碼和字符集不同。字符集只是字符的集合,無法進(jìn)行網(wǎng)絡(luò)傳送、處理,必須經(jīng)編碼后才能使用。如Unicode字符集可依不同需求以UTF-8、UTF-16、UTF-32等方式編碼。
字符編碼就是以二進(jìn)制的數(shù)字來對應(yīng)字符集的字符。各個(gè)國家和地區(qū)在制定編碼標(biāo)準(zhǔn)的時(shí)候,“字符的集合”和“編碼”一般都是同時(shí)制定的。因此,平常我們所說的“字符集”,除了有“字符的集合”這層含義外,同時(shí)也包含了“編碼”的含義。
常用字符集
簡單介紹幾個(gè)常見的。
ASCII:
ASCII是學(xué)計(jì)算機(jī)同學(xué)的啟蒙字符集,一般是從這本書里學(xué)到的:

請?jiān)试S我懷舊一下,以下引用譚浩強(qiáng)老師的講解:

中文字符集:
GB2312:包含6763個(gè)漢字。
GBK:包含21003個(gè)漢字。GBK兼容GB2312,也就是說用GB2312編碼的漢字可以用GBK來解碼。
GB18030:收錄了70000個(gè)漢字,這么多是因?yàn)榘松贁?shù)民族文字。同樣兼容GBK和GB2312。
Unicode:Unicode 是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,它為每種語言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,以滿足跨語言、跨平臺進(jìn)行文本轉(zhuǎn)換、處理的要求。具有多種編碼方式,如UTF-7、 UTF-8、UTF-16、UTF-32等。
為什么會(huì)產(chǎn)生亂碼
簡單的說亂碼的出現(xiàn)是因?yàn)椋壕幋a和解碼時(shí)用了不同的字符集。對應(yīng)到真實(shí)生活中,就好比是一個(gè)英國人為了表示祝福在紙上寫了bless(編碼)。而一個(gè)法國人拿到了這張紙,由于在法語中bless表示受傷的意思,所以認(rèn)為他想表達(dá)的是受傷(解碼)。同理,在計(jì)算機(jī)中,一個(gè)用UTF-8編碼后的字符,用GBK去解碼。由于兩個(gè)字符集的字庫表不一樣,同一個(gè)漢字在兩個(gè)字符表的位置也不同,最終就會(huì)出現(xiàn)亂碼。
那么,爬蟲中的亂碼是怎么產(chǎn)生的,又該如何解決呢?
爬蟲中的亂碼
假設(shè)我們的爬蟲是java開發(fā)的,網(wǎng)絡(luò)請求庫使用OkHttp,網(wǎng)頁存儲到MongoDB中。亂碼產(chǎn)生的過程如下:
OkHttp請求指定url,返回了一個(gè)GBK編碼的網(wǎng)頁字節(jié)流;
OkHttp以默認(rèn)UTF-8進(jìn)行解碼(此時(shí)已亂),并以UTF-16方式編碼為Java的String類型,返回給處理程序。(為什么以UTF-16方式編碼?因?yàn)镴ava的數(shù)據(jù)在內(nèi)存中的編碼是UTF-16);
爬蟲拿到這個(gè)編碼錯(cuò)誤的String類型的網(wǎng)頁,調(diào)用MongoDB的API,將數(shù)據(jù)編碼為UTF-8存儲到數(shù)據(jù)庫中。所以最后在數(shù)據(jù)庫看到的數(shù)據(jù)是亂的。

顯然,導(dǎo)致亂碼的根本原因就是OkHttp在最初使用了錯(cuò)誤的解碼方式進(jìn)行解碼。所以要解決這個(gè)問題,就要讓OkHttp知道網(wǎng)頁的編碼類型,進(jìn)行正確的解碼。

網(wǎng)頁有兩種約定的方式告訴爬蟲自己使用的是什么編碼方式:
1. Http協(xié)議的響應(yīng)頭中的約定:
Content-Type: text/html;charset=utf-8
2. Html中meta標(biāo)簽中的約定:
charset=UTF-8“/>
從約定中獲取網(wǎng)頁的編碼后,Okhttp就可以正確的解碼了。然而實(shí)際情況卻并不樂觀,很多網(wǎng)頁并不遵守約定,缺少這兩個(gè)信息。有人通過Alexa統(tǒng)計(jì)各國遵守這個(gè)約定的網(wǎng)頁數(shù):
語言URL后綴URL數(shù)HTTP頭中包含
charset的URL數(shù)
Chinese.cn100863776
English.us/.uk2156513223
Russian.ru3945328257
Japanese.jp203396833
Arabic.iq19041093
German.de3531823225
Persian.ir73964018
Indian.in122364867
Totalall14829785292
結(jié)果表明我們不能被動(dòng)的依賴網(wǎng)頁告訴我們,而要根據(jù)網(wǎng)頁內(nèi)容來主動(dòng)探測其編碼類型。
探測字符編碼
什么是字符編碼自動(dòng)檢測?
它是指當(dāng)面對一串不知道編碼信息的字節(jié)流的時(shí)候,嘗試著確定一種編碼方式以使我們能夠讀懂其中的文本內(nèi)容。它就像我們沒有解密鑰匙的時(shí)候,嘗試破解出編碼。
那不是不可能的嗎?
通常來說,是的,不可能。但是,有一些編碼方式為特定的語言做了優(yōu)化,而語言并非隨機(jī)存在的。有一些字符序列在某種語言中總是會(huì)出現(xiàn),而其他一些序列對該語言來說則毫無意義。一個(gè)熟練掌握英語的人翻開報(bào)紙,然后發(fā)現(xiàn)“txzqJv 2!dasd0a QqdKjvz”這樣一些序列,他會(huì)馬上意識到這不是英語(即使它完全由英語中的字母組成)。通過研究許多具有“代表性(typical)”的文本,計(jì)算機(jī)算法可以模擬人的這種對語言的感知,并且對一段文本的語言做出啟發(fā)性的猜測。換句話說就是,檢測編碼信息就是檢測語言的類型,并輔之一些額外信息,比如每種語言通常會(huì)使用哪些編碼方式。
這樣的算法存在嗎?
結(jié)果證明,是的,它存在。所有主流的瀏覽器都有字符編碼自動(dòng)檢測的功能,因?yàn)榛ヂ?lián)網(wǎng)上總是充斥著大量缺乏編碼信息的頁面。Mozilla Firefox包含有一個(gè)自動(dòng)檢測字符編碼的庫,已經(jīng)移植到Python中,叫做chardet。
chardet使用
安裝:
pip install chardet
使用:
>>> import urllib
>>> rawdata = urllib.urlopen('http://www.jd.com/').read()
>>> import chardet
>>> chardet.detect(rawdata)
{'confidence': 0.98999999999999999, 'language': '', 'encoding': 'utf-8'}
注意:返回結(jié)果中有confidence,即置信度,這說明探測結(jié)果不是100%準(zhǔn)確的。
使用其他語言的小伙伴不用擔(dān)心,chardet在很多語言中都有移植版。不過C++好像沒有太好的選擇,可以考慮使用IBM的ICU(http://site.icu-project.org/)。
擴(kuò)展閱讀
《A composite approach to language/encoding detection》
(https://www-archive.mozilla.org/projects/intl/UniversalCharsetDetection.html)
這篇論文解釋了Chardet背后使用的探測算法,分別是“編碼模式方法”、“字符分布方法”和“雙字符序列分布方法”。最后說明了三種方法組合使用的必要性,并舉例說明如何組合使用。
《Charset Encoding Detection of HTML Documents A Practical Experience》
利用現(xiàn)有的探測技術(shù),通過一些技巧來提高探測的準(zhǔn)確性。主要原理是組合使用Mozilla CharDet和IBM ICU,并在探測前巧妙的去掉了HTML標(biāo)簽。雖然這是伊朗大學(xué)發(fā)的Paper,但據(jù)說這種方法已經(jīng)在生產(chǎn)環(huán)境取得了很好的效果,目前正應(yīng)用在一個(gè)10億級別數(shù)據(jù)量的大型爬蟲上。
下一步
最近聊的話題越來越沉重,想必大家也累了。下期打算帶大家一起放松一下,聊點(diǎn)輕松的話題。從系列的開篇到現(xiàn)在也有半年了,技術(shù)領(lǐng)域有了不小的更新,出現(xiàn)了一些好用的工具,我們需要替換哪些工具呢?請聽下回分解!