标签:foreach lun 定义 man 逻辑 规则 arraylist 利用 模板方法
目录
前言:记录下在上家公司负责过的一个采集系统从零到整的过程,包括需求,分析,设计,实现,遇到的问题及系统的成效,系统最主要功能就是可以通过对每个网站进行不同的采集规则配置对每个网站爬取数据,目前系统运行稳定,已爬取的数据量大概在600-700万之间(算上一些历史数据,应该也有到千万级了),每天采集的数据增量在一万左右,配置采集的网站1200多个,这个系统其实并不大,但是作为主要的coding人员(基本整个系统的百分之八十的编码都是我写的),大概记录一下系统的实现,捡主要的内容分享下,最后在提供一些简单的爬虫demo供大家学习下
数据采集系统:一个可以通过配置规则采集不同网站的系统
主要实现目标:
第一步当然要先分析需求,所以在抽取一下系统的主要需求:
再分析一下网站的结构,无非就是两种;
一个是列表页,这里的列表页代表的就是那种需要在当前页面获取到更多别的详情页的网页链接,像一般的查询列表,可以通过列表获取到更多的详情页链接。
一个是详情页,这种就比较好理解,这种页面不需要在这个页面再去获得别的网页链接了,直接在当前页面就可以提取数据。
基本所有爬取的网站都可以抽象成这样。
针对分析的结果设计实现:
基础架构就是: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采集器:
详情页采集器:
使用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:http://localhost:9001/getData?url=https://www.cnblogs.com/&xpath=//*[@id="post_list"]/div/div[2]/h3/a
网页页面:
采集回的数据:
再次请求:http://localhost:9001/getData?url=https://blog.csdn.net/&xpath=//*[@id="feedlist_id"]/li/div/div[1]/h2/a
网页页面:
采集回的数据:
通过一个方法去采集两个网站,通过不同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端展示的一篇采集的中标的数据为例,看下采集效果:
本文只是大概记录下这个采集系统从零到整的过程,当然其中还遇到了很多本文没提到的问题。
欢迎转载,转载请保留出处 陈明羽:https://www.cnblogs.com/cmyxn/p/9376256.html
标签:foreach lun 定义 man 逻辑 规则 arraylist 利用 模板方法
原文地址:https://www.cnblogs.com/cmyxn/p/9376256.html