天天看點

Java爬蟲架構WebMagic簡介及使用一、介紹三、 基本的爬蟲

一、介紹

​ webmagic的是一個無須配置、便于二次開發的爬蟲架構,它提供簡單靈活的API,隻需少量代碼即可實作一個爬蟲。webmagic采用完全子產品化的設計,功能覆寫整個爬蟲的生命周期(連結提取、頁面下載下傳、内容抽取、持久化),支援多線程抓取,分布式抓取,并支援自動重試、自定義UA/cookie等功能。

二、概覽

​ WebMagic項目代碼分為核心和擴充兩部分。核心部分(webmagic-core)是一個精簡的、子產品化的爬蟲實作,而擴充部分則包括一些便利的、實用性的功能(例如注解模式編寫爬蟲等)。

​ WebMagic的結構分為Downloader、PageProcessor、Scheduler、Pipeline四大元件,并由Spider将它們彼此組織起來。這四大元件對應爬蟲生命周期中的下載下傳、處理、管理和持久化等功能。而Spider則将這幾個元件組織起來,讓它們可以互互相動,流程化的執行,可以認為Spider是一個大的容器,它也是WebMagic邏輯的核心。

  

WebMagic總體架構圖如下:

Java爬蟲架構WebMagic簡介及使用一、介紹三、 基本的爬蟲
2.1 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。

2.2 用于資料流轉的對象
  • Request

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

  • Page

    Page代表了從Downloader下載下傳到的一個頁面——可能是HTML,也可能是JSON或者其他文本格式的内容。Page是WebMagic抽取過程的核心對象,它提供一些方法可供抽取、結果儲存等。

  • ReusltItems

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

2.3 控制爬蟲運轉的引擎—Spider

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

​ 對于編寫一個爬蟲,PageProcessor是需要編寫的部分,而Spider則是建立和控制爬蟲的入口。

2.4 WebMagic項目組成

WebMagic項目代碼包括幾個部分,在根目錄下以不同目錄名分開。它們都是獨立的Maven項目。

WebMagic主要包括兩個包,這兩個包經過廣泛實用,已經比較成熟:

  • webmagic-core

    webmagic-core是WebMagic核心部分,隻包含爬蟲基本子產品和基本抽取器。

  • webmagic-extension

    webmagic-extension是WebMagic的主要擴充子產品,提供一些更友善的編寫爬蟲的工具。包括注解格式定義爬蟲、JSON、分布式等支援。

三、 基本的爬蟲

3.1 爬蟲的流程 (可以參考上邊的架構架構圖)
  1. Downloader-頁面下載下傳
    • 頁面下載下傳是一切爬蟲的開始。

      大部分爬蟲都是通過模拟http請求,接收并分析響應來完成。這方面,JDK自帶的HttpURLConnection可以滿足最簡單的需要,而Apache HttpClient(4.0後整合到HttpCompenent項目中)則是開發複雜爬蟲的不二之選。它支援自定義HTTP頭(對于爬蟲比較有用的就是User-agent、cookie等)、自動redirect、連接配接複用、cookie保留、設定代理等諸多強大的功能。

      webmagic使用了HttpClient 4.2,并封裝到了HttpClientDownloader。學習HttpClient的使用對于建構高性能爬蟲是非常有幫助的,官方的Tutorial就是很好的學習資料。目前webmagic對HttpClient的使用仍在初步階段,不過對于一般抓取任務,已經夠用了

  1. PageProcessor-頁面分析及連結抽取
    • 這裡說的頁面分析主要指HTML頁面的分析。頁面分析可以說是垂直爬蟲最複雜的一部分,在webmagic裡,PageProcessor是定制爬蟲的核心。通過編寫一個實作PageProcessor接口的類,就可以定制一個自己的爬蟲
    • HTML分析是一個比較複雜的工作,Java世界主要有幾款比較友善的分析工具:
      • Jsoup
      • HtmlParser
      • Apache tika
      • HtmlCleaner與Xpath
    • webmagic的Selector
      • Selector是webmagic為了簡化頁面抽取開發的獨立子產品,是整個項目中我最得意的部分。這裡整合了CSS Selector、XPath和正規表達式,并可以進行鍊式的抽取,很容易就實作強大的功能。即使你使用自己開發的爬蟲工具,webmagic的Selector仍然值得一試
  2. Scheduler-URL管理
    • URL管理的問題可大可小。對于小規模的抓取,URL管理是很簡單的。我們隻需要将待抓取URL和已抓取URL分開儲存,并進行去重即可。使用JDK内置的集合類型Set、List或者Queue都可以滿足需要。如果我們要進行多線程抓取,則可以選擇線程安全的容器,例如LinkedBlockingQueue以及ConcurrentHashMap。因為小規模的URL管理非常簡單,很多架構都并不将其抽象為一個子產品,而是直接融入到代碼中。但是實際上,抽象出Scheduler子產品,會使得架構的解耦程度上升一個檔次,并非常容易進行橫向擴充,這也是我從scrapy中學到的。
  1. Pipeline-離線處理和持久化
    • Pipeline其實也是容易被忽略的一部分。大家都知道持久化的重要性,但是很多架構都選擇直接在頁面抽取的時候将持久化一起完成,例如crawer4j。但是Pipeline真正的好處是,将頁面的線上分析和離線處理拆分開來,可以在一些線程裡進行下載下傳,另一些線程裡進行處理和持久化。
3.2 使用WebMagic爬取一個桌面網站

​ 首先引入WebMagic的依賴,webmagic-core-{version}.jar和webmagic-extension-{version}.jar。在項目中添加這兩個包的依賴,即可使用WebMagic。

  

maven中引入依賴jar包

<dependency>
    <groupId>us.codecraft</groupId>
    <artifactId>webmagic-core</artifactId>
    <version>0.5.3</version>
</dependency>
<dependency>
    <groupId>us.codecraft</groupId>
    <artifactId>webmagic-extension</artifactId>
    <version>0.5.3</version>
</dependency>12345678910
           

不使用maven的使用者,可以去http://webmagic.io中下載下傳最新的jar包。

3.3 爬蟲的實作
  1. 實作PageProcessor接口
    • 在WebMagic裡,實作一個基本的爬蟲隻需要編寫一個類,實作PageProcessor接口即可。這個類基本上包含了抓取一個網站,你需要寫的所有代碼
  1. 爬蟲的配置
    • 爬蟲相關配置,如抓取間隔、休息時間等
  1. 編寫爬蟲代碼
    • 這裡我們爬取的是一個桌面網站 https://wallhaven.cc/
    package photo;
    
    import us.codecraft.webmagic.Page;
    import us.codecraft.webmagic.Site;
    import us.codecraft.webmagic.Spider;
    import us.codecraft.webmagic.processor.PageProcessor;
    
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    public class GetPhoto implements PageProcessor {
    
        // 設定參數
        private Site site = Site.me().setRetryTimes(3).setSleepTime(1000).setTimeOut(3000);
    
        /**
         * 主方法啟動爬蟲
         */
        public static void main(String[] args) {
    
            // 這裡隻爬取第一頁的桌面,如果要爬取其他頁數修改for循環參數即可
            for (int i = 1; i <= 1; i++) {
                // 啟動爬蟲
                Spider.create(new GetPhoto())
                        // 添加初始化的URL
                        .addUrl("https://wallhaven.cc/toplist?page=" + i)
                        // 使用單線程
                        .thread(1)
                        // 運作
                        .run();
            }
        }
    
        /**
         * 頁面處理邏輯
         * 也就是通路主程式中的URL後得到的頁面
         *
         * 爬蟲思路:
         *      1. 主程式通路URL後得到頁面
         *      2. 将得到的頁面解析出需要的參數,并将解析出來并且需要爬取的連結放入爬取目标中 (45-59行)
         *          TIPS:WebMagic會自動去識别哪些連接配接爬取過哪些沒有。
         *      3. 通路第二步中放入的連結得到頁面,并解析(62行)
         *      4. 将圖檔的名字和字尾提取出來以便儲存(64-77行)
         */
        public void process(Page page) {
            // 一頁是24張圖檔
            for (int i = 1; i <= 24; i++) {
    
                // 使用Xpath解析,擷取到單個圖檔的網頁
                // WebMagic支援使用Xpath、CSS選擇器、正規表達式、JsonPath來解析頁面
                String str = page.getHtml().xpath("//div[@id=thumbs]/section/ul/li[" + i + "]/figure/a[@class=preview]/@href").toString();
    
                // 擷取到的連接配接為null則退出循環,不添加進爬取目标
                if (str == null)
                    break;
    
                // 将爬取到的連結添加到待爬取頁面中
                page.addTargetRequest(str);
            }
    
            // 将頁面中圖檔的位址提取出來,以便于使用工具類儲存
            String pageURL = page.getHtml().xpath("//img[@id=wallpaper]/@src").toString();
    
            if (pageURL != null) {
                try {
                    // 擷取圖檔的名字和字尾提取出來用于儲存
                    String name = pageURL.substring(pageURL.length() - 9, pageURL.length() - 4);
                    String suffix = pageURL.substring(pageURL.length() - 4);
    
                    // 将圖檔的位址、名字、儲存路徑傳入檔案工具類進行下載下傳
                    DownloadImage.downLoadFromUrl(pageURL, name + suffix, "src/file/");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
    
        public Site getSite() {
            return site;
        }
    }
    
    
               
  2. 儲存資源(工具類)
    package photo;
    
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    class DownloadImage {
    
        public static void downLoadFromUrl(String urlStr, String fileName, String savePath) throws IOException {
            URL url = new URL(urlStr);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 設定逾時間為3秒
            conn.setConnectTimeout(3 * 1000);
    
            // 防止屏蔽程式抓取而傳回403錯誤
            conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
    
            // 得到輸入流
            InputStream inputStream = conn.getInputStream();
    
            // 擷取自己數組
            byte[] getData = readInputStream(inputStream);
    
            // 檔案儲存位置
            File saveDir = new File(savePath);
            if (!saveDir.exists()) {
                saveDir.mkdir();
            }
    
            File file = new File(saveDir + File.separator + fileName);
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(getData);
            if (fos != null) {
                fos.close();
            }
    
            if (inputStream != null) {
                inputStream.close();
            }
    
            System.out.println("info:" + url + " download success");
    
        }
    
        private static byte[] readInputStream(InputStream inputStream) throws IOException {
            byte[] buffer = new byte[1024];
            int len = 0;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            while ((len = inputStream.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            bos.close();
            return bos.toByteArray();
        }
    }
    
               

參考連接配接:

https://blog.csdn.net/u012385190/article/details/51517466

https://my.oschina.net/flashsword/blog/145796#h4_7

http://webmagic.io/docs/zh/posts/ch1-overview/