[原創]手把手教你寫網絡爬蟲(8):完全解決亂碼問題

 

手把手教你寫網絡爬蟲(8)html

做者:拓海 (https://github.com/tuohai666)java

摘要:從零開始寫爬蟲,初學者的速成指南!git

封面:github

 

字符編解碼是爬蟲裏必學的一項知識,在咱們的爬蟲生涯中遲早會爬到亂碼的網頁,與其遇到時惶恐不安,不如早學早好,完全避免亂碼問題。算法

 

字符編碼簡介

什麼是字符集數據庫

在介紹字符編碼以前,咱們先了解下什麼是字符集。瀏覽器

字符(Character)是各類文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。字符集(Character set)是多個字符的集合,字符集種類較多,每一個字符集包含的字符個數不一樣,常見字符集:ASCII字符集、GBK字符集、Unicode字符集等。網絡

什麼是字符編碼app

字符編碼和字符集不一樣。字符集只是字符的集合,沒法進行網絡傳送、處理,必須經編碼後才能使用。如Unicode字符集可依不一樣需求以UTF-八、UTF-1六、UTF-32等方式編碼。less

字符編碼就是以二進制的數字來對應字符集的字符。各個國家和地區在制定編碼標準的時候,「字符的集合」和「編碼」通常都是同時制定的。所以,日常咱們所說的「字符集」,除了有「字符的集合」這層含義外,同時也包含了「編碼」的含義。

經常使用字符集

簡單介紹幾個常見的。

ASCII:

ASCII是學計算機同窗的啓蒙字符集,通常是從這本書裏學到的:

 

請容許我懷舊一下,如下引用譚浩強老師的講解:

 

 

中文字符集:

GB2312:包含6763個漢字。

GBK:包含21003個漢字。GBK兼容GB2312,也就是說用GB2312編碼的漢字能夠用GBK來解碼。

GB18030:收錄了70000個漢字,這麼可能是由於包含了少數民族文字。一樣兼容GBK和GB2312。

Unicode:Unicode 是爲了解決傳統的字符編碼方案的侷限而產生的,它爲每種語言中的每一個字符設定了統一而且惟一的二進制編碼,以知足跨語言、跨平臺進行文本轉換、處理的要求。具備多種編碼方式,如UTF-七、 UTF-八、UTF-1六、UTF-32等。

 

爲何會產生亂碼

簡單的說亂碼的出現是由於:編碼和解碼時用了不一樣的字符集。對應到真實生活中,就比如是一個英國人爲了表示祝福在紙上寫了bless(編碼)。而一個法國人拿到了這張紙,因爲在法語中bless表示受傷的意思,因此認爲他想表達的是受傷(解碼)。同理,在計算機中,一個用UTF-8編碼後的字符,用GBK去解碼。因爲兩個字符集的字庫表不同,同一個漢字在兩個字符表的位置也不一樣,最終就會出現亂碼。

那麼,爬蟲中的亂碼是怎麼產生的,又該如何解決呢?

 

爬蟲中的亂碼

假設咱們的爬蟲是java開發的,網絡請求庫使用OkHttp,網頁存儲到MongoDB中。亂碼產生的過程以下:

  1. OkHttp請求指定url,返回了一個GBK編碼的網頁字節流;
  2. OkHttp以默認UTF-8進行解碼(此時已亂),並以UTF-16方式編碼爲Java的String類型,返回給處理程序。(爲何以UTF-16方式編碼?由於Java的數據在內存中的編碼是UTF-16);
  3. 爬蟲拿到這個編碼錯誤的String類型的網頁,調用MongoDB的API,將數據編碼爲UTF-8存儲到數據庫中。因此最後在數據庫看到的數據是亂的。

 

 

顯然,致使亂碼的根本緣由就是OkHttp在最初使用了錯誤的解碼方式進行解碼。因此要解決這個問題,就要讓OkHttp知道網頁的編碼類型,進行正確的解碼。

 

 

網頁有兩種約定的方式告訴爬蟲本身使用的是什麼編碼方式:

1. Http協議的響應頭中的約定:

  Content-Type: text/html;charset=utf-8

2. Html中meta標籤中的約定:

  <meta http-equiv=」Content-Type」 content=」text/html; charset=UTF-8「/>

從約定中獲取網頁的編碼後,Okhttp就能夠正確的解碼了。然而實際狀況卻並不樂觀,不少網頁並不遵照約定,缺乏這兩個信息。有人經過Alexa統計各國遵照這個約定的網頁數:

語言

URL後綴

URL

HTTP頭中包含

charset的URL數

Chinese

.cn

10086

3776

English

.us/.uk

21565

13223

Russian

.ru

39453

28257

Japanese

.jp

20339

6833

Arabic

.iq

1904

1093

German

.de

35318

23225

Persian

.ir

7396

4018

Indian

.in

12236

4867

Total

all

148297

85292

結果代表咱們不能被動的依賴網頁告訴咱們,而要根據網頁內容來主動探測其編碼類型。

 

探測字符編碼

什麼是字符編碼自動檢測?

它是指當面對一串不知道編碼信息的字節流的時候,嘗試着肯定一種編碼方式以使咱們可以讀懂其中的文本內容。它就像咱們沒有解密鑰匙的時候,嘗試破解出編碼。

那不是不可能的嗎?

一般來講,是的,不可能。可是,有一些編碼方式爲特定的語言作了優化,而語言並不是隨機存在的。有一些字符序列在某種語言中老是會出現,而其餘一些序列對該語言來講則毫無心義。一個熟練掌握英語的人翻開報紙,而後發現「txzqJv 2!dasd0a QqdKjvz」這樣一些序列,他會立刻意識到這不是英語(即便它徹底由英語中的字母組成)。經過研究許多具備「表明性(typical)」的文本,計算機算法能夠模擬人的這種對語言的感知,而且對一段文本的語言作出啓發性的猜想。換句話說就是,檢測編碼信息就是檢測語言的類型,並輔之一些額外信息,好比每種語言一般會使用哪些編碼方式。

這樣的算法存在嗎?

結果證實,是的,它存在。全部主流的瀏覽器都有字符編碼自動檢測的功能,由於互聯網上老是充斥着大量缺少編碼信息的頁面。Mozilla Firefox包含有一個自動檢測字符編碼的庫,已經移植到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'}

注意:返回結果中有confidence,即置信度,這說明探測結果不是100%準確的。

使用其餘語言的小夥伴不用擔憂,chardet在不少語言中都有移植版。不過C++好像沒有太好的選擇,能夠考慮使用IBM的ICU(http://site.icu-project.org/)。

 

擴展閱讀

《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》

(https://github.com/shabanali-faghani/IUST-HTMLCharDet/blob/master/wiki/Charset-Encoding-Detection-of-HTML-Documents.pdf)

利用現有的探測技術,經過一些技巧來提升探測的準確性。主要原理是組合使用Mozilla CharDet和IBM ICU,並在探測前巧妙的去掉了HTML標籤。雖然這是伊朗大學發的Paper,但聽說這種方法已經在生產環境取得了很好的效果,目前正應用在一個10億級別數據量的大型爬蟲上。

 

下一步

最近聊的話題愈來愈沉重,想必你們也累了。下期打算帶你們一塊兒放鬆一下,聊點輕鬆的話題。從系列的開篇到如今也有半年了,技術領域有了不小的更新,出現了一些好用的工具,咱們須要替換哪些工具呢?請聽下回分解!

相關文章
相關標籤/搜索