tuohai666

[原创]手把手教你写网络爬虫(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亿级别数据量的大型爬虫上。

 

下一步

最近聊的话题越来越沉重,想必大家也累了。下期打算带大家一起放松一下,聊点轻松的话题。从系列的开篇到现在也有半年了,技术领域有了不小的更新,出现了一些好用的工具,我们需要替换哪些工具呢?请听下回分解!

 

分类:

技术点:

相关文章: