Java爬蟲框架之WebMagic的學習總結

2022-10-11 18:01:48
本篇文章給大家帶來了關於的相關知識,其中主要介紹了關於WebMagic的相關內容,WebMagic是一個簡單靈活的Java爬蟲框架,分為核心和擴充套件兩部分,下面一起來看一下,希望對大家有幫助。

程式設計師必備介面測試偵錯工具:

推薦學習:《》

概述

WebMagic是一個簡單靈活的Java爬蟲框架。基於WebMagic,可以快速開發出一個高效、易維護的爬蟲。

WebMagic分為核心和擴充套件兩部分。核心部分(webmagic-core)是一個精簡的、模組化的爬蟲實現,而擴充套件部分則包括一些便利的、實用性的功能。

特性

簡單的API,可快速上手

模組化的結構,可輕鬆擴充套件

提供多執行緒和分散式支援

架構

WebMagic的結構分為Downloader、PageProcessor、Scheduler、Pipeline四大元件,並由Spider將它們彼此組織起來。

四大元件對應爬蟲生命週期中的下載、處理、管理和持久化等功能。WebMagic的設計參考了Scapy,實現方式更加Java化一些。

Spider則將這幾個元件組織起來,讓它們可以互相互動,流程化的執行,可以認為Spider是一個大的容器,它也是WebMagic邏輯的核心。

19.png

四大元件

Downloader

Downloader負責從網際網路上下載頁面,以便後續處理。WebMagic預設使用了Apache HttpClient作為下載工具。

PageProcessor

PageProcessor負責解析頁面,抽取有用資訊,以及發現新的連結。WebMagic使用Jsoup作為HTML解析工具,並基於其開發瞭解析XPath的工具Xsoup。

在這四個元件中,PageProcessor對於每個站點每個頁面都不一樣,是需要使用者客製化的部分。

Scheduler

Scheduler負責管理待抓取的URL,以及一些去重的工作。WebMagic預設提供了JDK的記憶體佇列來管理URL,並用集合來進行去重。也支援使用Redis進行分散式管理。

Pipeline

Pipeline負責抽取結果的處理,包括計算、持久化到檔案、資料庫等。WebMagic預設提供了「輸出到控制檯」和「儲存到檔案」兩種結果處理方案。

Pipeline定義了結果儲存的方式,如果你要儲存到指定資料庫,則需要編寫對應的Pipeline。對於一類需求一般只需編寫一個Pipeline。

資料流轉物件

Request

Request是對URL地址的一層封裝,一個Request對應一個URL地址。它是PageProcessor與Downloader互動的載體,也是PageProcessor控制Downloader唯一方式。

除了URL本身外,它還包含一個Key-Value結構的欄位extra。你可以在extra中儲存一些特殊的屬性,然後在其他地方讀取,以完成不同的功能。例如附加上一個頁面的一些資訊等。

Page

Page代表了從Downloader下載到的一個頁面——可能是HTML,也可能是JSON或者其他文字格式的內容。

Page是WebMagic抽取過程的核心物件,它提供一些方法可供抽取、結果儲存等。

ResultItems

ResultItems相當於一個Map,它儲存PageProcessor處理的結果,供Pipeline使用。它的API與Map很類似,值得注意的是它有一個欄位skip,若設定為true,則不應被Pipeline處理。

控制爬蟲運轉的引擎Spider

Spider是WebMagic內部流程的核心。Downloader、PageProcessor、Scheduler、Pipeline都是Spider的一個屬性,這些屬性是可以自由設定的,通過設定這個屬性可以實現不同的功能。Spider也是WebMagic操作的入口,它封裝了爬蟲的建立、啟動、停止、多執行緒等功能。

以下是一個設定各個元件,並且設定多執行緒和啟動的例子。

public static void main(String[] args) {
    Spider.create(new GithubRepoPageProcessor())
            //從https://github.com/code4craft開始抓    
            .addUrl("https://github.com/code4craft")
            //設定Scheduler,使用Redis來管理URL佇列
            .setScheduler(new RedisScheduler("localhost"))
            //設定Pipeline,將結果以json方式儲存到檔案
            .addPipeline(new JsonFilePipeline("D:\\data\\webmagic"))
            //開啟5個執行緒同時執行
            .thread(5)
            //啟動爬蟲
            .run();
}
登入後複製

WebMagic的基本使用

新增WebMagic的核心與擴充套件依賴

      <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.7.5</version>
        </dependency>
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.7.5</version>
        </dependency>
登入後複製

爬蟲實現

public class MyJobProcessor implements PageProcessor {
    public void process(Page page) {
        List<String> all = page.getHtml().css("span.s_btn_wr").css("input").all();
        page.putField("title", all.get(0));
    }
    private Site site = Site.me()
            //設定編碼
            .setCharset("utf8")
            //設定超時時間,單位是ms毫秒
            .setTimeOut(10000)
            //設定重試的間隔時間
            .setRetrySleepTime(3000)
            //設定重試次數
            .setSleepTime(3);
    public Site getSite() {
        return site;
    }
    public static void main(String[] args) {
        Spider.create(new MyJobProcessor())
                //初始存取url地址
                .addUrl("http://www.baidu.com")
                .run();
    }
}
登入後複製
get page: http://www.baidu.com
title:<input type="submit" id="su" value="百度一下" class="bg s_btn">
登入後複製

爬蟲的編寫過程

實現PageProcessor

PageProcessor的客製化分為三個部分,分別是爬蟲的設定、頁面元素的抽取和連結的發現

爬蟲設定

爬蟲的設定,包括編碼、抓取間隔、超時時間、重試次數等,也包括一些模擬的引數,例如User Agent、cookie,以及代理的設定。

    private Site site = Site.me()
            //設定編碼
            .setCharset("utf8")
            //設定超時時間,單位是ms毫秒
            .setTimeOut(10000)
            //設定重試的間隔時間
            .setRetrySleepTime(3000)
            //設定重試次數
            .setSleepTime(3);
登入後複製

抽取頁面元素

頁面元素的抽取是爬蟲的核心部分:對於下載到的Html頁面,如何從中抽取到想要的資訊?

WebMagic主要使用三種抽取技術:XPath、正規表示式和CSS選擇器。對於JSON格式的內容,可使用JsonPath進行解析

XPath

獲取屬性class=myClass的div標籤,裡面的h1標籤的內容

page.getHtml().xpath("//div[@class=myClass]/h1/text()")
登入後複製

CSS選擇器

CSS選擇器是與XPath類似的語言。Jsoup的選擇器比XPath寫起來要簡單一些,但是如果寫複雜一點的抽取規則,就相對要麻煩一點。

獲取屬性class為myClass的div標籤下的直接子元素h1標籤

page.getHtml().css("div.mt>h1").toString()
登入後複製

可使用:nth-child(n)選擇第幾個元素,但是注意:需要使用直接子元素才可以選擇第幾個元素

如: 選擇第一個元素

page.getHtml().css("div#myId > ul > li:nth-child(1) a").toString()
登入後複製

正規表示式

正規表示式則是一種通用的文字抽取語言。在這裡一般用於獲取url地址。

匹配所有https://github.com/code4craft/webmagic這樣的連結。

page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
登入後複製
登入後複製

JsonPath

JsonPath是於XPath很類似的一個語言,它用於從Json中快速定位一條內容。

連結的發現

一個站點的頁面是很多的,一開始不可能全部列舉出來,於是如何發現後續的連結,是一個爬蟲不可缺少的一部分。

page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
登入後複製
登入後複製

page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all()用於獲取所有滿足https:/ /github\.com/\w+/\w+這個正規表示式的連結

page.addTargetRequests()則將這些連結加入到待抓取的佇列中去。

Selectable抽取元素

Selectable相關的抽取元素鏈式API是WebMagic的一個核心功能。使用Selectable介面,就可以直接完成頁面元素的鏈式抽取,也無需去關心抽取的細節。

上述page.getHtml()返回的是一個Html物件,它實現了Selectable介面。這個介面包含一些重要的方法,將它分為兩類:抽取部分和獲取結果部分。

抽取部分API

抽取部分API返回的都是一個Selectable介面,是支援鏈式呼叫的。

20.png

獲取結果的API

當鏈式呼叫結束時,一般都想要拿到一個字串型別的結果。這時候就需要用到獲取結果的API了。

一條抽取規則,無論是XPath、CSS選擇器或者正規表示式,總有可能抽取到多條元素。WebMagic對這些進行了統一,可以通過不同的API獲取到一個或者多個元素。

21.png

注意:當有多條資料的時候,使用get()和toString()都是獲取第一個url地址。

使用Pipeline儲存結果

如何將抓取的結果儲存下來?WebMagic用於儲存結果的元件叫做Pipeline。

例如通過「控制檯輸出結果」這件事也是通過一個內建的Pipeline完成的,它叫做ConsolePipeline。

想要把結果用Json的格式儲存下來,只需要將Pipeline的實現換成"JsonFilePipeline"就可以了。

想要把結果用儲存到檔案中,只將Pipeline的實現換成"FilePipeline"就可以了。

public static void main(String[] args) {
    Spider.create(new GithubRepoPageProcessor())
            // 初始存取url地址
            .addUrl("https://github.com/code4craft")
            .addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
            //.addPipeline(new FilePipeline("D:\\webmagic\\"))
            //開啟5個執行緒抓取
            .thread(5)
            //啟動爬蟲
            .run();
}
登入後複製

爬蟲的設定、啟動和終止

Spider啟動入口

Spider是爬蟲啟動的入口。在啟動爬蟲之前,需要使用一個PageProcessor建立一個Spider物件,然後使用run()進行啟動。同時Spider的其他元件(Downloader、Scheduler、Pipeline)都可以通過set方法來進行設定。

22.png

Site爬蟲設定

對站點本身的一些設定資訊,例如編碼、HTTP頭、超時時間、重試策略等、代理等,都可以通過設定Site物件來進行設定。

23.png

rivate Site site = Site.me()
        .setCharset("UTF-8")//編碼
        .setSleepTime(1)//抓取間隔時間
        .setTimeOut(1000*10)//超時時間
        .setRetrySleepTime(3000)//重試時間
        .setRetryTimes(3);//重試次數
登入後複製

設定代理

代理伺服器

有些網站不允許爬蟲進行資料爬取,因為會加大伺服器的壓力。其中一種最有效的方式是通過ip+時間進行鑑別,因為正常人不可能短時間開啟太多的頁面,發起太多的請求。

使用WebMagic可以設定爬取資料的時間,但是會大大降低爬取資料的效率。如果ip被禁了,就有必要使用代理伺服器來爬取資料。

代理(Proxy),也稱網路代理,是一種特殊的網路服務,允許一個網路終端(一般為使用者端)通過這個服務與另一個網路終端(一般為伺服器)進行非直接的連線。

提供代理服務的電腦系統或其它型別的網路終端稱為代理伺服器(Proxy Server)。一個完整的代理請求過程為:使用者端首先與代理伺服器建立連線,接著根據代理伺服器所使用的代理協定,請求對目標伺服器建立連線、或者獲得目標伺服器的指定資源。

使用代理伺服器

WebMagic使用的代理物件是APIProxyProvider。代理由HttpClientDownloader設定。

24.png

ProxyProvider有一個預設實現:SimpleProxyProvider。它是一個基於簡單Round-Robin的、沒有失敗檢查的ProxyProvider。可以設定任意個候選代理,每次會按順序挑選一個代理使用。它適合用在自己搭建的比較穩定的代理的場景。

如果需要根據實際使用情況對代理伺服器進行管理(例如校驗是否可用,定期清理、新增代理伺服器等),只需要自己實現APIProxyProvider

在Spider啟動入口處設定代理

    public void Process() {
        // 建立下載器Downloader
        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
        // 給下載器設定代理伺服器資訊
        Proxy proxy = new Proxy("183.166.148.28", 64305);
        httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(proxy));
        Spider.create(new ProxyTest())
                // 淘寶獲取本機IP地址
                .addUrl("https://www.taobao.com/help/getip.php")
                .setDownloader(httpClientDownloader)
                .run();
    }
登入後複製

Scheduler元件

概述

Scheduler是WebMagic中進行URL管理的元件。

Scheduler包括兩個作用:

對待抓取的URL佇列進行管理。

對已抓取的URL進行去重。

WebMagic內建了幾個常用的Scheduler。如果只是在本地執行規模比較小的爬蟲,那麼基本無需客製化Scheduler

25.png

對Scheduler的內部實現進行了重構,去重部分被單獨抽象成了一個介面:DuplicateRemover,從而可以為同一個Scheduler選擇不同的去重方式,以適應不同的需要,目前提供了兩種去重方式。

26.png

所有預設的Scheduler都使用HashSetDuplicateRemover來進行去重,除了RedisScheduler。

RedisScheduler是使用Redis的set進行去重,其他的Scheduler預設都使用HashSetDuplicateRemover來進行去重。

如果URL較多,使用HashSetDuplicateRemover會比較佔用記憶體,可嘗試BloomFilterDuplicateRemover

使用布隆過濾器

布隆過濾器 (Bloom Filter)是一種space efficient的概率型資料結構,用於判斷一個元素是否在集合中。在垃圾郵件過濾的黑白名單方法、爬蟲(Crawler)的網址判重模組中等等經常被用到。

雜湊表也能用於判斷元素是否在集合中,但是布隆過濾器只需要雜湊表的1/8或1/4的空間複雜度就能完成同樣的問題。

布隆過濾器可以插入元素,但不可以刪除已有元素。其中的元素越多,誤報率越大,但是漏報是不可能的。

原理:

布隆過濾器需要的是一個位陣列(和點陣圖類似)和K個對映函數(和Hash表類似),在初始狀態時,對於長度為m的位陣列array,它的所有位被置0。

如果要使用BloomFilter,必須要加入以下依賴:

 <!--WebMagic對布隆過濾器的支援-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>
登入後複製

新增布隆過濾器

public static void main(String[] args) {
    Spider.create(new JobProcessor())
            //初始存取url地址
            .addUrl("https://github.com/code4craft")
            .addPipeline(new FilePipeline("D:/webmagic/"))
            .setScheduler(new QueueScheduler()
            .setDuplicateRemover(new BloomFilterDuplicateRemover(10000000))) //引數設定需要對多少條資料去重
            .thread(1)//設定執行緒數
            .run();
}
登入後複製
    public boolean isDuplicate(Request request, Task task) {
        boolean isDuplicate = this.bloomFilter.mightContain(this.getUrl(request));
        if (!isDuplicate) {
            this.bloomFilter.put(this.getUrl(request));
            this.counter.incrementAndGet();
        }
        return isDuplicate;
    }
登入後複製

開啟布隆過濾器BloomFilterDuplicateRemover,在isDuplicate方法處可斷點驗證

27.png

對比

HashSet

使用java中的HashSet不能重複的特點去重。優點是容易理解。使用方便。缺點:佔用記憶體大,效能較低。

Redis去重

使用Redis的set進行去重。優點是速度快(Redis本身速度就很快),而且去重不會佔用爬蟲伺服器的資源,可以處理更巨量資料量的資料爬取。缺點:需要準備Redis伺服器,增加開發和使用成本。

布隆過濾器(BloomFilter)

使用布隆過濾器也可以實現去重。優點是佔用的記憶體要比使用HashSet要小的多,也適合大量資料的去重操作。缺點:有誤判的可能。沒有重複可能會判定重複,但是重複資料一定會判定重複。

布隆過濾器的實現

public class BloomFilter {
    /**
     * BitSet初始分配2^24個bit
     */
    private static final int DEFAULT_SIZE = 1 << 24;
    /**
     * 不同雜湊函數的種子,一般應取質數
     */
    private static final int[] seeds = new int[]{5, 7, 11, 13, 31, 37};
    private BitSet bits = new BitSet(DEFAULT_SIZE);
    /**
     * 雜湊函數物件
     */
    private SimpleHash[] func = new SimpleHash[seeds.length];
    public BloomFilter() {
        for (int i = 0; i < seeds.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
        }
    }
    /**
     * 將url標記到bits中
     *
     * @param str
     */
    public void add(String str) {
        for (SimpleHash f : func) {
            bits.set(f.hash(str), true);
        }
    }
    /**
     * 判斷是否已經被bits標記
     *
     * @param str
     * @return
     */
    public boolean contains(String str) {
        if (StringUtils.isBlank(str)) {
            return false;
        }
        boolean ret = true;
        for (SimpleHash f : func) {
            ret = ret && bits.get(f.hash(str));
        }
        return ret;
    }
    /**
     * 雜湊函數類
     */
    public static class SimpleHash {
        private int cap;
        private int seed;
        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }
        /**
         * hash函數,採用簡單的加權和hash
         *
         * @param value
         * @return
         */
        public int hash(String value) {
            int result = 0;
            int len = value.length();
            for (int i = 0; i < len; i++) {
                result = seed * result + value.charAt(i);
            }
            return (cap - 1) & result;
        }
    }
}
登入後複製

Pipeline元件

概述

Pileline是抽取結束後,進行處理的部分,它主要用於抽取結果的儲存,也可以客製化Pileline可以實現一些通用的功能。

Pipeline是將PageProcessor抽取的結果,繼續進行處理,在Pipeline中完成的功能,基本上也可以直接在PageProcessor實現。

Pipeline的介面定義

public interface Pipeline {
    // ResultItems儲存了抽取結果,它是一個Map結構
    // 在page.putField(key,value)中儲存的資料,可以通過ResultItems.get(key)獲取
    public void process(ResultItems resultItems, Task task);
}
登入後複製

Pipeline存在原因

為了模組分離。「頁面抽取」和「後處理、持久化」是爬蟲的兩個階段,將其分離開來,一個是程式碼結構比較清晰,另一個是以後也可能將其處理過程分開,分開在獨立的執行緒以至於不同的機器執行。

Pipeline的功能比較固定,更容易做成通用元件。每個頁面的抽取方式千變萬化,但是後續處理方式則比較固定,例如儲存到檔案、儲存到資料庫這種操作,這些對所有頁面都是通用的。

常用Pipeline

WebMagic中就已經提供了控制檯輸出、儲存到檔案、儲存為JSON格式的檔案几種通用的Pipeline。

28.png

在WebMagic裡,一個Spider可以有多個Pipeline,使用Spider.addPipeline()即可增加一個Pipeline。

public static void main(String[] args) {
    Spider.create(new GithubRepoPageProcessor())
            // 初始存取url地址
            .addUrl("https://github.com/code4craft")
            .addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
            .addPipeline(new FilePipeline("D:\\webmagic\\"))
            //開啟5個執行緒抓取
            .thread(5)
            //啟動爬蟲
            .run();
}
登入後複製

自定義Pipeline

基本Pipeline模式

在抽取的時候,將需要的資料儲存為一個物件

public void process(Page page) {
DemoData  demoData=new DemoData();
demoData.setName("pipeline")
        page.putField("demoData", demoData);
}
登入後複製

建立自定義Pipeline,在Pipeline中,只要使用即可

@Component
public class MyDataPipeline  implements Pipeline {
    @Autowired
    private DemoService demoService;
    @Override
    public void process(ResultItems resultItems, Task task) {
        // 獲取封裝好的資料
        DemoData demoData= resultItems.get("demoData");
        if (demoData!= null) {
            // 把資料儲存到資料庫中
            this.demoService.save(demoData);
        }
    }
}
登入後複製

註解模式

註解模式下,WebMagic內建了一個PageModelPipeline

public interface PageModelPipeline<T> {
    // 傳入的是處理好的物件
    public void process(T t, Task task);
}
登入後複製

註解模式的入口是OOSpider,它繼承了Spider類,提供了特殊的建立方法。建立一個註解模式的爬蟲需要一個或者多個Model類,以及一個或者多個PageModelPipeline——定義處理結果的方式。

 public static void main(String[] args) {
        OOSpider.create(Site.me().setSleepTime(1000)
                        , new MyPipeline(), DemoData.class)
               //.addPageModel(new MyPipeline2(),DemoData2.class)
                .addUrl("https://github.com/code4craft").thread(5).run();
    }
登入後複製

自定義MyPipeline類實現PageModelPipeline

@Component
public class MyPipeline implements PageModelPipeline<DemoData> {
    @Autowired
    private DemoService demoService;
    @Override
    public void process(DemoData demoData, Task task) {
        if (demoData!= null) {
            // 把資料儲存到資料庫中
            this.demoService.save(demoData);
        }
    }
}
登入後複製

推薦學習:《》

以上就是Java爬蟲框架之WebMagic的學習總結的詳細內容,更多請關注TW511.COM其它相關文章!