事实上,要写出一个合格并且真正能够使用的Frontier绝非一件简单的事情,尽管有了Frontier接口,其中的方法约束了Frontier的行为,也给编码带来了一定的指示。但是其中还存在着很多问题,需要很好的设计和处理才可以解决。 在Heritrix的官方文档上,有一个Frontier的例子,在此拿出来进行一下讲解,以此来向读者说明一个最简单的Frontier都能够做什么事。以下就是这个Frontier的代码。
FetchStatusCodes {
……
}
声明各种变量;
List pendingURIs = new ArrayList();
// 这个列表中保存了一系列的链接,它们的优先级
// 要高于pendingURIs那个List中的任何一个链接
// 表中的链接表示一些需要被满足的先决条件
List prerequisites = new ArrayList();
// 一个HashMap,用于存储那些已经抓取过的链接
Map alreadyIncluded = new HashMap();
// CrawlController对象
CrawlController controller;
// 用于标识是否一个链接正在被处理
boolean uriInProcess = false;
// 成功下载的数量
long successCount = 0;
// 失败的数量
long failedCount = 0;
// 抛弃掉链接的数量
long disregardedCount = 0;
// 总共下载的字节数
long totalProcessedBytes = 0;
super(Frontier.ATTR_NAME, "A simple frontier.");
}
throws FatalConfigurationException, IOException {
// 注入
this.controller = controller;
// 把种子文件中的链接加入到pendingURIs中去
this.controller.getScope().refreshSeeds();
List seeds = this.controller.getScope().getSeedlist();
synchronized(seeds) {
for (Iterator i = seeds.iterator(); i.hasNext();) {
UURI u = (UURI) i.next();
CandidateURI caUri = new CandidateURI(u);
caUri.setSeed();
schedule(caUri);
}
}
}
InterruptedException {
if (!uriInProcess && !isEmpty())
{
uriInProcess = true;
CrawlURI curi;
/*
* 算法很简单,总是先看prerequistes队列中是否有
* 要处理的链接,如果有,就先处理,如果没有
* 再看pendingURIs队列中是否有链接
* 每次在处理的时候,总是取出队列中的第一个链接
*/
if (!prerequisites.isEmpty()) {
curi = CrawlURI.from((CandidateURI) prerequisites.remove(0));
} else {
curi = CrawlURI.from((CandidateURI) pendingURIs.remove(0));
}
curi.setServer(controller.getServerCache().getServerFor(curi));
return curi;
} else {
wait(timeout);
return null;
}
}
判断是否为空
return pendingURIs.isEmpty() && prerequisites.isEmpty();
}
/*
* 首先判断要加入的链接是否已经被抓取过
* 如果已经包含在alreadyIncluded这个HashMap中
* 则说明处理过了,即可以放弃处理
*/
if (!alreadyIncluded.containsKey(caURI.getURIString())) {
if(caURI.needsImmediateScheduling()) {
prerequisites.add(caURI);
} else {
pendingURIs.add(caURI);
}
// HashMap中使用url的字符串来做为key
// 而将实际的CadidateURI对象做为value
alreadyIncluded.put(caURI.getURIString(), caURI);
}
}
批处理
schedule(caURI);
}
public void batchFlush() {
}
uriInProcess = false;
// 成功下载
if (cURI.isSuccess()) {
successCount++;
// 统计下载总数
totalProcessedBytes += cURI.getContentSize();
// 如果成功,则触发一个成功事件
// 比如将Extractor解析出来的新URL加入队列中
controller.fireCrawledURISuccessfulEvent(cURI);
cURI.stripToMinimal();
}
// 需要推迟下载
else if (cURI.getFetchStatus() == S_DEFERRED) {
cURI.processingCleanup();
alreadyIncluded.remove(cURI.getURIString());
schedule(cURI);
}
// 其他状态
else if (cURI.getFetchStatus() == S_ROBOTS_PRECLUDED
|| cURI.getFetchStatus() == S_OUT_OF_SCOPE
|| cURI.getFetchStatus() == S_BLOCKED_BY_USER
|| cURI.getFetchStatus() == S_TOO_MANY_EMBED_HOPS
|| cURI.getFetchStatus() == S_TOO_MANY_LINK_HOPS
|| cURI.getFetchStatus() == S_DELETED_BY_USER) {
// 抛弃当前URI
controller.fireCrawledURIDisregardEvent(cURI);
disregardedCount++;
cURI.stripToMinimal();
} else {
controller.fireCrawledURIFailureEvent(cURI);
failedCount++;
cURI.stripToMinimal();
}
cURI.processingCleanup();
}
public long discoveredUriCount() {
return alreadyIncluded.size();
}
// 返回所有等待处理的链接的数量
public long queuedUriCount() {
return pendingURIs.size() + prerequisites.size();
}
// 返回所有已经完成的链接数量
public long finishedUriCount() {
return successCount + failedCount + disregardedCount;
}
// 返回所有成功处理的链接数量
public long successfullyFetchedCount() {
return successCount;
}
// 返回所有失败的链接数量
public long failedFetchCount() {
return failedCount;
}
// 返回所有抛弃的链接数量
public long disregardedFetchCount() {
return disregardedCount;
}
// 返回总共下载的字节数
public long totalBytesWritten() {
return totalProcessedBytes;
}
public String report() {
return "This frontier does not return a report.";
}
public void importRecoverLog(String pathToLog) throws IOException {
throw new UnsupportedOperationException();
}
public FrontierMarker getInitialMarker(String regexpr,
boolean inCacheOnly) {
return null;
}
public ArrayList getURIsList(FrontierMarker marker, int numberOfMatches,
boolean verbose) throws InvalidFrontierMarkerException {
return null;
}
public long deleteURIs(String match) {
return 0;
}
}
Frontier是用来向线程提供链接的,因此,在上面的代码中,使用了两个ArrayList来保存链接。其中,第一个pendingURIs保存的是等待处理的链接,第二个prerequisites中保存的也是链接,只不过它里面的每个链接的优先级都要高于pendingURIs里的链接。通常,在prerequisites中保存的都是如DNS之类的链接,只有当这些链接被首先解析后,其后续的链接才能够被解析。
除了这两个ArrayList外,在上面的Frontier还有一个名称为alreadyIncluded的HashMap。它用于记录那些已经被处理过的链接。每当调用Frontier的schedule()方法来加入一个新的链接时,Frontier总要先检查这个正要加入到队列中的链接是不是已经被处理过了。
很显然,在分析网页的时候,会出现大量相同的链接,如果没有这种检查,很有可能造成抓取任务永远无法完成的情况。同时,在schedule()方法中还加入了一些逻辑,用于判断当前要进入队列的链接是否属于需要优先处理的,如果是,则置入prerequisites队列中,否则,就简单的加入pendingURIs中即可。
注意:Frontier中还有两个关键的方法,next()和finished(),这两个方法都是要交由抓取的线程来完成的。Next()方法的主要功能是:从等待队列中取出一个链接并返回,然后抓取线程会在它自己的run()方法中完成对这个链接的处理。而finished()方法则是在线程完成对链接的抓取和后续的一切动作后(如将链接传递经过处理器链)要执行的。它把整个处理过程中解析出的新的链接加入队列中,并且在处理完当前链接后,将之加入alreadyIncluded这个HashMap中去。
唉!其实看不懂啊!…………