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