上一节介绍了如何获取单个页面内容。在实际项目中,则使用爬虫程序遍历互联网,把网络中相关的网页全部抓取过来,这也体现了爬虫程序“爬”的概念。爬虫程序是如何遍历互联网,把网页全部抓取下来的呢?互联网可以看成一个超级大的“图”,而每个页面可以看作是一个“节点”。页面中的链接可以看成是图的“有向边”。因此,能够通过图的遍历的方式对互联网这个超级大“图”进行访问。图的遍历通常可以分为宽度优先遍历和深度优先遍历两种方式。但是深度优先遍历可能会在深度上过“深”地遍历或者陷入“黑洞”,大多数爬虫都不采用这种方式。另一方面,在爬取的时候,有时候也不能完全按照宽度优先遍历的方式,而是给待遍历的网页赋予一定的优先级,根据这个优先级进行遍历,这种方法称为带偏好的遍历。
1 图的宽度优先遍历
图的宽度优先遍历(BFS)算法是一个分层搜索的过程,和树的层序遍历算法相同。在图中选中一个节点,作为起始节点,然后按照层次遍历的方式,一层一层地进行访问。
图的宽度优先遍历需要一个队列作为保存当前节点的子节点的数据结构。具体算法如下所示:
(1) 定点V入队列。
(2) 当队列非空时继续执行,否则算法为空。
(3) 出队列,获取队列节点V,访问顶点V并标记V已经被访问。
(4) 查找顶点V的第一个邻接顶点col。
(5) 若V的邻接顶点col未被访问过,则col进队列。
(6) 继续查找V的其他邻接顶点co,l转到步骤(5),若V的所有邻接顶点都已经被访问过,则转到步骤(2)。
下面以图的方式介绍宽度优先遍历的过程,如下图。
选择A作为种子节点,则宽度优先遍历的过程。如下表所示:
操作 | 队列中的元素 |
初始 | 空 |
A入列 | A |
A出列 | 空 |
BCDEF入队列 | BCDEF |
B出队列 | CDEF |
C出队列 | DEF |
D出队列 | EF |
E出队列 | F |
H入队列 | FH |
F出队列 | H |
G入队列 | HG |
H出队列 | G |
I入队列 | GI |
G出队列 | I |
I出队列 | 空 |
在上表中所示的遍历过程中,出队列的节点顺序既是图的宽度优先遍历的访问顺序。由此可以看出,上图所示的宽度优先遍历的访问顺序为:A->B->C->D->E->F->H->G->I
本小节讲述了宽度优先遍历的理论基础,把互联网看成一个“超图”,则对这张图也可以采用宽度优先遍历的方式进行访问。下面将着重讲解如何对互联网进行宽度优先遍历。
2 宽度优先遍历互联网
上一节介绍的宽度优先遍历是从一个种子节点开始的。而实际的爬虫项目是从一系列的种子链接开始的。所谓种子链接,就好比宽度优先遍历中的种子节点(上图中A节点)一样。实际的爬虫项目中种子链接可以有多个,而宽度优先遍历中的种子节点只有一个。比如,可以指定www.leitu.com和www.sina.com两个种子链接。
如何定义一个链接的子节点?每个链接对应一个HTML页面或者其他文件(word、excel、paf、jpg等),在这些文件中,只有HTML页面有相应的“子节点”,这些“子节点”就是HTML页面上对应的超链接。如www.lietu.com页面中,“招聘”、“网址”、“更多”以及页面下方的“搜索产品”,“技术文档”,“成功案例”,“猎兔新闻”,“联系猎兔”,“关于我们”,ENGLISH等都是www.lietu.com的子节点。这些子节点本身又是一个链接。对于非HTML文档,比如excel文件等,不能从中提取超链接,因此,可以看作是图的“终端”节点。就好像上图中B、C、D、I、G等节点一样。
整个的宽度优先爬虫就是一系列的种子节点开始,把这些网页中的“子节点”(也就是超链接)提取出来,放入队列中以此进行抓取。被处理过的链接需要放入一张表(通常称为Visited表)中。每次新处理一个链接之前,需要查看这个链接是否已经存在于Visited表中。如果存在,证明链接已经处理过,跳过,不作处理,否则进行下一步处理。实际的过程如下图所示:
TODO表 | Visited表 |
A | 空 |
BCDEF | A |
CDEF | A,B |
DEF | A,B,C |
EF | A,B,C,D |
FH | A,B,C,D,E |
HG | A,B,C,D,E,F |
GI | A,B,C,D,E,F,H |
I | A,B,C,D,E,F,H,G |
空 | A,B,C,D,E,F,H,G,I |
- 重要的网页往往离种子比较近,例如我们打开新闻网站的时候往往是最热门的新闻,随着不断的深入冲浪,所看到的网页的重要性越来越第。
- 万维网的实际深度最多能达到17层,但到达某个网页总存在一条很短的路径。而宽度优先遍历会以最快的速度到达这个网页。
- 宽度优先有利于多爬虫的合作抓取,多爬虫合作通常先抓取站内链接,抓取的封闭性很强。