项目中使用webmagic作为爬虫爬取框架,需要实现2个功能:
这二个功能的添加都是对scheduler模块进行改造,webmagic的scheduler模块负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。此为实现的前提。
由于使用过的是webmagic框架,框架中的pageprocessor模块自带方法page.addTargetRequest()来将URL添加到队列尾,前期在使用时,直接将未爬取成功的URL添加进去,发现程序并未对该URL进行重试,研究源码后,发现他的实现是:
@Override
public void push(Request request, Task task) {
logger.trace("get a candidate url {}", request.getUrl());
if (shouldReserved(request) || noNeedToRemoveDuplicate(request) || !duplicatedRemover.isDuplicate(request, task)) {
logger.debug("push to queue {}", request.getUrl());
pushWhenNoDuplicate(request, task);
}
}
在往队列中push元素时,会进行一次队列中是否有重复数据的判断,若有重复数据便不添加到队列中,因此,我们重写了scheduler:
/**
* (shouldReserved(request) || noNeedToRemoveDuplicate(request) || !duplicatedRemover.isDuplicate(request, task))
* 忽略掉去重方法,认为去重总是返回true,
*
* @param request
* @param task
*/
@Override
public void push(Request request, Task task) {
logger.debug("push to queue {}", request.getUrl());
pushWhenNoDuplicate(request, task);
}
修改后,程序通过page.addTargetRequest()方法,设置爬取次数,进行重试爬取。
既然基于webmagic框架,则考虑对框架内使用的方法进行二次开发,原以为调用page.getTargetRequests().clear()方法,将所有剩余的request队列清空,即可达到停止的目的,后在项目上线后,运行几天,发现出现用户点击停止仍旧爬取的情况。
前面提到,scheduler模块使用JDK内存队列管理URL。经过研究webmagic源码发现,程序在爬取下一个URL的时候,是从scheduler队列中直接push一个元素出来,而我的操作并没有对scheduler进行修改。所以重写了一下scheduler,添加一个方法:
/**
* add a url to fetch
*
* @param request request
* @param task task
*/
public void push(Request request, Task task);
/**
* get an url to crawl
*
* @param task the task of spider
* @return the url to crawl
*/
public Request poll(Task task);
/**
* clear方法添加
*/
public void clear();
之后在Spider(WebMagic操作的入口)启动时,前一个URL爬取成功进行Pipeline操作时,调用scheduler的clear方法,进行清空操作:
private void onDownloadSuccess(Request request, Page page) {
if (site.getAcceptStatCode().contains(page.getStatusCode())) {
pageProcessor.process(page);
// 如果targetRequest队列清空了,则清空scheduler里面的queue
if (page.getResultItems().isSkip()) {
scheduler.clear();
exitWhenComplete = true;
}
extractAndAddRequests(page, spawnUrl);
if (!page.getResultItems().isSkip()) {
for (Pipeline pipeline : pipelines) {
pipeline.process(page.getResultItems(), this);
}
}
} else {
logger.info("page status code error, page {} , code: {}", request.getUrl(), page.getStatusCode());
}
sleep(site.getSleepTime());
return;
}
在Spider启动类的方法里面添加一个判断,其余不做修改,即可达到目的。