api帮助查询文档http://crawler.archive.org/apidocs/
Heritrix内嵌的Extractor并不能够很好的完成所需要的工作,这不是说它不够强大,而是因为在解析一个网页时,常常有特定的需要。比如,可能只想抓取某种格式的链接,或是抓取某一特定格式中的文本片断。Heritrix所提供的大众化的Extractor只能够将所有信息全部抓取下来。在这种情况下,就无法控制Heritrix到底该抓哪些内容,不该抓哪些内容,进而造成镜象信息太复杂,不好建立索引。 以下就使用一个实例,来讲解该如何定制和使用Extractor。这个实例其实很简单,主要功能就是抓取所有在Sohu的新闻主页上出现的新闻,并且URL格式如下所示。
http://news.sohu.com/20061122/n246553333.shtml
(1)分析一下这个URL可以知道,其中的主机部分是http://news.sohu.com,这是搜狐新闻的域名,“20061122”应该表示的是新闻的日期,而最后的“n246553333.shtml”应该是一个新闻的编号,该编号全部以“n”打头。
(2)有了这样的分析,就可以根据URL的特点,来定出一个正则表达式,凡是当链接符合该正则表达式,就认为它是一个潜在的值得抓取的链接,将其收藏,以待抓取。正则表达式如下:
http://news.sohu.com/[//d]+/n[//d]+.shtml
(3)事实上所有的Extractor均继承自org.archive.crawler.extractor.Extractor这个抽象基类,在它的内部实现了innerProcess方法,以下便是innerProcess的实现:
try {
/*
* 处理链接
*/
extract(curi);
} catch (NullPointerException npe) {
curi.addAnnotation( " err= " + npe.getClass().getName());
curi.addLocalizedError(getName(), npe, "" );
logger.log(Level.WARNING, getName() + " : NullPointerException " ,
npe);
} catch (StackOverflowError soe) {
curi.addAnnotation( " err= " + soe.getClass().getName());
curi.addLocalizedError(getName(), soe, "" );
logger.log(Level.WARNING, getName() + " : StackOverflowError " , soe);
} catch (java.nio.charset.CoderMalfunctionError cme) {
curi.addAnnotation( " err= " + cme.getClass().getName());
curi.addLocalizedError(getName(), cme, "" );
logger.log(Level.WARNING, getName() + " : CoderMalfunctionError " ,
cme);
}
}
(1)写一个类,继承Extractor的基类。
(2)在构造函数中,调用父类的构造函数,以形成完整的家族对象。
(3)继承extract(curi)方法。
为了实现抓取news.sohu.com首页上所有新闻的链接,所开发的Extractor的完整源代码如下所示。
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.URIException;
import org.archive.crawler.datamodel.CrawlURI;
import org.archive.crawler.extractor.Extractor;
import org.archive.crawler.extractor.Link;
import org.archive.io.ReplayCharSequence;
import org.archive.util.HttpRecorder;
public class SohuNewsExtractor extends Extractor {
private static Logger logger = Logger.getLogger(SohuNewsExtractor. class
.getName());
// 构造函数
public SohuNewsExtractor(String name) {
this (name, " Sohu News Extractor " );
}
// 构造函数
public SohuNewsExtractor(String name, String description) {
super (name, description);
}
// 第一个正则式,用于匹配SOHU新闻的格式
public static final String PATTERN_SOHU_NEWS =
" http://news.sohu.com/[/d]+/n[/d]+.shtml " ;
// 第二个正则式,用于匹配所有的<a href="xxx">
public static final String PATTERN_A_HREF =
" <a/s+href/s*=/s*("([^"]*)"|[^/s>])/s*> " ;
// 继承的方法
protected void extract(CrawlURI curi) {
// 将链接对象转为字符串
String url = curi.toString();
/*
* 下面一段代码主要用于取得当前链接的返回 字符串,以便对内容进行分析时使用
*/
ReplayCharSequence cs = null ;
try {
HttpRecorder hr = curi.getHttpRecorder();
if (hr == null ) {
throw new IOException( " Why is recorder null here? " );
}
cs = hr.getReplayCharSequence();
} catch (IOException e) {
curi.addLocalizedError( this .getName(), e,
" Failed get of replay char sequence " + curi.toString()
+ " " + e.getMessage());
logger.log(Level.SEVERE, " Failed get of replay char sequence in
"
+ Thread.currentThread().getName(), e);
}
// 如果什么也没抓取到,就返回
if (cs == null ) {
return ;
}
// 将链接返回的内容转成字符串
String content = cs.toString();
try {
// 将字符串内容进行正则匹配
// 取出其中的链接信息
Pattern pattern = Pattern.compile(PATTERN_A_HREF,
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
// 若找到了一个链接
while (matcher.find()) {
String newUrl = matcher.group( 2 );
// 查看其是否为SOHU新闻的格式
if (newUrl.matches(PATTERN_SOHU_NEWS)) {
// 若是,则将链接加入到队列中
// 以备后续处理
addLinkFromString(curi, newUrl, "" , Link.NAVLINK_HOP);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 将链接保存记录下来,以备后续处理
private void addLinkFromString(CrawlURI curi, String uri,
CharSequence context, char hopType) {
try {
curi.createAndAddLinkRelativeToBase(uri, context.toString(),
hopType);
} catch (URIException e) {
if (getController() != null ) {
getController().logUriError(e, curi.getUURI(), uri);
} else {
logger.info( " Failed createAndAddLinkRelativeToBase "
+ curi + " , " + uri + " , " + context + " , "
+ hopType + " : " + e);
}
}
}
}
注:还是不了解到底是如何继承extract(curi)方法的,难道是继承了Extractor中的innerprocess中的方法?还是详讲一下吧。
(1)首先是将Fetcher所获得的链接的HTML响应取得,并转成字符串,这样,才有可能在后面对页面中的链接做处理。
(2)从页面内容中,使用正则式取出所有链接的内容。判断链接是否符合Sohu的新闻格式,倘若符合,则调用addLinkFromString()方法,来将这个链接加入到某个队列缓存中,以备后续的处理。
在Extractor类开发完毕后,如果使用WebUI的方式启动Heritrix,并让它出现在下拉选项中,则需要修改Eclipse工程中的modules目录下的Processor.options文件。
打开Processor.options文件可以看到,所有在WebUI中设置处理器链时,页面上的下拉列表中的数据都保存在了其中,为了加入我们开发的SohuNewsExtractor,只需在其中合适的位置上加入一行,内容如下所示:
my.SohuNewsExtractor|SohuNewsExtractor
接下来,再次启动Heritrix,创建一个任务,进入处理器链设置的页面,就可以看到自己开发的Extractor了。
选择后,单击“Add”按钮,就可以将其加入到队列中。
需要注意的是,一定要将其置于ExtractorHTTP的后面,以保证Heritrix能够先行处理HTTP协议中的相关内容。与加入自己定制的Extractor的过程类似,开发者们也可以定制其他几种处理器。同样,只需要在modules目录下找到相应的.options文件,然后将类全名加入即可。