1. 程式人生 > >webmagic是個神奇的爬蟲(二)-- webmagic爬取流程細講

webmagic是個神奇的爬蟲(二)-- webmagic爬取流程細講

    webmagic流程圖鎮樓:

第一篇筆記講到了如何建立webmagic專案,這一講來說一說webmagic爬取的主要流程。


webmagic主要由Downloader(下載器)、PageProcesser(解析器)、Schedule(排程器)和Pipeline(管道)四部分組成。


從流程圖上可以看出,webmagic爬取資訊首先需要依賴給出的一個初始爬取的地址,下載器會下載這個頁面的具體資訊:

 @Override
    public Page download(Request request, Task task) {
        Site site = null;
        if (task != null) {
            site = task.getSite();
        }
        Set<Integer> acceptStatCode;
        String charset = null;
        Map<String, String> headers = null;
        if (site != null) {
            acceptStatCode = site.getAcceptStatCode();
            charset = site.getCharset();
            headers = site.getHeaders();
        } else {
            acceptStatCode = WMCollections.newHashSet(200);
        }
        logger.info("downloading page {}", request.getUrl());
        CloseableHttpResponse httpResponse = null;
        int statusCode=0;
        try {
            HttpHost proxyHost = null;
            Proxy proxy = null; //TODO
            if (site.getHttpProxyPool() != null && site.getHttpProxyPool().isEnable()) {
                proxy = site.getHttpProxyFromPool();
                proxyHost = proxy.getHttpHost();
            } else if(site.getHttpProxy()!= null){
                proxyHost = site.getHttpProxy();
            }
            
            HttpUriRequest httpUriRequest = getHttpUriRequest(request, site, headers, proxyHost);
            httpResponse = getHttpClient(site, proxy).execute(httpUriRequest);
            statusCode = httpResponse.getStatusLine().getStatusCode();
            request.putExtra(Request.STATUS_CODE, statusCode);
            if (statusAccept(acceptStatCode, statusCode)) {
                Page page = handleResponse(request, charset, httpResponse, task);
                onSuccess(request);
                return page;
            } else {
                logger.warn("get page {} error, status code {} ",request.getUrl(),statusCode);
                return null;
            }
        } catch (IOException e) {
            logger.warn("download page {} error", request.getUrl(), e);
            if (site.getCycleRetryTimes() > 0) {
                return addToCycleRetry(request, site);
            }
            onError(request);
            return null;
        } finally {
        	request.putExtra(Request.STATUS_CODE, statusCode);
            if (site.getHttpProxyPool()!=null && site.getHttpProxyPool().isEnable()) {
                site.returnHttpProxyToPool((HttpHost) request.getExtra(Request.PROXY), (Integer) request
                        .getExtra(Request.STATUS_CODE));
            }
            try {
                if (httpResponse != null) {
                    //ensure the connection is released back to pool
                    EntityUtils.consume(httpResponse.getEntity());
                }
            } catch (IOException e) {
                logger.warn("close response fail", e);
            }
        }
    }
以上是webmagic-core包 0.6.1版本中的下載器主要方法,從程式碼中可以看出,框架首先會載入程式中預先設定的配置引數site,之後根據頁面響應生成page資訊。


下載成功後,page資訊會傳遞給解析器,由解析器來定製爬蟲模板,通過Xpath、CSS、JSOUP等解析方法,從頁面中提取有用的資訊,值得一提的是,加入後續處理請求也在解析器中執行。

/**
     * add url to fetch
     *
     * @param requestString requestString
     */
    public void addTargetRequest(String requestString) {
        if (StringUtils.isBlank(requestString) || requestString.equals("#")) {
            return;
        }
        synchronized (targetRequests) {
            requestString = UrlUtils.canonicalizeUrl(requestString, url.toString());
            targetRequests.add(new Request(requestString));
        }
    }
 /**
     * add requests to fetch
     *
     * @param request request
     */
    public void addTargetRequest(Request request) {
        synchronized (targetRequests) {
            targetRequests.add(request);
        }
    }
    /**
     * add urls to fetch
     *
     * @param requests requests
     * @param priority priority
     */
    public void addTargetRequests(List<String> requests, long priority) {
        synchronized (targetRequests) {
            for (String s : requests) {
                if (StringUtils.isBlank(s) || s.equals("#") || s.startsWith("javascript:")) {
                    continue;
                }
                s = UrlUtils.canonicalizeUrl(s, url.toString());
                targetRequests.add(new Request(s).setPriority(priority));
            }
        }
    }
後續請求可以單獨加入,也可以加入一個佇列,以上三種方法最為常用。


還有一點值得注意的是Page類中有一個setSkip的方法,剛剛接觸webmagic的時候,對這個方法一頭霧水,也極少有說明這個方法到底是用途是什麼。

    public Page setSkip(boolean skip) {
        resultItems.setSkip(skip);
        return this;

    }


在我用webmagic寫了無數個爬蟲模板之後,再回回過頭來看這個方法,才清楚它的用途。

setSkip這個方法是對resultItems的內容進行忽略,預設設定為false,簡單說明,就是在本層邏輯中,爬取到的資訊不進入管道進行儲存。

 Html html = page.getHtml();
        if (page.getRequest().getUrl().endsWith("&ie=UTF-8")) {
            page.setSkip(true);
            ...此處忽略頁面解析邏輯
            }
        } else if (page.getRequest().getUrl().contains("&pn=")) {
            String eqid = StringUtils.substringBetween(page.getHtml().toString(), "bds.comm.eqid = \"", "\";");
           
	    ...此處忽略頁面解析邏輯
	    page.putField("test",需要儲存的內容)
}
 
 

 
 
這段程式碼中由於有setSkip的設定,以"&ie=UTF-8"結尾的請求就不需要進行儲存,而請求地址中包含"&pn="字樣的請求則需要儲存。這樣的好處就是可以減少一些不必要的資源開銷,也能在一定程度上防止程式丟擲一些莫名其妙的異常。


資訊光是爬取下來並沒有多大的價值,只有把爬取到的細資訊儲存起來資訊才能被真正利用起來。webmagic則是通過管道的功能,將爬取到的資訊進行儲存。框架本身提供了到輸出控制檯和到檔案中兩種儲存方式。但大多數情況下,爬取下來的內容還是需要輸出到資料庫,這樣的功能還是需要自己定製一個專門的pipeline。


說到現在,還剩最後一個部分,就是排程器。它主要的作用是負責爬取流程的管理。框架本身預設實現QueueScheduler的排程方法,而該方法又是繼承了DuplicateRemovedScheduler類,前者是通過阻塞佇列的方式保證請求一進一出不會亂,而後者則是相當於Set集合的功能,對佇列中的請求進行去重。


。。。至此,webmagic的主要流程及功能部件就講的差不多了,但是:


作為初級爬蟲開發來講,自己主要需要寫的內容,就是解析器的部分。其他的下載器,排程器和管道多數情況下都可以使用框架所提供的。但隨著需要爬取的內容和業務邏輯越來越複雜,就需要自己定製這幾方面的功能。


最後,建議在github上找一些基於webmagic開發的開源專案,加一些爬蟲愛好者的群,不斷借鑑,不斷交流,才能不斷的成長。