1. 一个框架,一个领域
一个好的框架必然凝聚了领域知识。WebMagic的设计参考了业界最优秀的爬虫Scrapy,而实现则应用了HttpClient、Jsoup等Java世界最成熟的工具,目标就是做一个Java语言Web爬虫的教科书般的实现。
如果你是爬虫开发老手,那么WebMagic会非常容易上手,它几乎使用Java原生的开发方式,只不过提供了一些模块化的约束,封装一些繁琐的操作,并且提供了一些便捷的功能。
如果你是爬虫开发新手,那么使用并了解WebMagic会让你了解爬虫开发的常用模式、工具链、以及一些问题的处理方式。熟练使用之后,相信自己从头开发一个爬虫也不是什么难事。
因为这个目标,WebMagic的核心非常简单——在这里,功能性是要给简单性让步的。
一般来说,一个爬虫包括几个部分:
2. 微内核和高可扩展性
WebMagic由四个组件(Downloader、PageProcessor、Scheduler、Pipeline)构成,核心代码非常简单,主要是将这些组件结合并完成多线程的任务。这意味着,在WebMagic中,你基本上可以对爬虫的功能做任何定制。
WebMagic的核心在webmagic-core包中,其他的包你可以理解为对WebMagic的一个扩展——这和作为用户编写一个扩展是没有什么区别的。
下面是使用webmaigc的步骤:
页面下载
页面下载是一个爬虫的基础。下载页面之后才能进行其他后续操作。
链接提取
一般爬虫都会有一些初始的种子URL,但是这些URL对于爬虫是远远不够的。爬虫在爬页面的时候,需要不断发现新的链接。
URL管理
最基础的URL管理,就是对已经爬过的URL和没有爬的URL做区分,防止重复爬取。
内容分析和持久化
一般来说,我们最终需要的都不是原始的HTML页面。我们需要对爬到的页面进行分析,转化成结构化的数据,并存储下来。
不同的爬虫,对这几部分的要求是不一样的。
对于通用型的爬虫,例如搜索引擎蜘蛛,需要指对互联网大部分网页无差别进行抓取。这时候难点就在于页面下载和链接管理上--如果要高效的抓取更多页面,就必须进行更快的下载;同时随着链接数量的增多,需要考虑如果对大规模的链接进行去重和调度,就成了一个很大的问题。一般这些问题都会在大公司有专门的团队去解决,比如这里有一篇来自淘宝的快速构建实时抓取集群。对Java来说,如果你要研究通用爬虫,那么可以看一下heritrix或者nutch。
而垂直类型的爬虫要解决的问题则不一样,比如想要爬取一些网站的新闻、博客信息,一般抓取数量要求不是很大,难点则在于如何高效的定制一个爬虫,可以精确的抽取出网页的内容,并保存成结构化的数据。这方面需求很多,webmagic就是为了解决这个目的而开发的。
使用Java语言开发爬虫是比较复杂的。虽然Java有很强大的页面下载、HTML分析工具,但是每个都有不小的学习成本,而且这些工具本身都不是专门为爬虫而生,使用起来也没有那么顺手
webmagic的实现还参考了另一个Java爬虫SpiderMan。SpiderMan是一个全栈式的Java爬虫,它的设计思想跟webmagic稍有不同,它希望将Java语言的实现隔离,仅仅让用户通过配置就完成一个垂直爬虫。理论上,SpiderMan功能更强大,很多功能已经内置,而webmagic则比较灵活,适合熟悉Java语法的开发者,可以比较非常方便的进行扩展和二次开发。
webmagic的模块划分
webmagic目前的核心代码都在webmagic-core中,webmagic-samples里有一些定制爬虫的例子,可以作为参考。而webmagic-plugin目前还不完善,后期准备加入一些常用的功能。下面主要介绍webmagic-core的内容。
前面说到,webmagic参考了scrapy的模块划分,分为Spider(整个爬虫的调度框架)、Downloader(页面下载)、PageProcessor(链接提取和页面分析)、Scheduler(URL管理)、Pipeline(离线分析和持久化)几部分。只不过scrapy通过middleware实现扩展,而webmagic则通过定义这几个接口,并将其不同的实现注入主框架类Spider来实现扩展。
Spider类-核心调度
Spider是爬虫的入口类,Spider的接口调用采用了链式的API设计,其他功能全部通过接口注入Spider实现,下面是启动一个比较复杂的Spider的例子。
long startTime, endTime;
System.out.println("========MM爬虫【启动】!=========");
startTime = new Date().getTime();
Spider spider = Spider.create(new MMPaqu()).addUrl(initUrl);
//添加spider监控
try {
SpiderMonitor.instance().register(spider);
} catch (JMException e) {
e.printStackTrace();
}
spider.thread(5).run();
endTime = new Date().getTime();
System.out.println("========MM爬虫【结束】!=========");
System.out.println("一共爬到" + num + "个MM!用时为:" + (endTime - startTime) / 1000 + "s");
Spider的核心处理流程非常简单,代码如下:
<!-- lang: java -->
private void processRequest(Request request) {
Page page = downloader.download(request, this);
if (page == null) {
sleep(site.getSleepTime());
return;
}
pageProcessor.process(page);
addRequest(page);
for (Pipeline pipeline : pipelines) {
pipeline.process(page, this);
}
sleep(site.getSleepTime());
}
Downloader-页面下载
页面下载是webmagic的入口,一切工作的开始.
大部分爬虫都是通过模拟http请求,接收并分析响应来完成。
而JDK自带的HttpURLConnection可以满足最简单的需要,并且Apache HttpClient(4.0后整合到HttpCompenent项目中)则是开发复杂爬虫的不二之选。它支持自定义HTTP头(对于爬虫比较有用的就是User-agent、cookie等)、自动redirect、连接复用、cookie保留、设置代理等诸多强大的功能。
webmagic使用了HttpClient 4.2,并封装到了HttpClientDownloader。学习HttpClient的使用对于构建高性能爬虫是非常有帮助的。
下面是一个使用HttpClient最简单的例子:
StringBuffer result = new StringBuffer();
org.apache.http.client.HttpClient httpClient = (org.apache.http.client.HttpClient) new HttpClient();
HttpResponse httpReponse=null ;
BufferedReader br=null;
try {
httpReponse = httpClient.execute(httpUriRequest);
InputStream is = httpReponse.getEntity().getContent();
//读取数据流内容
br = new BufferedReader(new InputStreamReader(is));
String line ="";
while((line = br.readLine() )!=null){
result.append(line);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
对于一些Javascript动态加载的网页,仅仅使用http模拟下载工具,并不能取到页面的内容。
目前我想到了两种解决方案:
1 抽丝剥茧:
分析一下页面js逻辑,一般动态请求构造生成的html页面都是通过ajax请求,我们拿到具体的ajax请求之后,可以通过爬虫来重现取数过程,然后提取关键数据,获取想要的数据。
2 模拟浏览器:
可以使用目前业内主流的多任务自动化测试工具Selenium,这是一个非常强大的浏览器模拟工具,我们可以通过java来调用selenium实现浏览器模拟,从而重现整个请求过程。
目前,我使用的是第一种方案,参见我的源码:xx
一般没有必要去扩展Downloader。
PageProcessor-页面分析及链接抽取
这里说的页面分析主要指HTML页面的分析。页面分析可以说是垂直爬虫最复杂的一部分,在webmagic里,PageProcessor是定制爬虫的核心。通过编写一个实现PageProcessor接口的类,就可以定制一个自己的爬虫。
页面抽取最基本的方式是使用正则表达式。正则表达式好处是非常通用,解析文本的功能也很强大。但是正则表达式最大的问题是,不能真正对HTML进行语法级别的解析,没有办法处理关系到HTML结构的情况(例如处理标签嵌套)。例如,我想要抽取一个<div>里的内容,可以这样写:"<div>(.*?)</div>"。但是如果这个div内部还包含几个子div,这个时候使用正则表达式就会将子div的"</div>"作为终止符截取。为了解决这个问题,我们就需要进行HTML的分析。
HTML分析是一个比较复杂的工作,Java世界主要有几款比较方便的分析工具:
####Jsoup
Jsoup是一个集强大和便利于一体的HTML解析工具。它方便的地方是,可以用于支持用jquery中css selector的方式选取元素,这对于熟悉js的开发者来说基本没有学习成本。
String html =null;
try {
html = HttpClient.doGet("https://www.tianyancha.com/login", "", "UTF-8");
Document doc = Jsoup.parse(html);
Element tel = doc.select(".pb30 .position-rel").get(0);
Element pass = doc.select(".pb40 .position-rel").get(0);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(html);
Jsoup还支持白名单过滤机制,对于网站防止XSS攻击也是很好的。
几个工具的对比
在这里评价这些工具的主要标准是“方便”。就拿抽取页面所有链接这一基本任务来说,几种代码分别如下:
XPath:
<!-- lang: java -->
tagNode.evaluateXPath("//a/@href")
CSS Selector:
<!-- lang: java -->
//使用类似js的实现
$("a[href]").attr("href")
HtmlParser:
<!-- lang: java -->
Parser p = new Parser(value);
NodeFilter aFilter = new TagNameFilter("a");
NodeList nodes = p.extractAllNodesThatMatch(aFilter);
for (int i = 0; i < nodes.size(); i++) {
Node eachNode = nodes.elementAt(i);
if (eachNode instanceof LinkTag) {
LinkTag linkTag = (LinkTag) eachNode;
System.out.println(linkTag.extractLink());
}
}
XPath是最简单的,可以精确选取到href属性值;而CSS Selector则次之,可以选取到HTML标签,属性值需要调用函数去获取;而HtmlParser和SAX则需要手动写程序去处理标签了,比较麻烦。
Selector
Selector是webmagic为了简化页面抽取开发的独立模块,是整个项目中我最得意的部分。这里整合了CSS Selector、XPath和正则表达式,并可以进行链式的抽取,很容易就实现强大的功能。即使你使用自己开发的爬虫工具,webmagic的Selector仍然值得一试。
Scheduler-URL管理
URL管理的问题可大可小。对于小规模的抓取,URL管理是很简单的。我们只需要将待抓取URL和已抓取URL分开保存,并进行去重即可。使用JDK内置的集合类型Set、List或者Queue都可以满足需要。如果我们要进行多线程抓取,则可以选择线程安全的容器,例如LinkedBlockingQueue以及ConcurrentHashMap。
webmagic目前有两个Scheduler的实现,QueueScheduler是一个简单的内存队列,速度较快,并且是线程安全的,FileCacheQueueScheduler则是一个文件队列,它可以用于耗时较长的下载任务,在任务中途停止后,下次执行仍然从中止的URL开始继续爬取。
Pipeline-离线处理和持久化
Pipeline其实也是容易被忽略的一部分。大家都知道持久化的重要性,但是很多框架都选择直接在页面抽取的时候将持久化一起完成,例如crawer4j。但是Pipeline真正的好处是,将页面的在线分析和离线处理拆分开来,可以在一些线程里进行下载,另一些线程里进行处理和持久化。
结语
附上webMagic介绍:http://webmagic.io/docs/zh/