记录两年前写的一个采集系统,包括需求,分析,设计,实现,遇到的问题及系统的成效,系统最主要功能就是能够经过对每一个网站进行不一样的采集规则配置对每一个网站爬取数据,两年前离职的时候已爬取的数据量大概就在千万级左右,天天采集的数据增量在一万左右,配置采集的网站1200多个,现记录一下系统实现,在提供一些简单的爬虫demo供你们学习下如何爬数据css
数据采集系统:一个能够经过配置规则采集不一样网站的系统 主要实现目标:html
数据采集系统架构图java
第一步固然要先分析需求,因此在抽取一下系统的主要需求:jquery
再分析一下网站的结构,无非就是两种;web
基本全部爬取的网站均可以抽象成这样。ajax
针对分析的结果设计实现:正则表达式
任务表redis
每一个网站能够当作一个任务,去执行采集设计模式
两张规则表浏览器
每一个网站对应本身的采集规则,根据上面分析的网站结构,采集规则又能够细分为两个表,一个是包含网站连接,获取详情页列表的列表采集规则表,一个针对是网站详情页的特征数据采集的规则表 详情采集规则表
url表
负责记录采集目标网站详情页的url
定时任务表
根据定时任务去定时执行某些任务 (能够采用定时任务和多个任务进行关联,也能够考虑新增一个任务组表,定时任务跟任务组关联,任务组跟任务关联)
数据存储表
这个因为咱们采集的数据主要是招标和中标两种数据,分别建了两张表进行数据存储,中标信息表,招标信息表
基础架构就是:ssm+redis+htmlunit+jsoup+es+mq+quartz java中能够实现爬虫的框架有不少,htmlunit,WebMagic,jsoup等等还有不少优秀的开源框架,固然httpclient也能够实现。
为何用htmlunit? htmlunit 是一款开源的java 页面分析工具,读取页面后,能够有效的使用htmlunit分析页面上的内容。项目能够模拟浏览器运行,被誉为java浏览器的开源实现
简单说下我对htmlunit的理解:
XPath语法即为XML路径语言(XML Path Language),它是一种用来肯定XML文档中某部分位置的语言。
为何用jsoup? jsoup相较于htmlunit,就在于它提供了一种相似于jquery选择器的定位页面元素的功能,二者能够互补使用。
采集数据逻辑分为两个部分:url采集器,详情页采集器
url采集器:
详情页采集器:
根据url去采集目标url的详情页数据
使用htmlunit的xpath,jsoup的select语法,和正则表达式进行特征数据的采集。
这样设计目的主要是将url采集和详情页的采集流程分开,后续若是须要拆分服务的话就能够将url采集和详情页的采集分红两个服务。
url采集器与详情页采集器之间使用mq进行交互,url采集器采集到url作完处理以后把消息冷到mq队列,详情页采集器去获取数据进行详情页数据的采集。
因为每一个网站的页面都不同,尤为是有的同一个网站的详情页结构也不同,这样就给特征数据的提取增长了难度,因此使用了htmlunit+jsoup+正则三种方式结合使用去采集特征数据。
因为采集的网站较多,假设每一个任务的执行都打开一个列表页,十个详情页,那一千个任务一次执行就须要采集11000个页面,因此采用url与详情页分开采集,经过mq实现异步操做,url和详情页的采集经过多线程实现。
对于一个网站,假设每半小时执行一次,那天天就会对网站进行48次的扫描,也是假设一次采集会打开11个页面,一天也是528次,因此被封是一个很常见的问题。解决办法,htmlunit提供了代理ip的实现,使用代理ip就能够解决被封ip的问题,代理ip的来源:一个是如今网上有不少卖代理ip的网站,能够直接去买他们的代理ip,另外一种就是爬,这些卖代理ip的网站都提供了一些免费的代理ip,能够将这些ip都爬回来,而后使用httpclient或者别的方式去验证一下代理ip的可用性,若是能够就直接入库,构建一个本身的代理ip库,因为代理ip具备时效性,因此能够建个定时任务去刷这个ip库,将无效ip剔除。
网站失效也有两种,一种是网站该域名了,原网址直接打不开,第二种就是网站改版,原来配置的全部规则都失效了,没法采集到有效数据。针对这个问题的解决办法就是天天发送采集数据和日志的邮件提醒,将那些没采到数据和没打开网页的数据汇总,以邮件的方式发送给相关人员。
当时对一个网站采集历史数据采集,方式也是先经过他们的列表页去采集详情页,采集了几十万的数据以后发现,这个网站采不到数据了,看页面以后发如今列表页加了一个验证码,这个验证码仍是属于比较简单的就数字加字母,当时就想列表页加验证码?,而后想解决办法吧,搜到了一个开源的orc文字识别项目tess4j(怎么使用能够看这),用了一下还能够,识别率在百分之二十左右,由于htmlunit能够模拟在浏览器的操做,因此在代码中的操做就是先经过htmlunit的xpath获取到验证码元素,获取到验证码图片,而后利用tess4j进行验证码识别,以后将识别的验证码在填入到验证码的输入框,点击翻页,若是验证码经过就翻页进行后续采集,若是失败就重复上述识别验证码操做,知道成功为止,将验证码输入到输入框和点击翻页均可用htmlunit去实现
有些网站使用的是ajax加载数据,这种网站在使用htmlunit采集的时候须要在获取到HtmlPage对象以后给页面一个加载ajax的时间,以后就能够经过HtmlPage拿到ajax加载以后的数据。
代码:webClient.waitForBackgroundJavaScript(time); 能够看后面提供的demo
系统总体的架构图,咱们这里说就是数据采集系统这部分
爬虫的实现:
@GetMapping("/getData")
public List<String> article_(String url,String xpath){
WebClient webClient = WebClientUtils.getWebClientLoadJs();
List<String> datas = new ArrayList<>();
try {
HtmlPage page = webClient.getPage(url);
if(page!=null){
List<?> lists = page.getByXPath(xpath);
lists.stream().forEach(i->{
DomNode domNode = (DomNode)i;
datas.add(domNode.asText());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
webClient.close();
}
return datas;
}
复制代码
上面的代码就实现了采集一个列表页
网页页面:
网页页面:
经过一个方法去采集两个网站,经过不一样url和xpath规则去采集不一样的网站,这个demo展现的就是htmlunit采集数据的过程。
每一个采集任务都是执行相同的步骤
- 获取client -> 打开页面 -> 提取特征数据(或详情页连接) -> 关闭cline
不一样的地方就在于提取特征数据
复制代码
优化:利用模板方法设计模式,将功能部分抽取出来
上述代码能够抽取为:一个采集执行者,一个自定义采集数据的实现
/** * @Description: 执行者 man * @author: chenmingyu * @date: 2018/6/24 17:29 */
public class Crawler {
private Gatherer gatherer;
public Object execute(String url,Long time){
// 获取 webClient对象
WebClient webClient = WebClientUtils.getWebClientLoadJs();
try {
HtmlPage page = webClient.getPage(url);
if(null != time){
webClient.waitForBackgroundJavaScript(time);
}
return gatherer.crawl(page);
}catch (Exception e){
e.printStackTrace();
}finally {
webClient.close();
}
return null;
}
public Crawler(Gatherer gatherer) {
this.gatherer = gatherer;
}
}
复制代码
在Crawler 中注入一个接口,这个接口只有一个方法crawl(),不一样的实现类去实现这个接口,而后自定义取特征数据的实现
/** * @Description: 自定义实现 * @author: chenmingyu * @date: 2018/6/24 17:36 */
public interface Gatherer {
Object crawl(HtmlPage page) throws Exception;
}
复制代码
优化后的代码:
@GetMapping("/getData")
public List<String> article_(String url,String xpath){
Gatherer gatherer = (page)->{
List<String> datas = new ArrayList<>();
List<?> lists = page.getByXPath(xpath);
lists.stream().forEach(i->{
DomNode domNode = (DomNode)i;
datas.add(domNode.asText());
});
return datas;
};
Crawler crawler = new Crawler(gatherer);
List<String> datas = (List<String>)crawler.execute(url,null);
return datas;
}
复制代码
不一样的实现,只须要去修改接口实现的这部分就能够了
最后看一下利用采集系统采集的数据。
效果仍是不错的,最主要是系统运行稳定:
系统配置采集的网站主要针对全国各省市县招投标网站(目前大约配置了1200多个采集站点)的标讯信息。 采集的数据主要作公司标讯的数据中心,为一个pc端网站和2微信个公众号提供数据
欢迎关注,掌握一手标讯信息
以pc端展现的一篇采集的中标的数据为例,看下采集效果:
本文只是大概记录下这个采集系统从零到整的过程,固然其中还遇到了不少本文没提到的问题。