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()方法。其代码如下。
// 初始化处理器链
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()方法:
if (((Boolean)getUncheckedAttribute( null , ATTR_PAUSE_AT_START))
.booleanValue()) {
// 若配置文件中不允许该次抓取开始
// 则停止
controller.requestCrawlPause();
} else {
// 若允许开始,则开始
unpause();
}
}
// 去除当前阻塞变量
shouldPause = false ;
// 唤醒所有阻塞线程,开始抓取任务
notifyAll();
}
在start()方法中,首先判断配置中的属性是否允许当前线程开始。若不允许,则令controller停止抓取。若允许开始,则简单的调用unpause()方法。unpause()方法更为简单,它首先将阻塞线程的信号量设为false,即允许线程开始活动,然后通过notifyAll()方法,唤醒线程池中所有被阻塞的线程,开始抓取。
注意: notifyAll()的作用就是唤醒所有正在等待该对象的线程。