Webmagic控制爬取深度

 

最近搞毕业设计,使用到了webmagic,但是才开始学习,对各个组件都还不是很熟悉。相信初学者都会遇到一个问题,那就是:必须要让所有URL都处理完,才能结束整个爬虫过程吗?

当然,动动脑筋就知道当然不用,但是作为新手还是不知道怎么去控制这个爬虫,我一开始也是只会傻傻的设置一个最开始的url,然后写processs方法。但是经过不断的百度,渐渐加深了对webmagic的理解,也开始看起源码来了。

一开始,我用的是非常简单的方法,如下:

    int pageCnt = 0;
    public static int limit = 10000;     //最多爬取1e4个界面,虽然pageCnt不准确
    @Override
    public void process(Page page) {

        if (pageCnt > limit){
            page.setSkip(true);
            return;
        }
        pageCnt++;
    }

这样看似可以,但是实际上有缺陷,就是pageCnt约束性不强,可能是多线程的原因,即使pageCnt大于limit了,还是会执行,我也不知道为啥,但是, 一般最终处理的页面数是limit的10倍,所以,这个limit还是能限制爬虫提前结束的,就是粗糙了一点,不到万不得已,还是别用这个方法。

(后来看到了可以使用AtomicInteger解决多线程下i++问题,但是我试了一下,还是不行,另外这个pageCnt每次访问一个网页会+10,挺有规律的)

下面是换一种思路的解决方法:

-----------------------------------------------------------------------------------------

这两篇文章算是非常有启发的

https://www.zhihu.com/question/46394779

https://bbs.csdn.net/topics/391952114

 

经过源码的查看,大致理清楚了spider的执行流程:入口是Spider.run(); 之后spider首先下载(并不是真正的下载),生成一个page对象,里面包含了网页的信息, 这一部分使用的是downloader的组件,然后就是开始process,使用的是 processer组件,而我们编写的process方法就是在这里被调用的,而我们写的process方法里面,最终要的是addTargetRequest这个方法,使用这个可以再将新的爬取网页加入队列,由于有新的request加入队列,process方法会在下一次循环执行,process完之后就是pipeline,进行一些下载或者存储,例如在pipeline里面我们可以将数据存到数据库,之后开始循环,直到队列为空。我们的修改就可以request这里开始。

addTargetRequest是将一个request加入了队列里面,这样下次就能取到这个request,那么我们只需要对request进行判断,只有在一定条件才能push进去就行了,request对象是有一个extra的,它可以设置数据,然后还可以取出来。

这里我们找到源码中的最关键函数,它是将request加入队列的地方(在QueueScheduler.class  FileCacheQueueScheduler.class 这样的类里面):

protected void pushWhenNoDuplicate(Request request, Task task) {
        if (!this.inited.get()) {
            this.init(task);
        }

        this.queue.add(request);
        this.fileUrlWriter.println(request.getUrl());
    }

最后贴上我修改的代码

int startDepth = 1; //这是自己新增的变量,表示层数
        int levelLimit = 4;     //从最开始的网页最大的深度
        protected void pushWhenNoDuplicate(Request request, Task task) {
            if (!this.inited.get()) {   //如果还是一个新的对象
                this.init(task);

                Map extras = new HashMap();
                extras.put("depth",this.startDepth);
                request.setExtras(extras);
            }
            else{           //深度的解释: 1表示是最开始的深度,每往下+1
                //这里还有可能为空
                if ( request.getExtra("depth") == null ){
                    Map extras = new HashMap();
                    extras.put("depth",this.startDepth);
                    request.setExtras( extras );
                }
                int currentDepth = (int) request.getExtra("depth");   //获取当前的深度
                if ( currentDepth > this.levelLimit ){   //如果超出了深度限制,则不能再继续了
                    return;
                }
            }
            this.queue.add(request);
            this.fileUrlWriter.println(request.getUrl());
        }

 

 当然,在process方法里面也要有修改,就是一开始获取request的深度,加1就是子节点的深度

int newDepth = (int)page.getRequest().getExtra("depth")+1;  //通过该界面的深度,推算出子界面的深度
        Map newMap = new HashMap();
        newMap.put("depth",newDepth);
        for ( Selectable selectable:aList ){        //对每个链接遍历

            String newURL = selectable.links().toString();
            if ( judgeURL(newURL) == true ) {           //判断URL是否合法
                Request newRequest = new Request();
                newRequest.setExtras(newMap);
                newRequest.setUrl(newURL);
                page.addTargetRequest(newRequest);      //这里加了一个newRequest,之前写的是加URL
            }
        }

 

 

以上就是控制深度的方法,写的很简略。

你可能感兴趣的:(Java爬虫)