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


 

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

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

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

封面:

 

字符編解碼是爬蟲里必學的一項知識,在我們的爬蟲生涯中早晚會爬到亂碼的網頁,與其遇到時驚慌失措,不如早學早好,徹底避免亂碼問題。

 

字符編碼簡介

什么是字符集

在介紹字符編碼之前,我們先了解下什么是字符集。

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

什么是字符編碼

字符編碼和字符集不同。字符集只是字符的集合,無法進行網絡傳送、處理,必須經編碼后才能使用。如Unicode字符集可依不同需求以UTF-8、UTF-16、UTF-32等方式編碼。

字符編碼就是以二進制的數字來對應字符集的字符。各個國家和地區在制定編碼標准的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。

常用字符集

簡單介紹幾個常見的。

ASCII:

ASCII是學計算機同學的啟蒙字符集,一般是從這本書里學到的:

 

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

 

 

中文字符集:

GB2312:包含6763個漢字。

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

GB18030:收錄了70000個漢字,這么多是因為包含了少數民族文字。同樣兼容GBK和GB2312。

Unicode:Unicode 是為了解決傳統的字符編碼方案的局限而產生的,它為每種語言中的每個字符設定了統一並且唯一的二進制編碼,以滿足跨語言、跨平台進行文本轉換、處理的要求。具有多種編碼方式,如UTF-7、 UTF-8、UTF-16、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億級別數據量的大型爬蟲上。

 

下一步

最近聊的話題越來越沉重,想必大家也累了。下期打算帶大家一起放松一下,聊點輕松的話題。從系列的開篇到現在也有半年了,技術領域有了不小的更新,出現了一些好用的工具,我們需要替換哪些工具呢?請聽下回分解!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM