爬取百万数据的采集系统从零到整的过程

目录

  • 需求
  • 分析
  • 设计
  • 实现
    • 框架
    • 采集
    • 遇到的问题
    • demo
  • 数据
    • 效果
    • 数据
    • 关注关注我的公众号啊

前言:记录下在上家公司负责过的一个采集系统从零到整的过程,包括需求,分析,设计,实现,遇到的问题及系统的成效,系统最主要功能就是可以通过对每个网站进行不同的采集规则配置对每个网站爬取数据,目前系统运行稳定,已爬取的数据量大概在600-700万之间(算上一些历史数据,应该也有到千万级了),每天采集的数据增量在一万左右,配置采集的网站1200多个,这个系统其实并不大,但是作为主要的coding人员(基本整个系统的百分之八十的编码都是我写的),大概记录一下系统的实现,捡主要的内容分享下,最后在提供一些简单的爬虫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去采集目标url的详情页数据
  • 使用htmlunit的xpath,jsoup的select语法,和正则表达式进行特征数据的采集。

    这样设计目的主要是将url采集和详情页的采集流程分开,后续如果需要拆分服务的话就可以将url采集和详情页的采集分成两个服务。

    url采集器与详情页采集器之间使用mq进行交互,url采集器采集到url做完处理之后把消息冷到mq队列,详情页采集器去获取数据进行详情页数据的采集。

遇到的问题

数据去重:

  • 在采集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<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就要采集的数据的xpath了

爬一下博客园

请求这个url:http://localhost:9001/getData?url=https://www.cnblogs.com/&xpath=//*[@id="post_list"]/div/div[2]/h3/a

  • url:传的是博客园首页的地址;
  • xpath:传的是获取博客园首页的博客列表的标题

网页页面:

采集回的数据:

再爬一下csnd

再次请求:http://localhost:9001/getData?url=https://blog.csdn.net/&xpath=//*[@id="feedlist_id"]/li/div/div[1]/h2/a

  • url:这次传是csnd的首页;
  • xpath:传的是获取csdn首页的博客列表的标题

网页页面:

采集回的数据:

采集步骤

通过一个方法去采集两个网站,通过不同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;
    }

不同的实现,只需要去修改接口实现的这部分就可以了

数据

最后看一下利用采集系统采集的数据。

效果

效果还是不错的,最主要是系统运行稳定:

  • 采集的历史数据在600-700万量级之间
  • 每天新采集的数据增量在一万左右
  • 系统目前配置了大约1200多个任务(一次定时的实现会去采集这些网站)

数据

系统配置采集的网站主要针对全国各省市县招投标网站(目前大约配置了1200多个采集站点)的标讯信息。

采集的数据主要做公司标讯的数据中心,为一个pc端网站和2微信个公众号提供数据

  • 网址:http://www.bid-data.com
  • 公众号:爱招标,中标喽(欢迎关注,掌握一手标讯信息)

以pc端展示的一篇采集的中标的数据为例,看下采集效果:

  • http://www.bid-data.com/bid_MQKHG001TD6.html

    采集的详情:

    特征数据的提取:

本文只是大概记录下这个采集系统从零到整的过程,当然其中还遇到了很多本文没提到的问题。

欢迎转载,转载请保留出处 陈明羽:https://www.cnblogs.com/cmyxn/p/9376256.html

关注关注我的公众号啊

原文地址:https://www.cnblogs.com/cmyxn/p/9376256.html

时间: 2024-08-08 23:30:01

爬取百万数据的采集系统从零到整的过程的相关文章

爬虫再探实战(五)———爬取APP数据——超级课程表【一】

关于爬虫,开始以为只能爬取网页数据,后来知道APP也能抓取.于是,在学校利用空闲时间,耗时两周实现了数据的抓取和简单的数据分析. 目标,抓取超级课程表XX大学(其实是我们大学啦...)学生20000条发帖信息.思路如下: STEP1:为我们的爬虫找到入口 APP请求数据,也是通过网络协议,这样,我们就抓包来定位入口,这里我用的是fiddler.关于设置手机和fiddler的关联,请参考这篇文章. 找到登陆入口为:http://120.55.151.61/V2/StudentSkip/loginC

利用linux curl爬取网站数据

看到一个看球网站的以下截图红色框数据,想爬取下来,通常爬取网站数据一般都会从java或者python爬取,但本人这两个都不会,只会shell脚本,于是硬着头皮试一下用shell爬取,方法很笨重,但旨在结果嘛,呵呵. 2.首先利用curl工具后者wget工具把整个网站数据爬取下来 curl 网址 >wangzhan.txt 3.查看wangzhan.txt文件,找出规则,看到数据是存放在哪个地方,本人是把txt文件拷到本机上用UE打开方便查看.通过查看文件,我发现数据是存储在"var aut

Python爬取房产数据,在地图上展现!

小伙伴,我又来了,这次我们写的是用python爬虫爬取乌鲁木齐的房产数据并展示在地图上,地图工具我用的是 BDP个人版-免费在线数据分析软件,数据可视化软件 ,这个可以导入csv或者excel数据. 首先还是分析思路,爬取网站数据,获取小区名称,地址,价格,经纬度,保存在excel里.再把excel数据上传到BDP网站,生成地图报表 本次我使用的是scrapy框架,可能有点大材小用了,主要是刚学完用这个练练手,再写代码前我还是建议大家先分析网站,分析好数据,再去动手写代码,因为好的分析可以事半功

python之爬取网页数据总结(一)

今天尝试使用python,爬取网页数据.因为python是新安装好的,所以要正常运行爬取数据的代码需要提前安装插件.分别为requests    Beautifulsoup4   lxml  三个插件. 因为配置了环境变量,可以cmd命令直接安装.假如电脑上有两个版本的python,建议进入到目录安装. 安装的命令为 pip install requests(Beautifulsoup4   /lxml  ) 三条分别执行. 安装结束,可以尝试网上一些简单的例子,明白了解 Beautifulso

和风api爬取天气预报数据

''' 和风api爬取天气预报数据 目标:https://free-api.heweather.net/s6/weather/forecast?key=cc33b9a52d6e48de852477798980b76e&location=CN101090101 得到中国城市的代码:https://a.hecdn.net/download/dev/china-city-list.csv 目前先查20个城市第二天的天气 ''' import requests url = "https://a.

python爬虫——爬取网页数据和解析数据

1.网络爬虫的基本概念 网络爬虫(又称网络蜘蛛,机器人),就是模拟客户端发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序.只要浏览器能够做的事情,原则上,爬虫都能够做到. 2.网络爬虫的功能   图2 网络爬虫可以代替手工做很多事情,比如可以用于做搜索引擎,也可以爬取网站上面的图片,比如有些朋友将某些网站上的图片全部爬取下来,集中进行浏览,同时,网络爬虫也可以用于金融投资领域,比如可以自动爬取一些金融信息,并进行投资分析等. 有时,我们比较喜欢的新闻网站可能有几个,每次

爬虫(十):AJAX、爬取AJAX数据

1. AJAX 1.1 什么是AJAX AJAX即“Asynchronous JavaScript And XML”(异步JavaScript和XML)可以使网页实现异步更新,就是不重新加载整个网页的情况下,对网页的某部分进行更新(局部刷新).传统的网页(不使用AJAX)如果需要更新内容,必须重载整个网页页面. AJAX = 异步JavaScript和XML,是一种新的思想,整合之前的多种技术,用于创建快速交互式网页应用的页面开发技术. 1.2 同步和异步 同步现象:客户端发送请求到服务器端,当

08 信息化领域热词分类分析及解释 第二步 将爬取的数据使用jieba分词处理并清洗

直接上代码: import jieba import pandas as pd import re from collections import Counter if __name__=='__main__': filehandle = open("news.txt", "r",encoding='utf-8'); mystr = filehandle.read() seg_list = jieba.cut(mystr) # 默认是精确模式 print(seg_l

用puppeteer爬取网页数据初体验

用puppeteer爬取网页数据 业务需求,页面需要显示很多链接列表,像这样的. 我问项目经理要字典表,他笑咪咪地拍着我的肩膀说:"这边有点忙,要不按照这个自己抄一下吧". emmm- 我看了一下,数据大概有七八百条,一个一个录入,那不得搞到地老天荒.海枯石烂. 心口一股燥热,差点就要口吐莲花,舌吐芬芳了- 转念一想,做人要儒雅随和,念在平时没少蹭吃蹭喝的份上,咱先弄一下吧. 可是怎么弄呢? 一个一个输入是不可能的,我们需要录入每个组的标题.标题下的名称和链接,这是需要看网页源码,效率