爬虫系列:读取文档
上一篇文章我们介绍了如何通过 MySQL 存储 Python 爬虫采集的内容,以及使用Python 与 MySQL 交互,这篇文章我们介绍如何通过 Python 读取文档。
虽然互联网在20世纪60年代末期就已经以不同的形式出现,但是 HTML 直到1992年才问世。在此之前,互联网上基本就是收发邮件传输文件;今天看到的网页概念那时还没有。总之,互联网并不是一个 HTML 页面的集合。他是一个信息集合,而 HTML 文件只是展示信息的一个框架而已。如果我们的爬虫不能读取其他类型的文件,包括纯文本、PDF、图像、视频、邮件等,我们将会失去很大一部分数据。
本篇文章我将详细介绍文档处理的相关内容,包括把文件下载到文件夹里,以及读取文档并提取数据。同时介绍文档不同编码类型,让程序可以读取非英文 HTML 页面。
文档编码
文档编码是一种告诉程序——无论是计算机的操作系统还是 Python 代码——读取文档的规则。文档的编码方式通常可以根据文件的扩展名进行判断,虽然文件扩展名并不是由编码确定的,而是由开发者确定的。例如,如果我把 python_logo.jpg 存储为 python_logo.txt 不会出现任何问题,但当我使用文本编辑器打开的时候就有问题了。这种情况很少见,如果要正确的读取一个文档,必须知道它的扩展名。
从最底层的角度看,所有文档都是由0和1编码而成的。而在高层(贴近用户的层级)编码算法会定义“每个字符多少位”或“每个像素的颜色值用多少位”(图像文件里)之类的事情,在哪里你会遇到一些数据压缩算法或体积缩减算法,比如 PNG 图像编码格式(一种无损压缩的位图图形格式)。
虽然我们第一次处理这些非 HTML 格式的文件会觉得没有任何经验,但是只要安装了合适的库,Python 就可以帮你处理任意类型的文档。纯文本文件、视频文件和图像文件的唯一区别,就是他们的0和1面向用户的转换方式不同。
纯文本
虽然把文件存储为在线的纯文本格式并不常见,但是一些简易的网站,或者有大量纯文本文件的“旧式学术”(old-shcool)网站经常会这么做。例如,互联网工程任务组(Internet Engineering Task Force,IETF)网站就存储了 IETF 发表过的所有文档,包含 HTML、PDF 和纯文本格式(例如 https://datatracker.ietf.org/doc/html/rfc4437.txt)。大多数浏览器都可以很好的显示纯文本文件,采集这些纯文本文件的网站不会遇到什么问题。
下面一个 Python 读取纯文本示例,展示了如何读取 https://image.pdflibr.com/crawler/blog/tencent_cloud_ip_range.txt 地址的纯文本文件。
from requests import Session
class ReadDocument(object):
def __init__(self):
self._text_url = 'https://image.pdflibr.com/crawler/blog/tencent_cloud_ip_range.txt'
def read_text_document(self):
init_session = Session()
response = init_session.get(url=self._text_url)
response.encoding = 'utf-8'
print(response.text)
if __name__ == '__main__':
ReadDocument().read_text_document()
这段 Python 代码,我们直接读取文本内容,并对文本从新编码,如果使用原来的编码方式,显示为乱码。一旦纯文本被读取成字符串,你就只能用普通的 Python 字符串方法分析他了。当然这没做有个缺点,就是你不能对字符串使用 HTML 标签,去定位那些你真正需要的文字,避开那些你不需要的文字。如果你现在需要在纯文本里面找到你需要的信息还是有困难的。
文本编码和全球互联网
记得我前面说过,如果你想正确的读取一个文件,知道它的扩展名就可以了。不过非常奇怪的是,这条规则不能应用到最基本的文档格式:.txt 文件。
大多数时候前面的方法读取纯文本文件都没有问题。但是,护粮网上的文本文件会比较复杂。下面介绍一些英文和非英文编码的基础知识,包括 ASCII、Unicode 和 ISO 编码,以及应对的处理方法。
编码类型简介
20世纪90年代,一个叫 Unicode 联盟(The Unicode Consortium)的非盈利组织尝试将地球所有的用于书写的符号经行统一编码。其目标包括拉丁字母、斯拉夫字母、中国象形文字(象形)、数字和逻辑符号,甚至表情和“杂项”(misellaneous)符号,比如生化危机标记(☣)和和平符号(☮)等。
UTF-8(8-bit Unicode Transformation Format) 是一种针对 Unicode 的可变长度字符编码,也是一种前缀码。它可以用一至四个字节对 Unicode 字符集中的所有有效编码点进行编码,属于U nicode 标准的一部分,最初由肯·汤普逊和罗布·派克提出。
一个最常见的错误就是 UTF-8 把所有的字符都存储成8位。其实“8位”显示一个字符所需要的最小位数,而不是最大位数。(如果 UTF-8 的每个字符都是8位,,那一共只能存储2^8个字符,这对于中文和其他字符显然不够。)
真实情况是,UTF-8每个字符开头都有一个标记表示“这个字符只用一个字节”或“那个字符需要两个字节”,一个字符最多可以是四个字节。由于这四个字节里面还包含一部分设置信息,用来决定多少字节用来做字符编码,所以全部的32位(32位=4字节x8位/字节)并不都会用,其实最多使用21位,也就是总共2097152种可能里面可以有1114112个字符。
虽然对很多程序来说,Unicode 都是上帝的礼物(godsend),但是有很多习惯都很难改变, ASCII 依然是许多英文用户的主要选择。
ASCII 是20世纪60年代开始使用的文字编码标准,每个字符7位,一共2^7,即128个字符。这个对于拉丁字母(包括大小写)、标点符号和英文键盘上的所有符号,都是够用的。
在20世纪60年代,存储的文件用7位编码和用8位编码之间的差异是巨大的,因为内存非常昂贵。当时,计算机科学家们为了需要增加一位获得一个漂亮的二进制数(用8位),还是在文件里用更少的位数(7位)费尽心机。最终,7位编码胜利了。但是,在新式计算方式中,每个7位码前面都补充(pad)了一个“0”,留给我们最坏的结果是,文件大了14%(编码由7为变成8位,体积增加了14%),并且由于只有128个字符,缺乏灵活性。
在 UTF-8 设计过程中,设计师决定利用 ASCII 文档里的“填充位”,让所有“0”开头的字节表示这个字符自用1个字节,从而把 ASCII 和 UTF-8 编码完美的结合在一起。因此,下面的字符在 ASCII 和 UTF-8 两种编码方式中都是有效的:
01000001 - A
01000010 - B
01000011 - C
除了 UTF-8,还有其他 UTF 标准,像 UTF-16、UTF-24、UTF-32,不过很少用这些编码标准对文件经行编码,所以在此不做过多介绍。
Python 编码示例
在上面的实例中我们通过 Python 的 requests 库读取了远程的文档内容,但是显示的是乱码,无法阅读,我们对文档内容重新设置编码,使其正常显示,示例如下:
from requests import Session
class ReadDocument(object):
def __init__(self):
self._text_url = 'https://image.pdflibr.com/crawler/blog/tencent_cloud_ip_range.txt'
def read_text_document(self):
init_session = Session()
response = init_session.get(url=self._text_url)
# 显示原来文本的编码方式
print(response.encoding)
# 将文本设置成 utf-8 的编码方式
response.encoding = 'utf-8'
print(response.text)
# 显示改变编码后的编码方式
print(response.encoding)
if __name__ == '__main__':
ReadDocument().read_text_document()
上面的代码首先打印出了原来文档采用的编码方式,之后将文档重设成 UTF-8 的编码方式,之后再次打印文档内容,再次显示被编码后的文档编码。
你可能打算以后使用网络爬虫全部采用 UTF-8 编码读取内容,毕竟 UTF-8 也可以完美的处理 ASCII 编码。但是,要记住还有9%的网站使用 ISO 编码格式。所以在处理纯文本文档的时候,想用一种编码搞定所有文档是不可能的。有一些库可以检查文档的编码,或是对文档编码经行估计,不过效果并不是很好。
处理 HTML 的时候,网站其实会在 <head>
部分显示页面使用的编码格式。大多数网站,尤其是英文网站,都会带上这样的标签:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
如果你要做很多网络数据采集工作,尤其是面对国际网站时,建议先看看 meta 标签的内容,用网站推荐的方式读取页面内容。