最近工作主要是一些爬虫相关的东西,由于公司需要构建自己的爬虫框架,在调研过程中参考了许多优秀的开源作品,包括webmagic,webcollector,Spiderman等,通过学习这些优秀的源码获益良多。

webmagic是一个简单灵活的爬虫框架。基于WebMagic,你可以快速开发出一个高效、易维护的爬虫。(官网地址:http://webmagic.io/)

本篇是webmagic源码阅读第一篇,主要探讨webmagic的核心机制,即一个BFS的爬虫是如何构建出来的。

webmagic分为以下四大组件,Downloader(页面下载器),Scheduler(下载调度器),PageProcessor(页面解析器),Pipeline(管道组件,通常做将抓取结果入库写文件等操作)

                          (图片来自官网)

 以上四个组件由Spider组件组装起来,爬取数据时协同工作。我们先研究webmagic的核心类Spider。

在Spider中的run()方法中可以清晰的看到典型的BFS代码,通过一个循环不断地从scheduler中的内存队列中取一个抓取任务(Request)并进行相应处理(processRequest),如果抓取成功则回调监听器中的onSuccess()方法,失败则调用onError()方法,最后将已抓取页面的数量自增。如果队列中没有任何抓取任务了,爬虫会在这里停一会防止有新的任务

加入(waitNewURL()),当然,这里的暂停时间是由你自己决定的。

    最后,如果等待一段时间后队列中仍没有请求,退出循环,将爬虫的状态改为停止并释放资源。

  1. /**
  2. * 爬虫的核心方法,广度优先遍历
  3. */
  4. @Override
  5. public void run() {
    //检查爬虫状态:初始化,抓取中,停止
  6. checkRunningStat();
  7. //初始化爬虫组件
  8. initComponent();
  9. logger.info("Spider " + getUUID() + " started!");
  10. //注意,这里的stat状态是一个CAS变量,保证了多线程访问的安全性
  11. //这里是一个BFS算法
  12. while (!Thread.currentThread().isInterrupted() && stat.get() == STAT_RUNNING) {
  13. final Request request = scheduler.poll(this);
  14. if (request == null) {
  15. if (threadPool.getThreadAlive() == 0 && exitWhenComplete) {
  16. break;
  17. }
  18. // wait until new url added
  19. //队列为空时等待一会以防有新URL加入
  20. waitNewUrl();
  21. } else {
  22. threadPool.execute(new Runnable() {
  23. @Override
  24. public void run() {
  25. try {
  26. //处理遍历到的request
  27. processRequest(request);
  28. //成功时回调我们注册的所有SpiderListener中的onSuccess()方法
  29. onSuccess(request);
  30. } catch (Exception e) {
  31. //失败时回调我们注册的所有SpiderListener中的onError()方法
  32. onError(request);
  33. logger.error("process request " + request + " error", e);
  34. } finally {
  35. //抓取总数自增,这里同样是一个CAS操作
  36. pageCount.incrementAndGet();
  37. signalNewUrl();
  38. }
  39. }
  40. });
  41. }
  42. }
  43. stat.set(STAT_STOPPED);
  44. // release some resources
  45. if (destroyWhenExit) {
  46. close();
  47. }
  48. }

  需要注意的是,这里无论是爬虫的状态变量检查还是最后的自增变量(pageCount)都是CAS操作,因为我们的大多数情况下都会为爬虫开多个线程(当然,你要确保你的

爬虫不会被网站封禁,而且最好也不要开过多线程,避免给对方服务器造成太大压力)。

这里的另一个核心方法是processRequest(见下图),对于从scheduler中取到的每个抓取请求,都会做如下操作:

1.页面下载:首先使用Downloader进行网页下载,获取网页对象Page,如果抓取内容为空,说明抓取出现错误,回调Listener中的onError方法并退出。

2.页面解析:接下来Spider会回调我们自己写的pageProcessor中的process方法,由于每个网页都有自己的特点,所以需要我们自己进行处理。

3.新URL抽取:如果事先定义了爬虫需要循环抓取(needCycleRetry)则从当前页面中抽取新的链接并放入调度队列中

4.数据入库/写文件:Spider回调我们注册的所有pipline,在pipline中我们通常会将结果诸如入库,写文件或简单输出到控制台(webmagic默认支持)。

  1. /**
  2. * 处理队列中的某个请求
  3. * @param request
  4. */
  5. protected void processRequest(Request request) {
  6. Page page = downloader.download(request, this);
  7. if (page == null) {
  8. sleep(site.getSleepTime());
  9. onError(request);
  10. return;
  11. }
  12. // for cycle retry
  13. if (page.isNeedCycleRetry()) {
  14. extractAndAddRequests(page, true);
  15. sleep(site.getRetrySleepTime());
  16. return;
  17. }
  18. //注意,在这里回调了我们自己写的process方法
  19. pageProcessor.process(page);
  20. //提取链接并放入调度队列中
  21. extractAndAddRequests(page, spawnUrl);
  22. //顺序调用我们注册的pipline,在pipline通常将结果入库,写文件
  23. if (!page.getResultItems().isSkip()) {
  24. for (Pipeline pipeline : pipelines) {
  25. pipeline.process(page.getResultItems(), this);
  26. }
  27. }
  28. sleep(site.getSleepTime());
  29. }

接下来,我们探讨一下爬虫的另一个核心组件Scheduler(任务调度器),以下代码是webmagic中调度器的接口,我们可以看到,它仅仅需要支持两个操作,插入待抓取

链接(push)和取链接(poll)

  1. public interface Scheduler {
  2.  
  3. /**
  4. * add a url to fetch
  5. *
  6. * @param request request
  7. * @param task task
  8. */
  9. public void push(Request request, Task task);
  10.  
  11. /**
  12. * get an url to crawl
  13. *
  14. * @param task the task of spider
  15. * @return the url to crawl
  16. */
  17. public Request poll(Task task);
  18.  
  19. }

下面的代码是webmagic默认提供的任务调度器,由于内存中的任务需要进行性排重,我们可以看到webmagic默认使用了HashSet排重,有可能你会说使用单机内存进

行排重会OOM,事实上在webmagic-extension(webmagic的扩展包)里支持其他几种排重方式,包括Redis排重,布隆过滤器排重(如果不了解的话可以维基一下)。当然,

如果使用布隆过滤器的话会有一定的误差。

  1. public abstract class DuplicateRemovedScheduler implements Scheduler {
  2.  
  3. protected Logger logger = LoggerFactory.getLogger(getClass());
  4. //可以看到,webmagic默认使用HashSet进行链接去重
  5. private DuplicateRemover duplicatedRemover = new HashSetDuplicateRemover();
  6.  
  7. public DuplicateRemover getDuplicateRemover() {
  8. return duplicatedRemover;
  9. }
  10.  
  11. public DuplicateRemovedScheduler setDuplicateRemover(DuplicateRemover duplicatedRemover) {
  12. this.duplicatedRemover = duplicatedRemover;
  13. return this;
  14. }
  15.  
  16. @Override
  17. public void push(Request request, Task task) {
  18. logger.trace("get a candidate url {}", request.getUrl());
  19. if (shouldReserved(request) || noNeedToRemoveDuplicate(request) || !duplicatedRemover.isDuplicate(request, task)) {
  20. logger.debug("push to queue {}", request.getUrl());
  21. pushWhenNoDuplicate(request, task);
  22. }
  23. }
  24.  
  25. protected boolean shouldReserved(Request request) {
  26. return request.getExtra(Request.CYCLE_TRIED_TIMES) != null;
  27. }
  28. /**
  29. * 判断是否需要去重,如果是一个POST请求则不进行去重
  30. */
  31. protected boolean noNeedToRemoveDuplicate(Request request) {
  32. return HttpConstant.Method.POST.equalsIgnoreCase(request.getMethod());
  33. }
  34.  
  35. protected void pushWhenNoDuplicate(Request request, Task task) {
  36.  
  37. }
  38. }

  在上图中,我们可以看到,在webmagic中默认不对POST请求进行排重(或许是POST参数的原因),在实际工作中,你也可以对这里进行修改,比如对POST请求的URL+Request Body做一个MD5操作,再将其放入队列中,这样会浪费一些计算时间,但可以对POST请求进行排重,也可以节省一些内存开销。

webmagic源码学习(一)的更多相关文章

  1. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  2. jQuery源码学习感想

    还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...

  3. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  7. 我的angularjs源码学习之旅2——依赖注入

    依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...

  8. ddms(基于 Express 的表单管理系统)源码学习

    ddms是基于express的一个表单管理系统,今天抽时间看了下它的代码,其实算不上源码学习,只是对它其中一些小的开发技巧做一些记录,希望以后在项目开发中能够实践下. 数据层封装 模块只对外暴露mod ...

  9. leveldb源码学习系列

    楼主从2014年7月份开始学习<>,由于书籍比较抽象,为了加深思考,同时开始了Google leveldb的源码学习,主要是想学习leveldb的设计思想和Google的C++编程规范.目 ...

随机推荐

  1. 关于label和input对齐的那些事

    input文本和label对齐 默认状态下,也就是下面这样, 文字和input是居中的. <div> <label>我是中国人</label> <input ...

  2. 1653: [Usaco2006 Feb]Backward Digit Sums

    1653: [Usaco2006 Feb]Backward Digit Sums Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 285  Solved:  ...

  3. app 评分

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #822e0e } p.p2 { margin: 0.0px 0. ...

  4. android开发之多线程实现方法概述

    一.单线程模型 当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件, ...

  5. 【方法】如何限定IP访问Oracle数据库

    [方法]如何限定IP访问Oracle数据库 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知 ...

  6. virtual dom的实践

    最近基于virtual dom 写了一个小框架-aoy. aoy是一个轻量级的mvvm框架,基于Virtual DOM.虽然现在看起来很单薄,但我做了完善的单元测试,可以放心使用.aoy的原理可以说和 ...

  7. XShell连接本地Ubuntu虚拟机

    VMware Workstation 安装好本地虚拟机之后,直接在虚拟机上敲命令着实不方便. 这个时候我们就需要一个远程命令工具来管理虚拟机,这里推荐使用XShell远程命令行工具 1.下载工具 直接 ...

  8. Dreamweaver如何开启代码错误提示,报错代码。

    DW的代码错误即无效提示功能设置:在DW代码窗口左面有一列很小的功能按钮,在其中寻找"高亮显示无效代码",选中之后就可以看到无效的代码会被添加背景色,会让你容易辨识.改正后背景色会 ...

  9. SSH相关小应用

    1.隐藏值:<s:hidden name="bbsTopic.id" value="%{bbsTopic.id}"></s:hidden> ...

  10. Tcl与Design Compiler (七)——环境、设计规则和面积约束

    本文属于原创手打(有参考文献),如果有错,欢迎留言更正:此外,转载请标明出处 http://www.cnblogs.com/IClearner/  ,作者:IC_learner 本文的主要内容是讲解( ...