Stateful crawler in Node.js
2014-08-07

之前的一篇文章介绍了怎么quick and dirty地捣鼓出一个网页爬虫,但距离一个成熟的爬虫还有一段路要走。本文就来填补这个空白。

网页爬虫一旦踏上征程,路途一般都坎坷而漫长,比如要抓取整个网站的内容,可能要好几天才能抓完。大一点的网站,抓取网页的过程中遇到意外中断几乎是必然的,例如各种各样的页面无法打开。这就要求网络爬虫能从意外中断中恢复过来,想要恢复则必须随时记下状态,即所谓的stateful。

状态记在哪里呢?最简单的方法就是用文件。抓取页面的工作我使用了curl这件神器,curl有个好处是可以在运行时指定一个配置文件。配置文件中可指定要抓取的页面地址,以及抓取下来将要保存成什么文件名等等。抓取一个页面所需的全部信息,都包含在配置文件中了,这样一来,每一个页面的抓取任务都对应着一个配置文件。于是,抓取任务的调度,就可以用文件的流转来表示了。

上图中的方框都是表示文件夹,蓝色的用来放curl配置文件,我们上面所说的文件流转,就是在这几个目录之间流转。所以先来关注这一部分。

前面说过,每一个页面的抓取任务都对应着一个curl配置文件,要抓取多少个页面,就对应在origin目录下生成多少个配置文件。生成这些配置文件当然是用程序来做:

$ node create.js 1 100

上面这行命令用来生成100个curl配置文件,用来抓取从第1页到第100页的数据。执行后,将在origin目录里看见这100个文件。爬不同的网站,URL自然不同,所以create.js这个文件需要随不同的业务而重写。这块完全是交给使用者来实现的,目的只有一个,在origin目录里创建好所有任务的配置文件。

下一步就是执行任务:

$ node crawl.js

一个任务的生命周期是这样的(结合上面的图):

  1. 任务的curl配置文件从origin目录移到working目录,调用curl根据此配置文件去下载页面
  2. 若下载成功,页面保存至html目录,若下载失败,配置文件移动到error目录
  3. 解析html目录里的html文件,解析模块parser.js是用户自己来写的
  4. 若解析成功,在parsed目录下生成解析后的文件,并且将配置文件从working目录移动到success目录;若解析失败,配置文件移动到error目录。

重试机制

上面的流程,无论下载和解析成功与否,都会继续执行后面的任务,直到执行至最后一个。全部执行完后,error目录下可能有一些配置文件,它们是执行失败的任务。这毕竟还属于正常的失败。如果出现中间意外中断的极端情况,那么在working目录下会残留正在执行的任务的配置文件,当然origin目录里也会有还未执行过的任务。

因此要重试的话,origin, working, error三个目录下的任务都要重试。

如果一个任务反复几次都error了,有两种可能,一个是下载失败,最有可能是网址本身就打不开。另一种可能是下载成功了但解析页面失败,找到下好的html文件分析一下不难查清原因。

并发连接数

把并发连接数单拎出来说,是怕和线程数搞混。本爬虫实现多任务的方法是同时调用多个curl命令,每一个curl命令都是一个进程,所以是多进程而非多线程。多进程其实也不是必要的,用多进程的原因是使用curl所限。就算是单进程单线程,也很容易实现多连接并发。

简单说一下本爬虫多连接并发的机制,这块代码在crawl.js文件里。首先有一个连接池,说是连接池纯是为方便理解,因为这个连接池里不是真正的socket,而仅仅是计数用的。每开始执行一个curl命令,就占用一个连接池中的连接,直到达到最大连接数。每执行完一条curl命令,就释放一个连接,腾出来的这个连接可以分配给下一个任务,直到没有下一个任务可分配。

并发连接数可在执行crawl.js的时候指定参数,例如下面命令指定最大并发连接数是10(不指定参数默认是3):

$ node crawl.js 10

Node.js到底起了什么作用

本文的标题是Stateful crawler in Node.js,相信有读者是冲着Node.js来的。Node.js在这里到底起了什么作用呢?答案是,毛用都没有。仅仅是因为我正在学Node.js,拿它练练手而已(源码在此)。这个爬虫程序用别的语言也能写,体现不出Node.js的优势。不过说实话,Node.js带来的便利还是超出了我的预期,API调起来简单直接,尤其值得称道的是html解析的模块cheerio,让我可以用jquery选择器的写法来定位dom元素,非常顺手。

代码量方面没有因为使用JS而膨胀,实现这个爬虫总共不到200行代码,所以你如果觉得不爽,自己重写一个也不费劲。基于此改一个适用于其他网站的爬虫也很方便,一般只改三五十行就够了。需要改动的业务相关的源文件只有两个,一个是用来生成任务配置文件的create.js,另一个是解析页面的parser.js,见下图:

source code on GitHub

END