目录
- 分布式爬虫框架 Demo
- 起因
- 弊端
- 目标
- 用最简单的代码实现一个爬虫
- 建立爬虫模型(接口)
- 集群,异构
- 分布式
- 后语
分布式爬虫框架 Demo
记录用Java开发一个简单的分布式爬虫框架,从最开始的十几行代码到开发一个支持集群、分布式的爬虫框架。
代码地址:单机版本 分布式版本
起因
在训练智能问答机器人的模型时,缺少模型数据,决定使用爬虫进行。
当前每天的生活如下:
弊端
- 单线程爬虫太慢了,没法充分利用计算资源,亟需性能提升。
- 目标网站经常变更,势必经常添加、修改爬取网站。需要一个框架(其实已有很多很好地框架,这里仅为了学习)。
- 手动触发爬虫和训练太蠢了,希望自动触发,夜里完成
由于“懒”,第一步第二步占用了90%的时间,决定将其自动化。
目标
- 每天凌晨2点定时增量抓取数据并保存
- 每天凌晨3点训练模型
- 每天白天到实验室增加爬虫数据源,看结果,调整模型
用最简单的代码实现一个爬虫
用十几行代码爬取全站新闻 代码地址
建立爬虫模型(接口)
用面向对象的思想将爬取数据抽象为
- 任务(主要包含目标网站 url)
- 爬虫(执行任务(发起HTTP请求),返回目标 url 的数据)
- 结果处理(将爬虫返回的数据处理,主要包含解析、保存)
模型完善
上面的基本模型有了,但是还不够完善,补充一些东西。
- 为了充分利用计算资源,爬虫和结果都使用多线程执行,线程还要复用,这里为他们分为两个不同的线程池(IO和计算)。
- 为了消除任务创建和爬虫的耦合,在任务和爬虫间增加一个队列(因为在处理页面结果时,可能再发出新的爬虫HTTP请求,或者说创建爬虫任务)。
初步方案如图:
模型解读
- 由爬虫启动器(SpiderStarter)创建爬虫任务(SpiderTask)加入到任务队列(SpiderTaskQueue)
- 爬虫管理器(SpiderManager)间歇性的从爬虫任务队列中尝试获取全部任务,并分配给爬虫(Spider)使用新线程去爬取
- 爬虫(Spider)向任务(SpiderTask)中描述的url发起Http请求并反回结果,交给结果处理器接头人(ResultHanderManager)然后等等爬虫管理器(SpiderManager)再次分配任务。
- 爬虫结果处理器接头人(ResultHanderManager)收到结果后开启处理线程,交给对应的结果处理器(Handler)去处理
- 爬虫结果处理器(Handler)来处理返回的数据(匹配想要的内容,保存到希望的文件),若希望爬取深一层的url,也可以在处理过程中创建新的爬虫任务,扔进爬虫队列(SpiderTaskQueue)
触发
调用启动器(SpiderStarter)的start方法即可。
代码实现
单机版本
使用
想新增加一个网站的爬取处理时,只需要新增一个爬虫类型:
- 新建 xxxSpiderTask 类来保存 url 和任务类型
- 新建 xxxHandler 类来处理新的爬取来的数据
集群,异构
实验室里有多台电脑,想充分利用这些电脑,就得让自己的爬虫支持集群,由于爬虫本身并不关心是否支持集群,那就从刚才搭好的框架做,框架实现,所有爬虫便全都支持。
需要改动的点
由于我们已经使用面向对象的思想建立好了模型,因此想支持集群只需要多个实例共享 Task 的状态即可。故只需要将 SpiderTaskQueue 切换为消息中间件,并实现Task的序列化即可。
其中消息中间件开源的有如 RabbitMq、KafKa 等,序列化直接用 Json 就好了。
代码参见 Gitee
分布式
集群后其实一个实例既能爬取,又能处理,但爬虫和处理消耗的资源不对等,因此决定将它拆开,分开运行,同时实验室有专门的同学也可以用python写爬虫,也有会Java的同学专门做数据处理,充分发挥各个语言的优势。
.#### 需要改动的点
为了解决这个问题很简单,我们只需要将爬虫执行器和处理器分开即可,只需要添加一个爬虫结果队列,爬虫执行完后,将结果放置在队列中,爬虫结果处理器从这个队列里取就好了。
代码参见 爬虫执行器代码 爬虫处理器代码
这样可以更加合理的利用计算机资源。
- 当爬虫任务大量增加时,便可以只部署爬虫执行实例,快速爬取。
- 当爬虫结果多但任务少时,可以减少爬虫执行器的实例,增加结果处理实例加速解析。
后语
本框架仅为思想启发,如何将一个问题更好地解决,未关注性能问题,实现中有很多可以优化的点~。