有关爬虫框架的设计的一些备忘

我们公司一直在使用自己设计的爬虫框架,这么做的初衷是让框架保持简单,新手可以很快写一个爬虫工作。但是也遇到过许许多多的问题。这里记一下,如果要重新写一个爬虫框架的话,这些问题必须好好处理,可以少走很多弯路。

1.任务队列

爬虫肯定是要用到任务队列的。将每个页面的抓取分成任务,可以让任务之间互不影响,方便监控,也方便调度。任务队列是个很大的话题,我们用的是beanstalkd,它轻量,速度快,但是也有一些缺点。我对任务队列没有什么研究,这里就不比较各个方案了。但是爬虫需要的任务队列要求如下:

  1. 必须能够快速切换。一个任务就是一个网络请求+解析结果。这样划分比较合理,可以看到对某个页面的抓取状态。这就要求任务队列的吞吐量很高,要频繁的放、取任务。我们每天的抓取任务在千万级别。
  2. 支持分组(tube)。这样就可以对不同的爬虫任务循环执行。单队列可能造成对抓去网站DoS攻击,很不友好。
  3. 支持优先级。
  4. 支持重试,延迟重试。但不是必须,这个可以在爬虫队列实现。

2.数据的去重

如果没有考虑到数据去重,那么每一次执行爬虫任务都会带来新数据,这肯定是不合理的,所以爬虫每次运行都要考虑到用某种方式判定是否是一条已经存在的数据。

比较通用的方法是通过hash来判定重复。我们就是用的这种方案:对每一条数据挑一些字段(全文hash很危险,经常造成数据重复。比如有一个字段变化了,但其实应该作为一条数据处理)来计算hash值,存入数据库。在数据库中插入的时候根据hash值是否存在。

这种方案存在几个问题:

  1. hash碰撞。如果手动实现处理hash碰撞的情况,又是一个复杂的过程。目前我们是没有考虑这个的,因为我们的每一个目标网站数据量不是很多,对每一个目标网站使用一个collection存放,就大胆地假设了数据不会存在hash碰撞的情况。但是这可能是一个隐患。可以看一下这个项目,很有意思,可以对两个完全不同的pdf修改让它们的sha1结果一模一样但是不改变显示内容。
  2. 对数据库的压力。爬虫集群如果规模很大,那么意味着每一次爬虫任务都会去查询数据库,我们的Mongo集群经历过多次压力过大的问题。

其实我觉得,比较合理的方式是建立一种“爬虫任务–页面–数据”之间的关系。根据这种关系来去重,比如如果在抓取页面的时候就能判断过这个页面已经被处理过。那么就可以认为页面上的数据已经存在,可以节省很多操作。

这就需要下面下载与处理的分离。

3.下载与处理分离

这里想说的是,将下载和处理页面解耦。这样做的好处是你可以部署一个网络比较好或者有特殊要求的(比如国外ip,国内ip)集群来专门下载网页,然后用一些配置高的机器用来处理网页,这可能会节省一些vps租金。因为众所周知,爬虫比较耗费的资源是网络请求和ip。这样做的缺点是会增加框架的复杂程度。

此外,还有一个好处是上面提到的,下载好的页面可以做简单的判断,如果是已经处理过的页面并且没有发生更新,那么就直接结束这个任务。如果是个新的页面,就要交给后面的某个处理程序继续处理,像一个管道一样。

而且,还可以对下载步骤(或处理步骤)做一些Hook,如果我想保存下载所有的下载过的页面,就可以在下载完成的时候,将页面同时交给一个线程来上传到S3 bucket,同时交给一个线程做抽取任务。

这种解耦对监控来说也应该方便很多。

4.监控

需要监控的地方:

  1. 任务队列中的状态
  2. 每个任务的执行情况,比如异常等
  3. 数据的增长
  4. 追踪每个任务执行带来的数据

最好监控到每一次任务执行的参数,比如从任务队列中获得的任务信息,执行时间等。以及任务失败时候的异常,这样发现爬虫挂掉就很方便。另外需要特别注意的是,任务没有异常抛出,但实际上一次任务没有带来数据。就好比程序实际是错的,但是实际跑一次并不会挂掉。这种情况很难避免,即使你处理异常的时候非常小心,也有可能捕获了不想捕获的异常,唯有从监控上下功夫。最好是将任务执行和数据存储关联起来。在存数据的时候,记录这条数据是哪一个任务带来的。如果一次任务没有带来数据,也应该有警告。当然如果目标就是抓取一个列表页面,提取url,没有数据是正常的,这种情况应该可以被过滤掉。总之重点是,一个任务应该和进来的数据有关联,可以查看一个任务带来多少数据,亦可以查看某条数据是由哪个任务带来的。

第1条和第3条可以使用Grafana等组合Metric来实现。

目前来看,ElasticSearch用来做日志分析挺不错的。其他没有使用过。

暂时就想到这个,最近经常会有一个动手写个框架的冲动。以上只是个人的经验和想法,并不一定是合适的方案,欢迎讨论。

有关爬虫框架的设计的一些备忘”已经有8条评论

  1. 本人虽只写轻量级的小爬虫,但也能看这个重量级大爬虫的运作流程很清晰,似乎也能了解框架对于繁重抓取任务的必要性。但我好奇的是,如果阁下打算动手写个框架的话,是否会考虑轻量级的需求及其实现呢?比如每天分几次只抓取千百个网站的这种需求?

    • 我觉得是有必要的,无论抓取数据多少,保存数据、监控、任务队列这些都必不可少。一个优秀的框架(虽然我现在能力不够写出这样优秀的框架)应该方便扩展,比如从抓几百个页面,只用一台服务器到要抓上亿页面,需要几百个服务器集群,都应该能胜任。

  2. 鄙人对框架的了解近乎无知, 大概理解为一种只给骨骼没肌肉的东西。但如果本人是轻量级需求方面潜在的用户,可能会要求此框架不能过多地依赖于其他软件包,否则就模糊了主次;也可能会要求某些模块能被禁用,比如简单的任务一次成功后就没必要再花额外的资源去监控, 否则就颠倒了轻重。对此, 若阁下考虑前者的需求, 势必要限制框架的功能, 而考虑后者, 又势必要影响框架的基础。这样的矛盾,是不是类似于本人需要一条狗来跑腿,而阁下却提供了恐龙的骨架给我呢?

    • 这倒是的,简单的爬虫其实可以直接用requests写的,等到业务逐渐复杂,需求很多,就可以考虑迁移到一个框架了,会发现你需要的功能框架都帮你做好了。不然你都自己写,到头来回发现自己造了个轮子。

      框架的大小方便,比如说flask和django,flask就比较小巧,只有核心,想要什么别的功能都要寻找插件。django就比较重量,什么都有。不同框架的设计哲学不同吧。

  3. 之前为了公司需求曾构建过一个简单的分布式爬虫框架,跟你的思路基本一致,抓取和处理分离、抓取后的页面发布到oss中,然后有专门的消费者进行抽取,中间通讯依赖的消息队列。感觉问题最大的反倒是监控方面,需求是做到每个请求的状态监控,尝试了很多次都不是很满意,而且对最终抓取可靠性也没有很好的评价指标,想请教下关于分布式爬虫的监控有什么好的解决方案么?

    • 可以看一下kibana,用Elasticsearch做日志存储,然后用kibana可视化,画出饼图或者曲线图。爬虫发日志频率比较大,所以sentry这种怕撑不住,Elasticsearch应该没问题。其余一些指标之类的metric可以用grafana统计,参考下这个:https://github.com/kamon-io/docker-grafana-graphite

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注