本章主要讲解FetcherThread的代码。
这个线程的作用,源码上方有一句话:
/**
* This class picks items from queues and fetches the pages.
*/
所以这个线程充当消费者的角色。
---------------------------------------------------------------------------------------------------
刚开始自然是初始化一些变量,代码如下:
activeThreads.incrementAndGet(); // count threads
FetchItem fit = null;
这里的activeThreads的初始化见
private AtomicInteger activeThreads = new AtomicInteger(0);
也就是说先统计启动的线程个数。
同时置FetchItem fit为null.初始为空,因为还未取出来。
-----------------------------------------------------------------------
接下来是一个典型的
try {
} catch (Throwable e) {
} finally {
}
重点是try里面的部分,下面我们就来分析try里面的while循环的源码。
-------------------------------------------------------------------------------------------------------
首先是从队列里取出FetchItem.代码如下:
fit = fetchQueues.getFetchItem();
if (fit == null)
{
if (feeder.isAlive() || fetchQueues.getTotalSize() > 0) {
LOG.debug(getName() + " spin-waiting ...");
// spin-wait.
spinWaiting.incrementAndGet();
try {
Thread.sleep(500);
} catch (Exception e) {
}
spinWaiting.decrementAndGet();
continue;
} else {
// all done, finish this thread
return;
}
}
这段代码放到后面再分析...
------------------------------------------接下来是设置若干变量
lastRequestStart.set(System.currentTimeMillis());
Text reprUrlWritable = (Text) fit.datum.getMetaData().get(
Nutch.WRITABLE_REPR_URL_KEY);
if (reprUrlWritable == null)
{
reprUrl = fit.url.toString();
}
else
{
reprUrl = reprUrlWritable.toString();
}
-------------------------------------------------------------------然后开始准备爬取这个网页了
先是重定向的准备
// fetch the page
redirecting = false;
redirectCount = 0;
---------------------------------------
接下来是打印日志:
do {
if (LOG.isInfoEnabled()) {
LOG.info("fetching "
+ fit.url
+ " (queue crawl delay="
+ fetchQueues
.getFetchItemQueue(fit.queueID).crawlDelay
+ "ms)");
}
if (LOG.isDebugEnabled()) {
LOG.debug("redirectCount=" + redirectCount);
LOG.info("redirectCount=" + redirectCount);
}
所以我们会看到
fetching http://www.baidu.com/ (queue crawl delay=5000ms)
redirectCount=0
--------------------------------接下来是获取爬虫协议及其机器人规则
redirecting = false;
Protocol protocol = this.protocolFactory
.getProtocol(fit.url.toString());
BaseRobotRules rules = protocol.getRobotRules(
fit.url, fit.datum);
一般情况下,我们的协议是HTTP协议,则其对应的分别是:
protocol--------------:org.apache.nutch.protocol.http.Http
BaseRobotRules--------------:crawlercommons.robots.SimpleRobotRules
这里面也是通过插件获取,所以如果你不想根据对方的robots协议的话,你可以把这段代码屏蔽掉来加速你的nutch爬虫。
------------------------------ 假如你屏蔽了robots协议,那么下面的代码对你来说是无用的。
if (!rules.isAllowed(fit.u.toString())) {
// unblock
fetchQueues.finishFetchItem(fit, true);
if (LOG.isDebugEnabled()) {
LOG.debug("Denied by robots.txt: "
+ fit.url);
}
output(fit.url, fit.datum, null,
ProtocolStatus.STATUS_ROBOTS_DENIED,
CrawlDatum.STATUS_FETCH_GONE);
reporter.incrCounter("FetcherStatus",
"robots_denied", 1);
continue;
}
if (rules.getCrawlDelay() > 0) {
if (rules.getCrawlDelay() > maxCrawlDelay
&& maxCrawlDelay >= 0) {
// unblock
fetchQueues.finishFetchItem(fit, true);
LOG.debug("Crawl-Delay for " + fit.url
+ " too long ("
+ rules.getCrawlDelay()
+ "), skipping");
output(fit.url,
fit.datum,
null,
ProtocolStatus.STATUS_ROBOTS_DENIED,
CrawlDatum.STATUS_FETCH_GONE);
reporter.incrCounter("FetcherStatus",
"robots_denied_maxcrawldelay", 1);
continue;
} else {
FetchItemQueue fiq = fetchQueues
.getFetchItemQueue(fit.queueID);
fiq.crawlDelay = rules.getCrawlDelay();
if (LOG.isDebugEnabled()) {
LOG.info("Crawl delay for queue: "
+ fit.queueID + " is set to "
+ fiq.crawlDelay
+ " as per robots.txt. url: "
+ fit.url);
}
}
}
接下来就是正式爬取url对应的内容了。
---------------------------------------------
ProtocolOutput output = protocol.getProtocolOutput(
fit.url, fit.datum);
上面提到过protocol--------------:org.apache.nutch.protocol.http.Http
这里的getProtocolOutput实际上是HttpBase里的代码。
这里不具体讲如何获取http内容的。我之前的文章有讲如何添加连接池,
今日看到这里,发现上次连接池的添加方案不是最佳的,果然技术无涯。
---先看看ProtocolOutput的代码
public class ProtocolOutput {
private Content content;
private ProtocolStatus status;
public ProtocolOutput(Content content, ProtocolStatus status) {
this.content = content;
this.status = status;
}
public ProtocolOutput(Content content) {
this.content = content;
this.status = ProtocolStatus.STATUS_SUCCESS;
}
public Content getContent() {
return content;
}
public void setContent(Content content) {
this.content = content;
}
public ProtocolStatus getStatus() {
return status;
}
public void setStatus(ProtocolStatus status) {
this.status = status;
}
}
这个很简单,我就不分析了...
--------------------------------------- 接下来清除资源
ProtocolStatus status = output.getStatus();//获取爬取的状态
Content content = output.getContent();//得到内容
ParseStatus pstatus = null;
// unblock queue
fetchQueues.finishFetchItem(fit); //从队列中去掉这个item.
---------------------------------------------------
接下来是判断爬取结果的状态
switch (status.getCode()) {
case ProtocolStatus.SUCCESS: // got a page
pstatus = output(fit.url, fit.datum, content,
status,
CrawlDatum.STATUS_FETCH_SUCCESS,
由于这个函数非常重要,所以下一篇文章中单独分析!