一個可配置的爬蟲採集系統的方案實現

記錄兩年前寫的一個採集系統,包括需求,分析,設計,實現,遇到的問題及系統的成效,系統最主要功能就是能夠經過對每一個網站進行不一樣的採集規則配置對每一個網站爬取數據,兩年前離職的時候已爬取的數據量大概就在千萬級左右,天天採集的數據增量在一萬左右,配置採集的網站1200多個,現記錄一下系統實現,在提供一些簡單的爬蟲demo供你們學習下如何爬數據css

需求

數據採集系統:一個能夠經過配置規則採集不一樣網站的系統 主要實現目標:html

  1. 針對不一樣的網站經過配置不一樣的採集規則實現網頁數據的爬取
  2. 針對每篇內容能夠實現對特徵數據的提取
  3. 定時去爬取全部網站的數據
  4. 採集配置規則可維護
  5. 採集入庫數據可維護

架構圖

數據採集系統架構圖java

分析

第一步固然要先分析需求,因此在抽取一下系統的主要需求:jquery

  1. 針對不一樣的網站能夠經過不一樣的採集規則實現數據的爬取
  2. 針對每篇內容能夠實現對特徵數據的提取,特徵數據就是指標題,做者,發佈時間這種信息
  3. 定時任務關聯任務或者任務組去爬取網站的數據

再分析一下網站的結構,無非就是兩種;web

  1. 一個是列表頁,這裏的列表頁表明的就是那種須要在當前頁面獲取到更多別的詳情頁的網頁連接,像通常的查詢列表,能夠經過列表獲取到更多的詳情頁連接。
  2. 一個是詳情頁,這種就比較好理解,這種頁面不須要在這個頁面再去得到別的網頁連接了,直接在當前頁面就能夠提取數據。

基本全部爬取的網站均可以抽象成這樣。ajax

設計

針對分析的結果設計實現:正則表達式

  1. 任務表redis

    每一個網站能夠當作一個任務,去執行採集設計模式

  2. 兩張規則表瀏覽器

    每一個網站對應本身的採集規則,根據上面分析的網站結構,採集規則又能夠細分爲兩個表,一個是包含網站連接,獲取詳情頁列表的列表採集規則表,一個針對是網站詳情頁的特徵數據採集的規則表 詳情采集規則表

  3. url表

    負責記錄採集目標網站詳情頁的url

  4. 定時任務表

    根據定時任務去定時執行某些任務 (能夠採用定時任務和多個任務進行關聯,也能夠考慮新增一個任務組表,定時任務跟任務組關聯,任務組跟任務關聯)

  5. 數據存儲表

    這個因爲咱們採集的數據主要是招標和中標兩種數據,分別建了兩張表進行數據存儲,中標信息表,招標信息表

實現

框架

基礎架構就是:ssm+redis+htmlunit+jsoup+es+mq+quartz java中能夠實現爬蟲的框架有不少,htmlunit,WebMagic,jsoup等等還有不少優秀的開源框架,固然httpclient也能夠實現。

爲何用htmlunit? htmlunit 是一款開源的java 頁面分析工具,讀取頁面後,能夠有效的使用htmlunit分析頁面上的內容。項目能夠模擬瀏覽器運行,被譽爲java瀏覽器的開源實現

簡單說下我對htmlunit的理解:

  1. 一個是htmlunit提供了經過xpath去定位頁面元素的功能,利用xpath就能夠實現對頁面特徵數據進行提取;
  2. 第二個就在於對js的支持,支持js意味着你真的能夠把它當作一個瀏覽器,你能夠用它模擬點擊,輸入,登陸等操做,並且對於採集而言,支持js就能夠解決頁面使用ajax獲取數據的問題
  3. 固然除此以外,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隊列,詳情頁採集器去獲取數據進行詳情頁數據的採集。

遇到的問題

數據去重:

  1. 在採集url的時候進行去重
  2. 同過url進行去重,經過在redis存儲key爲url,緩存時間爲3天,這種方式是爲了防止對同一個url進行重複採集。
  3. 經過標題進行去重,經過在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:傳的是獲取博客園首頁的博客列表的標題

網頁頁面:

採集回的數據:

再爬一下csdn

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

  • url:此次傳是csdn的首頁;
  • 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;
    }
複製代碼

不一樣的實現,只須要去修改接口實現的這部分就能夠了

數據

最後看一下利用採集系統採集的數據。

效果

效果仍是不錯的,最主要是系統運行穩定:

  1. 採集的歷史數據在600-700萬量級之間
  2. 天天新採集的數據增量在一萬左右
  3. 系統目前配置了大約1200多個任務(一次定時的實現會去採集這些網站)

數據

系統配置採集的網站主要針對全國各省市縣招投標網站(目前大約配置了1200多個採集站點)的標訊信息。 採集的數據主要作公司標訊的數據中心,爲一個pc端網站和2微信個公衆號提供數據

歡迎關注,掌握一手標訊信息

以pc端展現的一篇採集的中標的數據爲例,看下采集效果:

本文只是大概記錄下這個採集系統從零到整的過程,固然其中還遇到了不少本文沒提到的問題。

相關文章
相關標籤/搜索