网站内容采集系统(如何爬数据需求数据采集系统:一个可以通过配置规则采集)
优采云 发布时间: 2021-09-06 14:05网站内容采集系统(如何爬数据需求数据采集系统:一个可以通过配置规则采集)
记录一个两年前写的采集系统,包括需求、分析、设计、实现、遇到的问题以及系统的有效性。系统的主要功能是为每个网站制作不同的采集rule配置为每个网站抓取数据。两年前我离开时爬取的数据量大约是几千万。 采集每天的数据增量在10000左右。配置采集的网站1200多个,现记录下系统实现,并提供一些简单的爬虫demo供大家学习爬取数据
要求
Data采集system:一个可以配置规则采集不同网站的系统
主要目标:
对于不同的网站,我们可以配置不同的采集规则来实现网络数据爬取。对于每条内容,可以实现特征数据提取,抓取所有网站数据采集配置规则可以维护采集Inbound数据可维护性分析
第一步当然是先分析需求,所以我们提取系统的主要需求:
对于不同的网站,可以通过不同的采集规则实现数据爬取。可以为每条内容提取特征数据。特征数据是指标题、作者、发布时间信息定时任务关联任务或任务组爬取网站的数据
再次解析网站的结构,无非就是两个;
一个是列表页面。这里的列表页代表的是需要获取当前页面更多详情页的那种网页链接,就像一般查询列表一样,可以通过列表获取更多详情页链接。一是详情页。这种页面更容易理解。这种页面不需要在这个页面上获取到其他网页的链接,直接在当前页面上提取数据即可。
基本上所有爬到的网站都可以这样抽象出来。
设计
基于分析结果的设计与实现:
任务表
每个网站都可以当作一个任务去执行采集
两个规则表
每个网站 对应于自己的采集 规则。根据上面分析的网站结构,采集规则可以进一步细分为两个表,一个收录网站链接获取详情页列表采集Rules表的列表,一个规则表用于特征数据采集网站详情页@规则表详情采集消防表
网址表
负责记录采集target网站detail页面的url
定时任务列表
根据定时任务定时执行某些任务(可以使用定时任务关联多个任务,也可以考虑添加任务组表,定时任务关联任务组,任务组与任务相关)
数据存储表
这是因为我们的采集数据主要是中标和中标两种数据。建立了两张表用于数据存储,中标信息表和中标信息表
实现框架
基本结构为:ssm+redis+htmlunit+jsoup+es+mq+quartz
java中可以实现爬虫的框架有很多。有很多优秀的开源框架,比如htmlunit、WebMagic、jsoup等,当然也可以实现httpclient。
为什么要使用 htmlunit?
htmlunit 是一个开源的 java 页面分析工具。阅读完页面后,您可以有效地使用 htmlunit 来分析页面上的内容。该项目可以模拟浏览器操作,被誉为java浏览器的开源实现
简单说说我对htmlunit的理解:
一个是htmlunit提供了通过xpath定位页面元素的功能,可以用来提取页面特征数据;二是对js的支持,对js的支持意味着你真的可以把它当作一个浏览器,你可以用它来模拟点击、输入、登录等操作,而对于采集,支持js可以解决使用问题ajax获取页面数据。当然除此之外,htmlunit还支持代理ip、https,通过配置可以模拟谷歌、火狐、Referer、user-agent等浏览器,是否加载js、css,是否支持ajax等
XPath 语法是 XML 路径语言(XML Path Language),它是一种用于确定 XML 文档某部分位置的语言。
为什么要使用 jsoup?
相对于htmlunit,jsoup提供了类似于jquery选择器的定位页面元素的功能,两者可以互补使用。
采集
采集数据逻辑分为两个部分:url采集器,详情页采集器
url采集器:
详情页采集器:
重复数据删除遇到的问题:当使用采集url与url相同去重时,key作为url存储在redis中,缓存时间为3天。这个方法是为了防止同一个A url 重复采集。重复数据删除由标题执行。通过在redis中存储key为采集的title,缓存时间为3天。这个方法是为了防止一个文章被不同的网站发布,重复采集的情况发生。数据质量:
因为每个网站页面都不一样,尤其是同一个网站的详情页结构也不同,增加了特征数据提取的难度,所以使用htmlunit+jsoup+正则三种方式组合得到采集特征数据。
采集efficiency:
因为采集的网站有很多,假设每次任务执行打开一个列表页和十个详情页,那么一千个任务执行一次需要采集11000页,所以使用url和详情页以采集分隔,通过mq实现异步操作,url和详情页的采集通过多线程实现。
被阻止的ip:
对于一个网站,如果每半小时执行一次,那么网站一天会被扫描48次。还假设采集每天会打开11页,528次,所以Sealing是一个很常见的问题。解决办法,htmlunit提供了代理ip的实现,使用代理ip可以解决被封ip的问题,代理ip的来源:一是网上有很多网站卖代理ip的,可以买他们的代理ip直接,另一种就是爬取,这些网站卖代理ip都提供了一些免费的代理ip,你可以爬回这些ip,然后用httpclient或者其他方式验证代理ip的可用性,如果可以输入直接建数据库,搭建自己的代理ip库。因为代理ip是时间敏感的,可以创建定时任务刷ip库,去除无效ip。
网站失败:
网站失效有两种,一种是网站域名,原来的网址不能直接打开,第二种是网站改版,原来配置的规则全部失效,而采集不可用@有效数据。解决这个问题的办法是每天发送采集data和日志的邮件提醒,将未采集到的数据和未打开的网页汇总,通过邮件发送给相关人员。
验证码:
当时,对于网站采集史数据采集,方式是通过他们的列表页面进入采集detail页面。 采集查到几十万条数据后,这个网站我就拿不到数据了。查看页面后,我发现列表页面添加了验证码。这个验证码是一个比较简单的数字加字母。那个时候想在列表页加个验证码? ,然后想到了一个解决办法,找了一个开源的orc文字识别项目tess4j(使用方法看这里),过一会就好了,识别率在20%左右,因为htmlunit可以模拟操作浏览器,所以代码中的操作是先通过htmlunit的xpath获取验证码元素,获取验证码图片,然后使用tess4j识别验证码,然后将识别到的验证码填入验证中代码输入框,点击翻页,如果验证码通过,翻页进行后续采集,如果失败,重复上面的识别验证码操作,直到知道成功,将验证码输入输入框和点击翻页可以用htmlunit实现
Ajax 加载数据:
一些网站使用ajax加载数据。使用htmlunit采集时,网站需要在获取到HtmlPage对象后给页面一个加载ajax的时间,然后可以通过HtmlPage获取ajax加载后的数据。
代码:webClient.waitForBackgroundJavaScript(time);你可以看到后面提供的演示
系统整体架构图,这里指的是data采集system部分
演示
爬虫的实现:
@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
网页:
采集返回数据:
采集Steps
通过一个方法去采集两个网站,通过不同url和xpath规则去采集不同的网站,这个demo展示的就是htmlunit采集数据的过程。
每个采集任务都是执行相同的步骤
- 获取client -> 打开页面 -> 提取特征数据(或详情页链接) -> 关闭cline
不同的地方就在于提取特征数据
优化:使用模板方法设计模式提取功能部分
上面的代码可以提取为:一个采集executor,一个自定义的采集data实现
/**
* @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;
}
不同的实现,只需要修改这部分接口实现即可。
数据
最后使用采集系统采集查看数据。
效果
效果还是不错的,最重要的是系统运行稳定:
采集的历史数据在6-7百万左右。 采集的数据增量约为每天10,000。系统目前配置了1200多个任务(一次定时执行会去采集这些网站)数据
系统配置采集网站主要针对全国各个省市县的网站竞价(目前配置的采集站点已超过1200个)。
采集的数据主要作为公司标准新闻的数据中心,为一个pc端网站和2个微信公众号提供数据
欢迎关注和掌握第一手招标信息
以PC端显示的采集中标数据为例,来看看采集的效果:
本文只是对采集系统从零到全过程的粗略记录,当然也遇到了很多本文没有提到的问题。