如何设计爬虫架构

摘要: 设计爬虫架构一个设计良好的爬虫架构必须满足如下需求。(1)分布式:爬虫应该能够在多台机器上分布执行。(2)可伸缩性:爬虫结构应该能够通过增加额外的机器和带宽来提高抓取速度。(3)性能和有效性:爬虫系统必须有效地 ...
 
 

设计爬虫架构

 

一个设计良好的爬虫架构必须满足如下需求。

(1) 分布式:爬虫应该能够在多台机器上分布执行。

(2) 可伸缩性:爬虫结构应该能够通过增加额外的机器和带宽来提高抓取速度。

(3) 性能和有效性:爬虫系统必须有效地使用各种系统资源,例如,处理器、存储空间和网络带宽。

(4) 质量:鉴于互联网的发展速度,大部分网页都不可能及时出现在用户查询中,所以爬虫应该首先抓取有用的网页。

(5) 新鲜性:在许多应用中,爬虫应该持续运行而不是只遍历一次。

(6) 更新:因为网页会经常更新,例如论坛网站会经常有回帖。爬虫应该取得已经获取的页面的新的拷贝。例如一个搜索引擎爬虫要能够保证全文索引中包含每个索引页面的较新的状态。对于搜索引擎爬虫这样连续的抓取,爬虫访问一个页面的频率应该和这个网页的更新频率一致。

(7) 可扩展性:为了能够支持新的数据格式和新的抓取协议,爬虫架构应该设计成模块化的形式。

 

爬虫架构

 

1.爬虫的简化版本架构如下图所示

 

这里最主要的关注对象是爬虫和存储库。其中的爬虫部分阶段性地抓取互联网上的内容。存储库存储爬虫下载下来的网页,是分布式的和可扩展的存储系统。在往存储库中加载新的内容时仍然可以读取存储库。

 

2.实际的爬虫逻辑架构如下图所示[单线程爬虫结构]。

 

其中:

(1) URLFrontier包含爬虫当前待抓取的URL(对于持续更新抓取的爬虫,以前已经抓取过的URL可能会回到Frontier重抓)。

(2) DNS解析模块根据给定的URL决定从哪个Web服务器获取网页。

(3) 获取模块使用HTTP协议获取URL代表的页面。

(4) 解析模块提取文本和网页的链接集合。

(5) 重复消除模块决定一个解析出来的链接是否已经在URLFrontier或者最近下载过。

 

网络爬虫DNS解析瓶颈与解决

 

DNS解析是网络爬虫的瓶颈。由于域名服务的分布式特点,DNS可能需要多次请求转发,并在互联网上往返,需要几秒有时甚至更长的时间解析出IP地址。如果我们的目标是

一秒钟抓取数百个文件,这样就达不到性能要求。

一个标准的补救措施是引入缓存:最近完成DNS查询的网址可能会在DNS缓存中找到,避免了访问互联网上的DNS服务器。然而,由于抓取礼貌的限制,降低了DNS缓存的命中率。

用DNS解析还有一个难点:在标准库中实现的查找是同步的。这意味着一旦一个请求发送到DNS服务器上,在那个节点上的其他爬虫线程也被阻塞直到第一个请求完成。

为了避免这种情况发生,许多爬虫自己来实现DNS解析。执行解析代码的线程i将发送一个消息到DNS服务器,然后执行一个定时等待,不超过这个设定的时间段,这个线程会继续执行。一个单独的DNS线程侦听标准的DNS端口(53端口)从名称服务器传入的响应数据包。一旦接受一个响应,它就激活对应的爬虫线程i并把响应数据包交给i。如果i因为等待超时还没有恢复运行,则爬虫线程如果尝试5次全都失败,下次发送一个新的消息给DNS服务等待的时间会延长一倍。鉴于有的主机名需要长达几十秒的时间来解析,所以等待DNS解析的时间范围可以为1~90秒。

 

设计并行爬虫架构

 

整个爬虫系统可以由一台抓取机器或多个爬虫节点组成。

多机并行抓取的分布式系统则需要考虑节点之间的通信和调度,在一个爬虫节点上实现并行抓取,可以考虑多线程同步I/O或者单线程异步I/O。

多线程爬虫需要考虑线程之间的同步问题。对单线程并行抓取来说,异步I/O是很重要的基本功能。

异步I/O 模型大体上可以分为两种,反应式(Reactive)模型和前摄式(Proactive)模型。

传统的select/epoll/kqueue模型,以及JavaNIO模型,都是典型的反应式模型,即应用代码对I/O 描述符进行注册,然后等待I/O 事件。当某个或某些I/O 描述符所对应的I/O 设备上产生I/O 事件(可读、可写、异常等)时,系统将发出通知,于是应用便有机会进行I/O 操作并避免阻塞。由于在反应式模型中应用代码需要根据相应的事件类型采取不同的动作,因此最常见的结构便是嵌套的if{…}else{…}或switch,并常常需要结合状态机来完成复杂的逻辑。前摄式模型则恰恰相反。

在前摄式模型中,应用代码主动投递异步操作而不管I/O 设备当前是否可读或可写。投递的异步I/O 操作被系统接管,应用代码也并不阻塞在该操作上,而是指定一个回调函数并继续自己的应用逻辑。当该异步操作完成时,系统将发起通知并调用应用代码指定的回调函数。在前摄式模型中,程序逻辑由各个回调函数串联起来:异步操作A的回调发起异步操作B,B的回调再发起异步操作C,以此往复。

Java6版本开始引入的NIO包,通过Selectors类提供了非阻塞式的I/O.

Java7附带的NIO.2文件系统中包含了异步I/O支持。也可以使用框架实现异步I/O.

例如:

Mina(http://mina.apache.org/)为开发高性能和高可用性的网络应用程序提供了非常便利的框架。当前发行的MINA版本支持基于Java的NIO技术的TCP/UDP应用程序开发。MINA是借由Java的NIO的反应式实现的模拟前摄式模型。

Grizzly是Web服务器GlassFish的I/O核心。Grizzly通过队列模型提供异步读/写。

Netty是一个NIO客户端服务器框架。

Naga(http://naga.googlecode.com)是一个很小的库,提供了一些Java类,把普通的Socket和ServerSocket封装成支持NIO的形式。

从性能测试上比较,Netty和Grizzly都很快,而Mina稍慢一些。

JDK1.6内部并不使用线程来实现非阻塞式I/O。在Windows平台下,使用select();在新的Linux核下,使用epoll工具。

Niocchi(http://www.niocchi.com)是Java实现的开源异步I/O爬虫。

 

在爬虫中使用NIO的时候,主要用到的就是下面两个类。

1.java.nio.channels.Selector:Selector类通过调用select方法,将注册的channel中有事件发生的SelectionKey取出来进行处理。如果想要把管理权交到Selector类手中,首先就要在Selector对象中注册相应的Channel。

2.java.nio.channels.SocketChannel:SocketChannel用于和Web服务器建立连接。

 

资料为笔者学习笔记,非个人原创,版权归原作者所有.

posted @ 2014-08-13 11:30  王天泽博客  阅读(1598)  评论(0编辑  收藏  举报