Webmagic源码分析系列文章,请看这里
从解决问题开始吧。
问题描述:由于数据库的数据量特别大,而且公司没有搞主从读写分离,导致从数据库读取数据比较慢,而我需要从数据库查询出特定标识来拼url去抓。实际运行中就发现了一个有趣的现象。爬虫抓取的速度超过了我用scheduler给它推送url的速度,导致爬虫从scheduler获取不到url,同时此刻线程池所有线程都已停止。这个时候,根据Spider的机制是要退出调度循环的,从而终止Spider。从下面代码可以看出:(取自Spider的run方法):
while ((!(Thread.currentThread().isInterrupted()))
&& (this.stat.get() == 1)) {
Request request = this.scheduler.poll(this);
if (request == null) {
if ((this.threadPool.getThreadAlive() == 0)
&& (this.exitWhenComplete)) {
break;
}
waitNewUrl();
} else {
Request requestFinal = request;
this.threadPool.execute(new Runnable(requestFinal) {
public void run() {
try {
Spider.this.processRequest(this.val$requestFinal);
Spider.this.onSuccess(this.val$requestFinal);
} catch (Exception e) {
Spider.this.onError(this.val$requestFinal);
Spider.this.logger.error("process request "
+ this.val$requestFinal + " error", e);
} finally {
Spider.this.pageCount.incrementAndGet();
Spider.this.signalNewUrl();
}
}
});
}
}
this.stat.set(2);
if (this.destroyWhenExit)
close();
上述中,由于Spider默认exitWhenComplete=true,而this.threadPool.getThreadAlive() == 0也在我刚刚描述的场景中应验了,所以此时Spider会break退出调度循环,进而终止。
那么如何解决呢?我们应该注意到了exitWhenComplete这个标志,Spider是开放了这个标志的setter的,那么我们可以通过它来实现自定义的管理。如何管理?
//设置exitWhenComplete=false,避免scheduler.poll返回null,且没有工作线程时退出循环。
spider.setExitWhenComplete(false);
spider.start();
//分页循环推送url
int i=2;
while(i<=page.getTotalPages()){
page=storeManager.findPage(c, i, 50);
for(Store s:page.getResult()){
if(StringUtils.isNotBlank(s.getSkipLink()) && s.getSkipLink().contains("?id=")){
Request request=new Request(s.getSkipLink());
scheduler.push(request,spider);
}
}
i++;
}
int nullCount=0;
//分5次重试来确保真的没有了。
while(nullCount<5){
//如果没有活动线程,我们就查看scheduler中是否有request,如果没有,计数+1,之后休眠再重新循环。
if(spider.getThreadAlive()==0){
Request req=scheduler.poll(spider);
if(req==null){
nullCount++;
}else{
if(nullCount>0){
nullCount=0;
}
scheduler.push(req, spider);
}
}
HttpReqUtil.sleep(5*60*1000);
}
//退出检测循环说明结束了,手动调用stop()来是Spider退出调度循环,终止爬虫。
spider.stop();
其实,如果你想Spider池化,也可以采用这个思路来。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。