webmagic的使用學習


Part.01 Webmagic介紹

webmagic是一個開源的Java垂直爬蟲框架,目標是簡化爬蟲的開發流程,讓開發者專注於邏輯功能的開發
WebMagic項目代碼分為核心和擴展兩部分

  • 核心部分(webmagic-core)是一個精簡的、模塊化的爬蟲實現,而擴展部分則包括一些便利的、實用性的功能。WebMagic的架構設計參照了Scrapy,目標是盡量的模塊化,並體現爬蟲的功能特點。這部分提供非常簡單、靈活的API,在基本不改變開發模式的情況下,編寫一個爬蟲
  • 擴展部分(webmagic-extension)提供一些便捷的功能,例如注解模式編寫爬蟲等。同時內置了一些常用的組件,便於爬蟲開發

Part.02 Webmagic設計原理

WebMagic的結構分為Downloader、PageProcessor、Scheduler、Pipeline四大組件,並由Spider將它們彼此組織起來。
WebMagic總體架構圖如下:

技術

WebMagic的四個組件

  • Downloader:Downloader負責從互聯網上下載頁面,以便后續處理。WebMagic默認使用了Apache HttpClient作為下載工具。
  • PageProcessor:PageProcessor負責解析頁面,抽取有用信息,以及發現新的鏈接。WebMagic使用Jsoup作為HTML解析工具,並基於其開發了解析XPath的工具Xsoup。在這四個組件中,PageProcessor對於每個站點每個頁面都不一樣,是需要使用者定制的部分。
  • Scheduler:Scheduler負責管理待抓取的URL,以及一些去重的工作。WebMagic默認提供了JDK的內存隊列來管理URL,並用集合來進行去重。也支持使用Redis進行分布式管理。除非項目有一些特殊的分布式需求,否則無需自己定制Scheduler。
  • 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處理。

Part.03 Webmagic 實例(爬取 筆趣閣&bilibili的數據)

webmagic使用maven管理依賴,在項目中添加對應的依賴即可使用webmagic

        <!-- web magic -->
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.7.3</version>
        </dependency>

        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.7.3</version>
        </dependency>

WebMagic 使用slf4j-log4j12作為slf4j的實現.如果你自己定制了slf4j的實現,請在項目中去掉此依賴

<exclusions>
    <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
</exclusions>

核心代碼

  • BilibiliReptile.java
package com.reptile.bilibili;

import com.mysql.dao.BilibiliDao;
import com.mysql.entity.Bilibili;

import com.mysql.pipeline.MysqlPipelineBilibili;
import com.tool.SplitJson;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;
import us.codecraft.webmagic.processor.PageProcessor;


import java.sql.SQLException;

import static com.reptile.json.GetHttpInterface.GetHttpInterface;

public class BilibiliReptile implements PageProcessor{

    //設置拼接的url變量
    //爬取av號從1至1000000
    private static int start =1;
    private static int end =1000000;

    //設置網站相關配置
    //重試次數和抓取間隔
    private Site site = Site.me().setRetryTimes(5).setSleepTime(0).setUserAgent("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36");

    public synchronized void process(Page page) {

        //視頻標題
        page.putField("title",page.getHtml().xpath("//h1[@class='video-title']/span/text()").get());
        //如果title為空則跳過
        if (page.getResultItems().get("title") == null) {
            page.setSkip(true);
        }

        //標題圖
        page.putField("image",page.getHtml().xpath("/html/head/meta[10]").get());

        //up
        page.putField("up",page.getHtml().xpath("//div[@class='name']/a[1]/text()").get());

        //簡介
        page.putField("info",page.getHtml().xpath("//div[@class='u-info']/div[2]/text()").get());

        //分p
        page.putField("part",page.getHtml().xpath("//*[@id=\"multi_page\"]/div[1]/div/span/text()").get());

        //時間戳
        page.putField("date",page.getHtml().xpath("//div/time/text()").get());
    }

    public Site getSite() {
        // TODO Auto-generated method stub
        return site;
    }

    public static void main(String[] args) throws SQLException {

        int id = 1;

        BilibiliDao bilidao = new BilibiliDao();
        Bilibili bilibili = new Bilibili();

        SplitJson sj = new SplitJson();

        while (start<end) {

            Spider.create(new BilibiliReptile()).addUrl("https://www.bilibili.com/video/av" + start + "/")
                    //輸出到控制台
                    .addPipeline(new ConsolePipeline())
                    //傳輸到數據庫
                   // .addPipeline(new MysqlPipelineBilibili())
                    //開啟5個線程抓取
                    .thread(5)
                    //啟動爬蟲
                    .run();

            String str = GetHttpInterface("https://api.bilibili.com/x/web-interface/archive/stat?aid=" + start);

            if((sj.splitCode(str)).equals("0"))
            {
                bilibili.setId(id);
                bilibili.setPlay(sj.splitView(str));
                bilibili.setBarrage(sj.splitDanmaku(str));
                bilidao.addData(bilibili);
                System.out.println(str);
                System.out.println("view:" + sj.splitView(str));
                System.out.println("danmuke:" + sj.splitView(str));
                id++;
            }

            start++;

        }
    }
}
  • BiQuGeReptile.java
package com.reptile.biquge;

import com.mysql.pipeline.MysqlPipelineBiQuGe;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;
import us.codecraft.webmagic.processor.PageProcessor;

import java.util.List;

public class BiQuGeReptile implements PageProcessor {

    //regex of URL:http://www.xbiquge.la/
    public static final String FIRST_URL = "http://www\\.xbiquge\\.la/\\w+";
    public static final String HELP_URL = "/\\d+/\\d+/";
    public static final String TARGET_URL = "/\\d+/\\d+/\\d+\\.html/";

    private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);

    public Site getSite() {
        // TODO Auto-generated method stub
        return site;
    }

    public void process(Page page) {


        if(page.getUrl().regex(FIRST_URL).match()){

            List<String> urls = page.getHtml().links().regex(HELP_URL).all();
            page.addTargetRequests(urls);

            //標題
            page.putField("title",page.getHtml().xpath("//div[@id='info']/h1/text()").get());
            //如果title為空則跳過
            if (page.getResultItems().get("title") == null) {
                page.setSkip(true);
            }

            //作者
            page.putField("author",page.getHtml().xpath("//div[@id='info']/p/text()").get());

            //簡介
            page.putField("info",page.getHtml().xpath("//div[@id='intro']/p[2]/text()").get());

            //首圖url
            page.putField("image",page.getHtml().xpath("//div[@id='fmimg']/img").get());

            //下一深度的網頁爬取章節和內容
            if(page.getUrl().regex(HELP_URL).match()){

                List<String> links = page.getHtml().links().regex(TARGET_URL).all();
                page.addTargetRequests(links);

                //章節
                page.putField("chapter", page.getHtml().xpath("//div[@class='bookname']/h1/text()").get());

                //內容
                page.putField("content", page.getHtml().xpath("//div[@id='content']/text()").get());
        }
        }
    }

    public static void main(String[] args){
        Spider.create(new BiQuGeReptile()).addUrl("http://www.xbiquge.la/xiaoshuodaquan/")
                //輸出到控制台
                .addPipeline(new ConsolePipeline())
                //傳輸到數據庫
               // .addPipeline(new MysqlPipelineBiQuGe())
                //開啟5個線程抓取
                .thread(5)
                //啟動爬蟲
                .run();
    }

}

注意事項

在0.7.3版本中,爬取只支持TLS1.2的https站點的時候會報錯:

javax.net.ssl.SSLException: Received fatal alert: protocol_version

解決辦法:https://github.com/code4craft/webmagic/issues/701


Part.04 Webmagic 拓展

URL 去重

Scheduler是WebMagic中進行 URL 管理的組件。一般來說,Scheduler包括兩個作用:

    對待抓取的URL隊列進行管理。
    對已抓取的URL進行去重。

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

    HashSet
        使用 java 中 HashSet 不能重復的特點去重。占用內存大,性能低
    Redis 去重
        使用 Redis 的 set 進行去重。優點是速度快,而且不會占用爬蟲服務器的資源。可以處理更大數據量的數據爬取;缺點是需要 redis 服務器,增加開發和使用成本
    布隆過濾器(BloomFilter)
        優點是占用內存比 HashSet 小的多,也適合大數據量的去重操作。

布隆過濾器的使用實例:

@Scheduled(initialDelay = 1000, fixedDelay = 60 * 1000 * 60 * 12)
    public void start() {
        Spider.create(new BdProcessor())
                .addUrl(URL)
                .thread(10)
                // 設置布隆過濾器去重操作(默認使用HashSet來進行去重,占用內存較大;使用BloomFilter來進行去重,占用內存較小,但是可能漏抓頁面)
                .setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)))
                .addPipeline(dbPipeline)
                .run();
    }

網頁去重

指紋碼對比

最常見的去重方案是生成文檔的指紋門。例如對一篇文章進行 MD5 加密生成一個字符串,我們可以認為這是文章的指紋碼,再和其他的文章指紋碼對比,一致則說明文章重復。

但是這種方式是完全一致則是重復的,如果文章只是多了幾個標點符號,那仍舊被認為是重復的,這種方式並不合理。

BloomFilter

這種方式就是我們之前對 url 進行去重的方式,使用在這里的話,也是對文章進行計算得到一個數,再進行對比,缺點和方法 1 是一樣的,如果只有一點點不一樣,也會認為不重復,這種方式不合理。

KMP 算法

KMP 算法是一種改進的字符串匹配算法。KMP 算法的關鍵是利用匹配失敗后的信息,盡量減少模式串與主串的匹配次數以達到快速匹配的目的。能夠找到兩個文章有哪些是一-樣的,哪些不一樣

這種方式能夠解決前面兩個方式的“只要一點不一樣就是不重復”的問題。但是它的時空復雜度太高了,不適合大數據量的重復比對

SimHash (主要)

Google 的 simhash 算法產生的簽名,可以滿足上述要求。這個算法並不深奧,比較容易理解。這種算法也是目前 Google 搜索引擎所目前所使用的網頁去重算法

    分詞,把需要判斷文本分詞形成這個文章的特征單詞。
    hash,通過 hash 算法把每個詞變成 hash 值,比如“美國”通過 hash 算法計算為 100101,“51 區”通過 hash 算法計算為 101011。這樣我們的字符串就變成了一串串數字。
    加權,通過 2 步驟的 hash 生成結果,需要按照單詞的權重形成加權數字串,。“美國”的 hash 值為“100101”,通過加權計算為“4-4-44-44”。“51 區”計算為‘“5-55-555”。
    合並,把上面各個單詞算出來的序列值累加,變成只有一一個序列串。。“美國”的“4-4-44-44”,“51 區”的“5-55-555”。

代理的使用

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

我們使用的 WebMagic 可以很方便的設置爬取數據的時間(參考第二天的的 3.1. 爬蟲的配置、啟動和終止)。但是這樣會大大降低我們 J 爬取數據的效率,如果不小心 ip 被禁了,會讓我們無法爬去數據,那么我們就有必要使用代理服務器來爬取數據。

代理 L(英語:Proxy),也稱網絡代理,是一-種特殊的網絡服務,允許一個網絡終端(一般為客戶端)通過這個服務與另一個網絡終端(一般為服務器)進行非直接的連接。

提供代理服務的電腦系統或其它類型的網絡終端稱為代理服務器(英文:Proxy. Server)。一個完整的代理請求過程為:客戶端首先與代理服務器創建連接,接着根據代理服務器所使用的代理協議,請求對目標服務器創建連接、或者獲得目標服務器的指定資源。

我們就需要知道代理服務器在哪里(ip 和端口號)才可以使用。網上有很多代理服務器的提供商,但是大多是免費的不好用,付費的還行。推薦個免費的服務網站:

配置代理

WebMagic的代理API ProxyProvider。因為相對於 Site 的“配置”,ProxyProvider定位更多是一個“組件”,所以代理不再從Site設置,而是由HttpClientDownloader設置。

  • 設置代理:HttpClientDownloader.setProxyProvider(ProxyProvider proxyProvider)

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

代理示例:

設置單一的普通HTTP代理為101.101.101.101的8888端口,並設置密碼為"username","password"

    HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
    httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("101.101.101.101",8888,"username","password")));
    spider.setDownloader(httpClientDownloader);
    HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
    httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(
    new Proxy("101.101.101.101",8888)
    ,new Proxy("102.102.102.102",8888)));

Part.05 總結

  • 關於Webmagic使用說明的總結

    • Webmagic屬於可快速上手的簡易爬蟲框架,在閱讀官方文檔后可快速上手開發,主要難點在於對於xpath(會正則的同學會很快就上手)的學習以及對於部分網站需要進行的cookie驗證、代理以及登陸驗證時有一定難度,對於部分動態渲染的前端頁面可通過Chrome內核內嵌代碼渲染的方式解決

Part.06 參考文獻

PS:確實覺得寫的很好,轉給大家分享,文中提到的一些操作自己打算日后試試



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM