解决方案:一个可配置的爬虫采集系统的方案实现
优采云 发布时间: 2020-08-30 08:01一个可配置的爬虫采集系统的方案实现
记录两年前写的一个采集系统,包括需求,分析,设计,实现,遇到的问题及系统的成效,系统最主要功能就是可以通过对每位网站进行不同的采集规则配置对每位网站爬取数据,两年前辞职的时侯已爬取的数据量大约就在千万级左右,每天采集的数据增量在一万左右,配置采集的网站1200多个,现记录一下系统实现,在提供一些简单的爬虫demo供你们学习下怎样爬数据
需求
数据采集系统:一个可以通过配置规则采集不同网站的系统
主要实现目标:
针对不同的网站通过配置不同的采集规则实现网页数据的爬取针对整篇内容可以实现对特点数据的提取定时去爬取所有网站的数据采集配置规则可维护采集入库数据可维护剖析
第一步其实要先剖析需求,所以在抽取一下系统的主要需求:
针对不同的网站可以通过不同的采集规则实现数据的爬取针对整篇内容可以实现对特点数据的提取,特征数据就是指标题,作者,发布时间这些信息定时任务关联任务或则任务组去爬取网站的数据
再剖析一下网站的结构,无非就是两种;
一个是列表页,这里的列表页代表的就是那个须要在当前页面获取到更多别的详情页的网页链接,像通常的查询列表,可以通过列表获取到更多的详情页链接。一个是详情页,这种就比较好理解,这种页面不需要在这个页面再去获得别的网页链接了,直接在当前页面就可以提取数据。
基本所有爬取的网站都可以具象成这样。
设计
针对剖析的结果设计实现:
任务表
每个网站可以当作一个任务,去执行采集
两张规则表
每个网站对应自己的采集规则,根据前面剖析的网站结构,采集规则又可以细分为两个表,一个是收录网站链接,获取详情页列表的列表采集规则表,一个针对是网站详情页的特点数据采集的规则表 详情采集规则表
url表
负责记录采集目标网站详情页的url
定时任务表
根据定时任务去定时执行个别任务 (可以采用定时任务和多个任务进行关联,也可以考虑新增一个任务组表,定时任务跟任务组关联,任务组跟任务关联)
数据储存表
这个因为我们采集的数据主要是招标和中标两种数据,分别建了两张表进行数据储存,中标信息表,招标信息表
实现框架
基础构架就是:ssm+redis+htmlunit+jsoup+es+mq+quartz
java中可以实现爬虫的框架有很多,htmlunit,WebMagic,jsoup等等还有好多优秀的开源框架,当然httpclient也可以实现。
为什么用htmlunit?
htmlunit 是一款开源的java 页面剖析工具,读取页面后,可以有效的使用htmlunit剖析页面上的内容。项目可以模拟浏览器运行,被誉为java浏览器的开源实现
简单说下我对htmlunit的理解:
一个是htmlunit提供了通过xpath去定位页面元素的功能,利用xpath就可以实现对页面特点数据进行提取;第二个就在于对js的支持,支持js意味着你真的可以把它当作一个浏览器,你可以用它模拟点击,输入,登录等操作,而且对于采集而言,支持js就可以解决页面使用ajax获取数据的问题其实除此之外,htmlunit还支持代理ip,https,通过配置可以实现模拟微软,火狐等浏览器,Referer,user-agent,是否加载js,css,是否支持ajax等。
XPath句型即为XML路径语言(XML Path Language),它是一种拿来确定XML文档中某部份位置的语言。
为什么用jsoup?
jsoup相较于htmlunit,就在于它提供了一种类似于jquery选择器的定位页面元素的功能,两者可以互补使用。
采集
采集数据逻辑分为两个部份:url采集器,详情页采集器
url采集器:
详情页采集器:
遇到的问题数据去重:在采集url的时侯进行去重同过url进行去重,通过在redis储存key为url,缓存时间为3天,这种方法是为了避免对同一个url进行重复采集。通过标题进行去重,通过在redis中储存key为采集到的标题 ,缓存时间为3天,这种方法就是为了避免一篇文章被不同网站发布,重复采集情况的发生。数据质量:
由于每位网站的页面都不一样,尤其是有的同一个网站的详情页结构也不一样,这样就给特点数据的提取降低了难度,所以使用了htmlunit+jsoup+正则三种形式结合使用去采集特征数据。
采集效率:
由于采集的网站较多,假设每位任务的执行都打开一个列表页,十个详情页,那一千个任务一次执行就须要采集11000个页面,所以采用url与详情页分开采集,通过mq实现异步操作,url和详情页的采集通过多线程实现。
被封ip:
对于一个网站,假设每半小时执行一次,那每晚都会对网站进行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加载数据:
有些网站使用的是ajax加载数据,这种网站在使用htmlunit采集的时侯须要在获取到HtmlPage对象以后给页面一个加载ajax的时间,之后就可以通过HtmlPage领到ajax加载以后的数据。
代码:webClient.waitForBackgroundJavaScript(time); 可以看前面提供的demo
系统整体的构架图,我们这儿说就是数据采集系统这部份
demo
爬虫的实现:
@GetMapping("/getData")
public List article_(String url,String xpath){
WebClient webClient = WebClientUtils.getWebClientLoadJs();
List 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::9001/getData?url=;xpath=//*[@id="post_list"]/div/div[2]/h3/a
网页页面:
采集回的数据:
再爬一下csdn
再次恳求::9001/getData?url=;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 article_(String url,String xpath){
Gatherer gatherer = (page)->{
List 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 datas = (List)crawler.execute(url,null);
return datas;
}
不同的实现,只须要去更改插口实现的这部份就可以了
数据
最后看一下借助采集系统采集的数据。
效果
效果还是不错的,最主要是系统运行稳定:
采集的历史数据在600-700万量级之间每晚新采集的数据增量在一万左右系统目前配置了大概1200多个任务(一次定时的实现会去采集这些网站)数据
系统配置采集的网站主要针对全省各省市区招投标网站(目前大概配置了1200多个采集站点)的标讯信息。
采集的数据主要做公司标讯的数据中心,为一个pc端网站和2陌陌个公众号提供数据
欢迎关注,掌握一手标讯信息
以pc端展示的一篇采集的中标的数据为例,看下采集效果:
本文只是大约记录下这个采集系统从零到整的过程,当然其中还遇见了好多本文没提及的问题。