访问我的博客

前言

从去年到今年,笔者主要负责的是与合作方的内容对接,新增的合作商不是很多的情况下,在我自从去年引入了 WebMagic 这个爬虫框架之后,基本很少需要去关注维护爬虫,做的最多的是新接入合作商去写对应爬虫抓取模板。

因为在代码中实现了增量抓取,单机也足以承担日常的抓取工作。

在前两周,由于公司拓展新的业务渠道,需要接入的合作商瞬间增加了 3 倍,又被要求在 2 天内全部接入,那两天和另外一个同事,几乎都在忙着适配模板。

急速增加合作商的同时,服务器无法承受压力,频繁爆出 OOM 异常,导致抓取大批量失败,其中最多的一个合作商接口,需要解析下载的页面近 500w 个,单机抓取已无法满足需求,需要多台服务器同时抓取。

但鉴于当时需求紧,没有时间对爬虫部分代码进行重构升级,单机抓取也不行,而且会影响正常抓取任务的执行,于是临时想了个办法在其他服务器上抓取某个合作商,才坎坷解决了这个问题,但这也并非长久之计。

分布式抓取基础前提之一

因为刚刚引入 WebMagic 这个框架的时候,还不是太熟悉,使用的 Scheduler 是默认基于内存的队列 QueueScheduler ,当待抓取的 URL 太多时,内存就被占满了,从而导致 OOM。

如果要实现分布式抓取,前提需要使用基于 Redis 的 RedisScheduler

在创建爬虫的时候,手动设置 Scheduler 为 RedisScheduler。

spider.setScheduler(new RedisScheduler(jedisPool));

RedisScheduler 需要传入 JedisPool 参数。

如果使用的是 SpringBoot,可以声明一个 RedisConfig 的配置类。

@Configuration
public class RedisConfig { @Value("${spring.redis.host}")
private String host; @Value("${spring.redis.port}")
private int port; @Value("${spring.redis.password}")
private String password; @Value("${spring.redis.timeout}")
private int timeout; @Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle; @Value("${spring.redis.jedis.pool.max-wait}")
private long maxWaitMillis; @Bean
public JedisPool redisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis); return new JedisPool(jedisPoolConfig, host, port, timeout, password);
}
}

如果使用的是 Spring,可以在 XML 中配置声明一个 Bean 节点。

<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxActive" value="3000" />
<property name="maxIdle" value="100" />
<property name="maxWait" value="1000" />
<property name="testOnBorrow" value="true"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" ref="poolConfig" />
<constructor-arg index="1" value="127.0.0.1"/>
<constructor-arg index="2" value="6379"/>
<constructor-arg index="3" value="60000" />
<constructor-arg index="4" value="eCii8TH3xR8"/>
</bean>

声明了 JedisPool 之后,直接在代码中注入即可。

@Autowired
private JedisPool jedisPool;

分布式抓取基础前提之二

仅仅配置了 RedisScheduler,还无法达成我们的进行分布式抓取的目的,如果需要进行分布式抓取,其队列应该是共享的,即多台服务器的多个爬虫使用同一个 Redis URL 队列,取 URL 或者添加 URL 都是同一个。

又因为是 WebMagic 在帮助我们管理 Scheduler,所以 URL 的维护也是 WebMagic 在做。

先看一段 WebMagic 的源码

public void run() {
checkRunningStat();
initComponent();
logger.info("Spider {} started!",getUUID());
while (!Thread.currentThread().isInterrupted() && stat.get() == STAT_RUNNING) {
final Request request = scheduler.poll(this);
if (request == null) {
if (threadPool.getThreadAlive() == 0 && exitWhenComplete) {
break;
}
// wait until new url added
waitNewUrl();
} else {
// ......
}
}
// ......
}

可以看到 WebMagic 抓取的时候通过这行代码获取队列中待抓取的 URL 地址。

Request request = scheduler.poll(this);

而这个 this 是指实现了 Task 接口的对象,即把当前的 Spider 对象作为参数传入。

因为我们使用了 RedisScheduler,所以进入该类的 poll() 方法查看。

String url = jedis.lpop(getQueueKey(task));

通过 task 的 UUID 获取到队列的 key,然后利用 redis 的 list 的 lpop 命令从队列左侧弹出一个带抓取的 URL,构造 Request 对象。

同样的查看 poll 上面的 pushWhenNoDuplicate 方法,是将待抓取请求的 URL push到队列的右侧,而这个队列也是通过 Spider 的 UUID 里唯一确定的。

jedis.rpush(getQueueKey(task), request.getUrl());

所以,如果要实现分布式同时抓取同一个队列,就需要保持 多个 Spider 的 UUID 是一致的

实现分布式抓取

用过 WebMagic 的人都知道,爬虫启动需要给他一个起始 URL,然后通过这个 URL 获取新的 URL;所以如果需要进行分布式抓取,肯定爬虫的起始 URL 是不能相同的,因为WebMagic 会对重复的 URL 进行自动去重。

因此爬虫的架构图从

变成了如下架构

即保证多个爬虫使用同一个 Redis 队列。具体思路就是第一只通过起始 URL 爬虫启动的时候,记录启动爬虫的设置UUID,然后启动其他爬虫的时候,设置爬虫的 UUID 为记录的 UUID 的值。

代码中体现的就是如下所示:

启动其他爬虫的时候,手动从队列中获取 URL 设置为启动 URL 即可。

分布式爬虫任务调度

笔者实现的爬虫启动是通过定时任务启动的,因为其他爬虫与第一只爬虫的入口不同,因此定义了两个任务去调度,并且两个任务之间有 30s 的间隔时间,防止第一只爬虫还未添加 URL 到队列当中,而造成其他爬虫无 URL 可抓取情况的发生。

基于这个思路,因 URL 放在 Redis 之中,所以同时也可以实现 断点抓取。

结语

WebMagic 的源码很简洁易懂,可以学习到很多东西,尤其是多线程以及锁的应用,很值得借鉴学习。

WebMagic实现分布式抓取以及断点抓取的更多相关文章

  1. 分布式爬虫:使用Scrapy抓取数据

    分布式爬虫:使用Scrapy抓取数据 Scrapy是Python开发的一个快速,高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据.Scrapy用途广泛,可以用于数据挖掘. ...

  2. Phantomjs+Nodejs+Mysql数据抓取(2.抓取图片)

    概要 这篇博客是在上一篇博客Phantomjs+Nodejs+Mysql数据抓取(1.抓取数据) http://blog.csdn.net/jokerkon/article/details/50868 ...

  3. 网络爬虫: 从allitebooks.com抓取书籍信息并从amazon.com抓取价格(3): 抓取amazon.com价格

    通过上一篇随笔的处理,我们已经拿到了书的书名和ISBN码.(网络爬虫: 从allitebooks.com抓取书籍信息并从amazon.com抓取价格(2): 抓取allitebooks.com书籍信息 ...

  4. python实现一个栏目的分页抓取列表页抓取

    python实现一个栏目的分页抓取列表页抓取 #!/usr/bin/env python # coding=utf-8 import requests from bs4 import Beautifu ...

  5. Hibernate学习---第十一节:Hibernate之数据抓取策略&批量抓取

    1.hibernate 也可以通过标准的 SQL 进行查询 (1).将SQL查询写在 java 代码中 /** * 查询所有 */ @Test public void testQuery(){ // ...

  6. charles之抓包和断点

    一 .charles抓包 Charles抓包很简单,只要手机设置代理即可,不会的也可以去百度. 在这里是要记录抓包过程中win10遇到的问题,手机代理设置没问题但是就是抓不到包的情况 1.关闭防火墙 ...

  7. fiddler抓包工具 https抓取 ios手机端抓取

    fiddler抓包工具 https抓取 ios手机端抓取  转载链接:https://www.cnblogs.com/bais/p/9118297.html   抓取pc端https请求,ios手机端 ...

  8. jmeter旅程第一站:Jmeter抓包浏览器或者抓取手机app的包

    学习jmeter?从实际出发,我也是一个初学者,会优先考虑先用来做一些简单的抓包.接口测试,在实践的过程中学习jmeter用途.那么接下来,这篇文章我会以jmeter抓包开启我的jmeter旅程. 这 ...

  9. wireshark怎么抓包、wireshark抓包详细图文教程

    wireshark怎么抓包.wireshark抓包详细图文教程 作者:佚名  来源:本站整理  发布时间:2013-05-02 19:56:27 本日:53 本周:675 本月:926 总数:3749 ...

随机推荐

  1. spring-aop代理的生效原理

    主要说下spring里aop的生效的原理吧,并不是讲底层的cglib和gdk动态代理. 还是老一套的分析流程,先找到了aop的标签的handler,然后看下在解析这个标签的时候,都干了些什么,其实主要 ...

  2. hdu 4939 三色塔防

    http://acm.hdu.edu.cn/showproblem.php?pid=4939 给出一条长为n个单位长度的直线,每通过一个单位长度需要 t 秒. 有3种塔,红塔可以在当前格子每秒造成 x ...

  3. 分形之谢尔宾斯基(Sierpinski)三角形

    谢尔宾斯基三角形(英语:Sierpinski triangle)是一种分形,由波兰数学家谢尔宾斯基在1915年提出,它是一种典型的自相似集.也有的资料将其称之为谢尔宾斯基坟垛. 其生成过程为: 取一个 ...

  4. cnn公式推导

    CNN公式推导 1 前言 在看此blog之前,请确保已经看懂我的前两篇blog[深度学习笔记1(卷积神经网络)]和[BP算法与公式推导].并且已经看过文献[1]的论文[Notes on Convolu ...

  5. mysql命令行客户端结果分页浏览

    转载请注明出处:http://xiezhenye.com/2008/06/mysql%e5%91%bd%e4%bb%a4%e8%a1%8c%e5%ae%a2%e6%88%b7%e7%ab%af%e7% ...

  6. SSO集成方案[随笔]

    看这个方案之前,先说明下为什么要加入SSO,以防对大家产生不好的影响.我们产品使用传统winform+db服务+Db存储方式开发,一群老菜帮子开发,以传统的datatble做数据传递,很多年了未有变化 ...

  7. c#中的几种Dialog

    1.OpenFileDialog private void FileOpen_Click(object sender, EventArgs e) { OpenFileDialog openFile = ...

  8. CefSharp.v49.0.1浏览器控件完全WPF版,实现禁止弹出新窗口,在同一窗口打开链接,并且支持带type="POST" target="_blank"的链接

    需求场景:在查询页面,填写查询条件,查询条件包括上传的图片,根据图片的特征查询,这就需要在提交的时候,使用POST提交,因为GET提交无法提交图片数据,提交查询条件之后,在新的窗口展示查询结果.(当然 ...

  9. 尝试asp.net mvc 基于controller action 方式权限控制方案可行性(转载)

    微软在推出mvc框架不久,短短几年里,版本更新之快,真是大快人心,微软在这种优秀的框架上做了大量的精力投入,是值得赞同的,毕竟程序员驾驭在这种框架上,能够强力的精化代码,代码层次也更加优雅,扩展较为方 ...

  10. C# MemoryStream BinaryReader

    不清楚这类东西内部搞什么鬼,直接看代码才舒爽 https://referencesource.microsoft.com/#mscorlib 然后可以在线测试 https://www.tutorial ...