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

来源:互联网 发布:五羊宝宝洗衣液 知乎 编辑:程序博客网 时间:2024/05/20 05:08

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






27 0
原创粉丝点击