Heritrix的架构之CrawController

CrawlController即中央控制器,是一次抓取任务中的核心组件。它将决定整个抓取任务的开始和结束CrawlController位于org.archive.crawler.framework中,在它的Field声明中,看到如下代码片段。
// key subcomponents which define and implement a crawl in progress 
private transient CrawlOrder order; 
private transient CrawlScope scope; 
private transient ProcessorChainList processorChains; 
 
private transient Frontier frontier; 
 
private transient ToePool toePool; 
 
private transient ServerCache serverCache; 
 
// This gets passed into the initialize method. 
private transient SettingsHandler settingsHandler; 

注:这里transient是用于声明序列化的时候不被存储的。

可以看到,在CrawlController类中,定义了以下几个组件: 
 CrawlOrder:这就不用说了,因为一个抓取工作必须要有一个Order对象,它保存了对该次抓取任务中order.xml的属性配置。
CrawlScope:这是决定当前的抓取范围的一个组件。
ProcessorChainList:从名称上很明显就能看出,它表示了处理器链。 
 Frontier:很明显,一次抓取任务需要设定一个Frontier,以此来不断为其每个线程提供URI。
ToePool:这是一个线程池,它管理了所有该抓取任务所创建的子线程。
ServerCache:这是一个缓存,它保存了所有在当前任务中,抓取过的Host名称和Server名称。

以上组件应该是一次正常的抓取过程中所必需的几项,它们各自的任务很独立,分工明确,但在后台中,它们之间却有着千丝万缕的联系,彼此互相做为构造函数或初始化的参数传入。 那么,究竟该如何获CrawlController的实例,并且通过自主的编程来使用Heritrix提供的API进行一次抓任务呢? 事实上CrawlController有一个不带参数的构造函数,开发者可以直接通过它的构造函数来构造一个CrawlController的实例。但是值得注意的一点,在构造一个实例并进行抓取任务时,有几个步骤需要完成:
(1)首先构造一个XMLSettingsHandler对象,将order.xml内的属性信息装入。
(2)调用CrawlController的构造函数,构造一个CrawlController的实例。
(3)调用CrawlController的intialize(SettingsHandler)方法,初始化CrawlController实例。其中,传入的参数  是在第一步是构造的XMLSettingsHandler实例。
(4)当上述3步完成后,CrawlController就已经具备运行的条件,可以开始运行了。此时,只需调用它的requestCrawlStart()方法,就可以启运线程池和Frontier,然后就可以开始不断的抓取网页了。

在CrawlController的initialize()方法中,Heritrix主要做了以下几件事:
(1)从XMLSettingsHandler中取出Order。
(2)检查了用户设定的UserAgent等信息,看是否符合格式。
(3)设定了开始抓取后保存文件信息的目录结构。
(4)初始化了日志信息的记录工具。
(5)初始化了使用Berkley DB的一些工具。
(6)初始化了Scope、Frontier以及ProcessorChain。
(7)最后实例化了线程池。

在正常情况下,以上顺序不能够被随意变动,因为后一项功能的初始化很有可能需要前几项功能初始化的结果。例如线程池的初始化,必须要在先有了Frontier的实例的基础上来进行。

最终启动抓取工作的是requestCrawlStart()方法。其代码如下。

public void requestCrawlStart() { 
 
// 初始化处理器链 
 runProcessorInitialTasks(); 
 
 
// 设置一下抓取状态的改变,以便能够激发一些Listeners 
 
// 来处理相应的事件 
 sendCrawlStateChangeEvent(STARTED, CrawlJob.STATUS_PENDING); 
 String jobState; 
 state 
= RUNNING; 
 jobState 
= CrawlJob.STATUS_RUNNING; 
 sendCrawlStateChangeEvent(
this.state, jobState); 
 
 
// A proper exit will change this value. 
 this.sExit = CrawlJob.STATUS_FINISHED_ABNORMAL; 
 
 
// 开始日志线程 
 Thread statLogger = new Thread(statistics); 
 statLogger.setName(
"StatLogger"); 
 statLogger.start(); 
 
 
// 启运Frontier,抓取工作开始 
 frontier.start(); 

可以看到,启动抓取工作的核心就是要启动Frontier(通过调用其start()方法),以便能够开始向线程池中的工作线程提供URI,供它们抓取。

 下面的代码就是BdbFrontier的父类AbstractFrontier中的start()方法和unpause()方法:

public void start() { 
 
if (((Boolean)getUncheckedAttribute(null, ATTR_PAUSE_AT_START)) 
.booleanValue()) { 
 
// 若配置文件中不允许该次抓取开始 
 
// 则停止 
  controller.requestCrawlPause(); 
 } 
else { 
 
// 若允许开始,则开始 
  unpause(); 
 } 

 

 

synchronized public void unpause() { 
 
// 去除当前阻塞变量 
 shouldPause = false
 
// 唤醒所有阻塞线程,开始抓取任务 
 notifyAll(); 

在start()方法中,首先判断配置中的属性是否允许当前线程开始。若不允许,则令controller停止抓取。若允许开始,则简单的调用unpause()方法。unpause()方法更为简单,它首先将阻塞线程的信号量设为false,即允许线程开始活动,然后通过notifyAll()方法,唤醒线程池中所有被阻塞的线程,开始抓取。

注意: notifyAll()的作用就是唤醒所有正在等待该对象的线程。

阅读更多
个人分类: All Spiders
想对作者说点什么? 我来说一句

heritrix3.x 详解

2016年03月01日 1.64MB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭