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池化,也可以采用这个思路来。


xbynet
1k 声望124 粉丝

不雨花犹落,无风絮自飞