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<String,Object> extras = new HashMap<String,Object>();
                extras.put("depth",this.startDepth);
                request.setExtras(extras);
            }
            else{           //深度的解釋: 1表示是最開始的深度,每往下+1
                //這裏還有可能爲空
                if ( request.getExtra("depth") == null ){
                    Map<String,Object> extras = new HashMap<String,Object>();
                    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<String,Object> newMap = new HashMap<String, Object>();
        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
            }
        }

 

 

以上就是控制深度的方法,寫的很簡略。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章