作为纯Java语言开发的、功能强大的网络爬虫Heritrix,其功能极其强大,且扩展性良好,深受热爱搜索技术的盆友们的喜爱,但它配置较为复杂,且源码不好理解,最近又使劲看了下,结合自己的学习和理解,跟大家分享Heritrix的点点滴滴。
Heritrix的下载(http://sourceforge.net/projects/archive-crawler/)安装、配置,就不罗嗦了,可以自己找找资料看看哦,很丰富的。
1)Heritrix的Job
Heritrix的WebUI菜单栏上有“Jobs”的标签,即为Heritrix的爬取任务,可以通过4种创建方式来生成一个任务。
Based on existing job:以一个已经有的抓取任务为模板,创建所有抓取属性和抓取起始URL的列表。
Based on a recovery:在以前的某个任务中,可能设置过一些状态点,新的任务将从这个设置的状态点开始。
Based on a profile:专门为不同的任务设置了一些模板,新建的任务将按照模板来生成。
With defaults:这个最简单,表示按默认的配置来生成一个任务。
在Heritrix中,一个任务对应一个描述文件。这个描述文件的默认的名称为order.xml。每次创建一个新任务时,都相当于生成了一个order.xml的文件。order.xml中详细记录了Heritrix在运行时需要的所有信息。例如,它包括该用户所选择的Processor类、Frontier类、Fetcher类、抓取时线程的最大数量、连接超时的最大等待时间等信息。上面所说的4种创建抓取任务的方式,其实都是在生成一个order.xml文件。其中,第4种With defaults,则是直接拷贝默认的order.xml文件。
下面是通过order.xml控制爬虫的一段简单的代码,还是比较好理解的:
public static void main(String[] args) {
try {
//order.xml的路径
String crawlOrderFile = "D:\\XX\\order.xml";
CrawlStatusListener listener = null;
File file = new File(crawlOrderFile);
XMLSettingsHandler handler = new XMLSettingsHandler(file);
handler.initialize();
CrawlController controller = new CrawlController();
controller.initialize(handler);
if (listener != null) {
controller.addCrawlStatusListener(listener);
}
//这里会启动N个线程去抓取页面,而程序也会在同时往下走
controller.requestCrawlStart();
while ( true ) {
if ( false == controller.isRunning() ) {
System.out.println( "Finished crawing!" );
break;
}
Thread.sleep( 1000 );
}
// 必须调用此方面,释放内存
controller.requestCrawlStop();
System.out.println( "OK!" );
} catch ( Exception e ) {
e.printStackTrace();
}
}
order.xml的细节都可以在Heritrix的WebUI中反映出来,可以装一个看看^_^
2)Heritrix的架构
抓取任务CrawlOrder:整个抓取工作的起点,在org.archive.crawler.settings包下有一个XMLSettingsHandler类,它可以用来帮助读取order.xml
public XMLSettingsHandler(File orderFile) throws InvalidAttributeValueException
在XMLSettingsHandler的构造函数中,其所传入的参数orderFile正是一个经过对象封装的order.xml的File。这样,就可以直接调用其构造函数,来创建一个XMLSettingsHandler的实例,以此做为一个读取order.xml的工具。当一个XMLSettingsHandler的实例被创建后,可以通过getOrder()方法来获取CrawlOrder的实例,可以参看上面简单的代码。
中央控制器CrawlController:中央控制器是一次抓取任务中的核心组件。它将决定整个抓取任务的开始和结束。CrawlController位于org.archive.crawler.framework中,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链接制造工厂:在Heritrix中,Frontier是一种为线程提供链接的工具。它通过一些特定的算法来决定哪个链接将接下来被送入处理器链中,同时,它本身也负责一定的日志和状态报告功能。
Frontier中还有两个关键的方法,next()和finished(),这两个方法都是要交由抓取的线程来完成的。Next()方法的主要功能是:从等待队列中取出一个链接并返回,然后抓取线程会在它自己的run()方法中完成对这个链接的处理。而finished()方法则是在线程完成对链接的抓取和后续的一切动作后(如将链接传递经过处理器链)要执行的。它把整个处理过程中解析出的新的链接加入队列中,并且在处理完当前链接后,将之加入alreadyIncluded这个HashMap中去。
处理链和Processor:Heritrix的处理器链包括以下几种:PreProcessor、Fetcher、Extractor、Writer、PostProcessor。为了很好的表示整个处理器链的逻辑结构,以及它们之间的链式调用关系,Heritrix设计了几个API来表示这种逻辑结构。
org.archive.crawler.framework.Processor
该类代表着单个的处理器,所有的处理器都是它的子类。
org.archive.crawler.framework.ProcessorChain
该类表示一个队列,里面包括了同种类型的几个Processor。例如,可以将一组的Extractor加入到同一个ProcessorChain中去。
org.archive.crawler.framework.ProcessorChainList
正常情况下,一个ProcessorChainList中,应该包括有5个ProcessorChain,分别为PreProcessor链、Fetcher链、Extractor链、Writer链和PostProcessor链,而每个链中又包含有多个的Processor。
3)Heritrix的多线程
Heritrix的多线程ToeThread和ToePool,想要更有效更快速的抓取网页内容,则必须采用多线程。Heritrix中提供了一个标准的线程池ToePool,它用于管理所有的抓取线程。
ToePool和ToeThread都位于org.archive.crawler.framework包中。前面已经说过,ToePool的初始化,是在CrawlController的initialize()方法中完成的。
ToePool以及ToeThread是如何被初始化的。以下代码是在CrawlController中用于对ToePool进行初始化的。
// 构造函数
toePool = new ToePool(this);
// 按order.xml中的配置,实例化并启动线程
toePool.setSize(order.getMaxToes());
ToePool的构造函数很简单,如下所示:
public ToePool(CrawlController c) {
super("ToeThreads");
this.controller = c;
}
它仅仅是调用了父类java.lang.ThreadGroup的构造函数,同时,将注入的CrawlController赋给类变量。这样,便建立起了一个线程池的实例了,当线程被启动后,所执行的是其run()方法中的片段。
4)Heritrix的扩展
向Heritrix中添加自己的Extractor:Heritrix所提供的大众化的Extractor只能够将所有信息全部抓取下来。在这种情况下,就无法控制Heritrix到底该抓哪些内容,不该抓哪些内容,进而造成镜象信息太复杂,不好建立索引。
编写过滤器扩展Extractor时要做的几件事:
(1)写一个类,继承Extractor的基类。
(2)在构造函数中,调用父类的构造函数,以形成完整的家族对象。
(3)继承extract(curi)方法。
public class MyExtractor extends Extractor {
// 构造函数
public MyExtractor(String name) {
this(name, "Sohu News Extractor");
}
// 构造函数
public MyExtractor(String name, String description) {
super(name, description);
}
// 第一个正则式,用于匹配SOHU新闻的格式
public static final String PATTERN_URLS ="<a href=\"http://127.0.0.1:8080/*\">";
// 第二个正则式,用于匹配所有的<a href="xxx">
public static final String PATTERN_A_HREF = "<a href=\"*\">";
// 继承的方法
protected void extract(CrawlURI curi) {
//编写自己的代码控制逻辑
}
}
扩展FrontierScheduler来抓取特定的内容:FrontierScheduler是一个PostProcessor,它的作用是将在Extractor中所分析得出的链接加入到Frontier中,以待继续处理。
比如,要去除所有的扩展名为.zip、.exe、.rar、.pdf和.doc的链接(其实也就是不想下载这类文件)。可以通过继承FrontierScheduler,并重写内部的schedule方法来达到我们的需要。以下是一个示例。
protected void schedule(CandidateURI caUri) {
String url = caUri.toString();
if (url.endsWith(".zip")
|| url.endsWith(".rar")
|| url.endsWith(".exe")
|| url.endsWith(".pdf")
|| url.endsWith(".doc")
|| url.endsWith(".xls")) {
return;
}
getController().getFrontier().schedule(caUri);
}
这样,每当Heritrix在执行任务时,遇到这样的文件,就会跳过抓取,从而达到了对URL链接进行筛选的目的。
以上均为学习总结,希望能和对爬虫感兴趣的童鞋共同探讨Heritrix
<!--EndFragment-->