webmagic是个神奇的爬虫(二)-- webmagic爬取流程细讲

时间:2021-04-27 16:57:26

    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开发的开源项目,加一些爬虫爱好者的群,不断借鉴,不断交流,才能不断的成长。