最近研究了一下爬虫技术,与大家分享一下。
由于目前有很多成熟的框架(奉劝不要自己花时间再写爬虫框架了,真心没必要),俺也就从中选一个适合我目前需求或者说相对简单的框架来学习吧。
先把各种网络爬虫框架地址曝光一下:基于Java的网络爬虫框架集合。
这次学习的框架WebCollector2:WebCollector2。
WebCollector中集成的Jsoup:Jsoup中文文档。
后面抓取js动态生成的html还需要selenium-api-2.12.chm(selenium中文api手册)与phantomJS(windows运行程序),当然如果你想在linux或者mac上配置环境可以访问各种操作系统phantomJS运行环境。
至于项目中需要的各种jar请看下面的pom清单(本次是用maven构建的工程)。
4.0.0
WebCollectorDemo
WebCollectorDemo
1.0
war
UTF-8
junit
junit
4.11
log4j
log4j
1.2.17
org.slf4j
slf4j-log4j12
1.7.5
cn.edu.hfut.dmic.webcollector
WebCollector
2.09
org.seleniumhq.selenium
selenium-java
2.44.0
com.github.detro
phantomjsdriver
1.2.0
${basedir}/src
${basedir}/WebRoot/WEB-INF/classes
${basedir}/src
**/*.java
maven-war-plugin
${basedir}/WebRoot
${basedir}/WebRoot
maven-compiler-plugin
1.6
目录结构:
好,准备工作已经做完,下面按照官网教程做一个到两个小例子,后面会有一个爬取京东商品的例子展现给大家。
在进行小例子之前,先简单说下爬虫的逻辑,方便大家对框架有更好的理解。基本思路是:
1.有个种子链接(你准备爬取的网页地址)
2.设置爬取规则(根据此规则在种子链接网页上进行爬取新的链接,以方便进行更深入挖掘,当然你也可以不进行爬取,可以添加更多种子链接,值爬取页面上你感兴趣的东西,而不进行更深入爬取,看你自己的需求)
3.爬取到感兴趣的网页后,对网页上感兴趣的内容进行爬取,主要通过css属性或者html标签来抓取你想要的内容,所以你需要具有相关前端网页知识
ok,上面就是基本思路。下面给出一张webcollector爬虫类的继承体系结构图:
结合上图,抽象类DeepCrawler是最主要的爬虫类,可以通过此抽象类自定义自己需要的爬取策略,其中BreadthCrawler为DeepCrawler的子抽象类,MultiExtractor为BreadthCrawler的子类。BreadthCrawler实现了一个深度爬取功能的封装,用户可以添加自己的种子与过滤规则并且可以决定是否进行深度爬取。MultiExtractor在BreadthCrawler基础上做了一个多页面爬取的封装。下面结合官网的第一个小例子WebCollector 2.x tutorial 2 (BreadthCrawler中文教程)进行我们的学习。
我稍稍去掉了一些内容,去掉了jdbc 模版的部分,有兴趣的同学稍微看下官网例子即可。下面是我的代码
/*
* Copyright (C) 2015 zhao
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package com.zhao.crawler.demo;
import org.jsoup.nodes.Document;
import cn.edu.hfut.dmic.webcollector.crawler.BreadthCrawler;
import cn.edu.hfut.dmic.webcollector.model.Links;
import cn.edu.hfut.dmic.webcollector.model.Page;
/**
*
* WebCollector 2.x版本的tutorial
* 2.x版本特性:
* 1)自定义遍历策略,可完成更为复杂的遍历业务,例如分页、AJAX
* 2)内置Berkeley DB管理URL,可以处理更大量级的网页
* 3)集成selenium,可以对javascript生成信息进行抽取
* 4)直接支持多代理随机切换
* 5)集成spring jdbc和mysql connection,方便数据持久化
* 6)集成json解析器
* 7)使用slf4j作为日志门面
* 8)修改http请求接口,用户自定义http请求更加方便
*
* 可在cn.edu.hfut.dmic.webcollector.example包中找到例子(Demo)
*
* @author hu
*
* @author zhao>
* @date 2015-10-20
*/
public class TutorialCrawler2 extends BreadthCrawler {
/**
* 如果autoParse设置为true,遍历器会自动解析页面中符合正则的链接,加入后续爬取任务,否则不自动解析链接。
*
* @param crawlPath
* @param autoParse
*/
public TutorialCrawler2(String crawlPath, boolean autoParse) {
super(crawlPath, autoParse);
/*BreadthCrawler可以直接添加URL正则规则*/
this.addRegex("http://item.jd.com/.*.html");
// this.addRegex("http://.*zhihu.com/.*");
// this.addRegex("-.*jpg.*");
}
/**
*用户自定义对每个页面的操作,一般将抽取、持久化等操作写在visit方法中。
*
* @param page
* @param nextLinks 需要后续爬取的URL。如果autoParse为true,爬虫会自动抽取符合正则的链接并加入nextLinks。
*/
@Override
public void visit(Page page, Links nextLinks) {
Document doc=page.getDoc();
String title = doc.title();
System.out.println("URL:" + page.getUrl() + " 标题:" + title);
// System.out.println(doc.html());
/*
//添加到nextLinks的链接会在下一层或下x层被爬取,爬虫会自动对URL进行去重,所以用户在编写爬虫时完全不必考虑生成重复URL的问题。
//如果这里添加的链接已经被爬取过,则链接不会在后续任务中被爬取
//如果需要强制添加已爬取过的链接,只能在爬虫启动(包括断点启动)时,通过Crawler.addForcedSeed强制加入URL。
nextLinks.add("http://www.csdn.net");
*/
}
public static void main(String[] args) throws Exception {
/*
第一个参数是爬虫的crawlPath,crawlPath是维护URL信息的文件夹的路径,如果爬虫需要断点爬取,每次请选择相同的crawlPath
第二个参数表示是否自动抽取符合正则的链接并加入后续任务
*/
TutorialCrawler2 crawler = new TutorialCrawler2("D:/test/crawler/demo",true);
crawler.setThreads(50);
crawler.addSeed("http://list.jd.com/list.html?cat=1319,1523,7052&page=1&go=0&JL=6_0_0");
// crawler.addSeed("http://www.zhihu.com/");
crawler.setResumable(false);
/*
//requester是负责发送http请求的插件,可以通过requester中的方法来指定http/socks代理
HttpRequesterImpl requester=(HttpRequesterImpl) crawler.getHttpRequester();
//单代理
requester.setProxy("127.0.0.1", 1080,Proxy.Type.SOCKS);
//多代理随机
RandomProxyGenerator proxyGenerator=new RandomProxyGenerator();
proxyGenerator.addProxy("127.0.0.1",8080,Proxy.Type.SOCKS);
requester.setProxyGenerator(proxyGenerator);
*/
/*设置是否断点爬取*/
crawler.setResumable(false);
/*设置每层爬取爬取的最大URL数量*/
crawler.setTopN(1000);
/*如果希望尽可能地爬取,这里可以设置一个很大的数,爬虫会在没有待爬取URL时自动停止*/
crawler.start(2);
}
}
TutorialCrawler2.java我将原例子中爬取知乎改成了爬取京东的一个商品列表页面。重写父类中visit方法,在此方法中对页面进行抽取工作。构造函数中添加了一个过滤规则,这块需要大家对正则表达式有些基本了解,第一个过滤规则是抽取链接符合该规则的所有链接(即所有商品链接),我们这里visit方法里只做了简单的打印工作,打印了爬取的url与页面标题。
再说下main函数里面,我们首先创建一个TutorialCrawler2对象实例,两个参数分别为保存爬取数据的保存路径,与是否进行深度爬取。setThreads()是设置爬取启动的最大线程数(理论上同时爬取多页面时启动线程越多,爬取速度越快,追踪源码发现默认为50)。addSeed()是添加爬取的种子页面,可以添加一个或者多个。setResumable()设置是否支持断点爬取,setTop()为设置每层爬取的最大url数量,start(int depth)为启动爬取程序,其中depth为爬取层数。在这里解释下层数的概念,种子url为第一层,如果设置start(1),那么只会爬取种子页面,不会再往下进行爬取,如果设置start(2),那么从种子页面爬取出来的所有链接即为第二层准备爬取的链接,爬取完第二层链接就不会继续往下继续爬取。好接下来运行该爬取程序,会把种子页面所有符合爬取规则的链接都抽取出来,然后会继续爬取从第一层抽取出来的链接,继续按照过滤规则抽取符合过滤规则的链接,但是不会继续往下继续怕去了额,下面为运行结果。
[cn.edu.hfut.dmic.webcollector.crawler.Crawler]starting depth 1
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=1, spinWaiting=0, fetchQueue.size=0
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]fetch http://list.jd.com/list.html?cat=1319,1523,7052&page=1&go=0&JL=6_0_0
URL:http://list.jd.com/list.html?cat=1319,1523,7052&page=1&go=0&JL=6_0_0 标题:婴幼奶粉 奶粉 母婴 【行情 价格 评价 正品行货】-京东
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=0, spinWaiting=0, fetchQueue.size=0
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]clear all activeThread
[cn.edu.hfut.dmic.webcollector.fetcher.DbUpdater]start merge
[cn.edu.hfut.dmic.webcollector.fetcher.DbUpdater]merge fetch database
[cn.edu.hfut.dmic.webcollector.fetcher.DbUpdater]merge link database
[cn.edu.hfut.dmic.webcollector.fetcher.DbUpdater]end merge
[cn.edu.hfut.dmic.webcollector.fetcher.DbUpdater]remove fetch database
[cn.edu.hfut.dmic.webcollector.fetcher.DbUpdater]remove link database
[cn.edu.hfut.dmic.webcollector.crawler.Crawler]depth 1 finish:
TOTAL urls: 1
TOTAL time: 2 seconds
[cn.edu.hfut.dmic.webcollector.crawler.Crawler]starting depth 2
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950232304.html to http://item.jd.hk/1950232304.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950401845.html to http://item.jd.hk/1950401845.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950290275.html to http://item.jd.hk/1950290275.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950106400.html to http://item.jd.hk/1950106400.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950301488.html to http://item.jd.hk/1950301488.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950252374.html to http://item.jd.hk/1950252374.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950220622.html to http://item.jd.hk/1950220622.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950334150.html to http://item.jd.hk/1950334150.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950204490.html to http://item.jd.hk/1950204490.html
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950351781.html to http://item.jd.hk/1950351781.html
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=50, spinWaiting=0, fetchQueue.size=12
[cn.edu.hfut.dmic.webcollector.net.HttpRequest]redirect from http://item.jd.com/1950095740.html to http://item.jd.hk/1950095740.html
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=50, spinWaiting=0, fetchQueue.size=12
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=50, spinWaiting=0, fetchQueue.size=12
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=50, spinWaiting=0, fetchQueue.size=12
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]fetch http://item.jd.com/1313006459.html
[cn.edu.hfut.dmic.webcollector.fetcher.Fetcher]-activeThreads=50, spinWaiting=0, fetchQueue.size=12
URL:http://item.jd.com/1313006459.html 标题:惠氏启赋较大婴儿和幼儿配方2段奶粉900g新老包装随机发货 1桶装【图片 价格 品牌 报价】-京东闪购
.
.
.
cn.edu.hfut.dmic.webcollector.crawler.Crawler]depth 2 finish:
TOTAL urls: 62
TOTAL time: 19 seconds
控制台打印日志分析:
启动时start(2),我们输入的参数为2,所以只会爬取2层。
第一层:由于我们只添加了一个种子链接,故第一次爬取结束后,打印爬取url数量为1,用时2s(符合验证)
第二层:抽取第一层符合过滤规则的所有链接进行爬取,爬取62个符合规则的链接页面,用时19秒,
没有第三层爬取日志(符合验证)
注意:实际中第一层爬取符合过滤规则的链接数会大于62,因为有重复的链接,但是再进行爬取的时候,会自动过滤掉重复链接的爬取(即相同链接只会爬取一次)。
ok,第一个小例子到这就结束了,通过这个例子可以对这个框架的用法有个基本了解了,但是到了这里还不够,因为在爬取的时候发现,只用该框架不能js生成动态网页(例如京东页面上的价格),下一篇会介绍webcollector与两外两个框架(selenium与phantomjs)结合使用爬取js生成的动态网页.
最后一篇会放出源码。