[原創]一款基於Reactor線程模型的java網絡爬蟲框架


AJSprider

github: https://github.com/zhuchangwu/AJSpider

概述

AJSprider是筆者基於Reactor線程模式+Jsoup+HttpClient封裝的一款輕量級java多線程網絡爬蟲框架,簡單上手,小白也能玩爬蟲,
使用本框架,只需要關注如何解析(提供了無腦的匹配取值方法),而不必關心線程的調度,源碼的下載;

本項目僅供學習使用,禁止任何人用它非法盈利

目前是第一冊測試版本,可以使用,后續會進行規整,簡化使用

坐標

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.zhuchangwu</groupId>
    <artifactId>AJSpider</artifactId>
    <version>1.0.0.SNAPSHOT</version>
</dependency>

使用說明

使用方法簡單的沒商量,三步打完收工

  • 在自己的項目中引入坐標
  • 繼承SpiderSingleThreadExecutor<T>實現它的抽象方法
  • 在main方法,創建啟動器類SpiderBootStrap完成爬蟲的啟動

ok,現在進行第二步,重寫SpiderSingleThreadExecutor<T>的抽象方法,他有兩個抽象方法,子類必須實現,如下:

解析:

  • 入參1: var1是框架根據url下載下來的String類型的html的源碼,需要用戶把這里面需要的屬性從html中解析下來封裝進新創建的java對象中
  • 入參2: var2是框架自定義的容器,里面存放着兩個集合
    • 集合1: 盛放用戶在第一步新創建的對象並且已經付好值的對象
    • 集合2: 盛放需要下載二級任務 (比如,拿新聞來說,新聞的標題在url1上,點擊標題查看新聞體進入的新的url算作是二級任務)
  • 入參3: 框架提供的工具類,輔助第一步的解析
  • 返回值: 將入參位置的容器返回
protected abstract SpiderSingleThreadExecutor<T>.SpiderContainer<T> resolution1(String var1, SpiderSingleThreadExecutor<T>.SpiderContainer<T> var2, SpiderResolutionUtil var3);

解析拓展:

如果用戶存在二級任務,需要用戶重寫SpiderSingleThreadExecutorresolution2,使用方式和resolution1相同

  • 入參1: 存放的是 根據用戶在resolution1()中放入容器的url集合批量下載的對應的html源碼
  • 入參2: spiderContainer是用戶在resolution1()中返回的容器
  • 入參3: 工具類,輔助用戶將入參1html數組中的源碼,解析進容器中的bean集合中
  • 返回值: 將入參2返回
protected SpiderSingleThreadExecutor<T>.SpiderContainer<T> resolution2(String[] htmls, SpiderSingleThreadExecutor<T>.SpiderContainer<T> spiderContainer, SpiderResolutionUtil util) {
}

持久化

  • 入參1: 是用戶自己解析並封裝的容器中的bean集合
  • 入參2: 工具類,輔助持久化
public abstract void persistence(List<T> var1, PersistenceUtil<T> var2);

啟動爬蟲

創建啟動器對象

  • 添加任務隊列
  • 初始化線程執行器組
    • 入參1: 開啟的線程數(不填,默認是2*CPU核數)
    • 入參2: 用戶自定義的SpiderSingleThreadExecutor的實現類
new SpiderBootStrap()
            .setTaskUrlQueue(taskQueue)
            .initThreadExcutorGroup(1,MyExecutor.class)
            .build();

完整Demo-拉取新聞

快捷鍵F12,觀察需要爬取的網頁的源碼,DIY解析過程(使用提供的輔助類基礎的解析都ok,當然你是一個正則大牛,按自己的解析方式也很好)

public class MyThreadExcutor extends SpiderSingleThreadExecutor<News> {

protected SpiderContainer<News> resolution1(String s, SpiderContainer<News> spiderContainer, SpiderResolutionUtil spiderResolutionUtil) {

    // 觀察上圖,我需要的新聞信息在一個id為wp_news_w6的div下
    // 選擇如下方法,根據id以及標簽名獲取出li的數組
    String[] lis = spiderResolutionUtil.getElementsByIdAndTaggetName(s, "wp_news_w6", "ul", "li");

    // 大家一定要注意, 解析的步驟是一遍遍歷上面的數組,一遍解析它,每次循環都創建一個新的對象盛放解析出來的字段
    for (int i = 0; i < lis.length; i++) {
        String html = lis[i];
        News qluNew = new News();

        // 使用工具方法,把用戶提供的 前后綴 之間的值取出來
        // 注意了, 這里的前后綴一定得是先把源碼輸出到控制台,再復制過來
        String title = spiderResolutionUtil.getValueByPrefixSuffix(html, "\">", "</a></span> <span class=");
        String time = spiderResolutionUtil.getValueByPrefixSuffix(html, "<span class=\"news_meta\">", "</span> </li>");
        String url1 = spiderResolutionUtil.getValueByPrefixSuffix(html, "class=\"news_title\"><a href=\"", "\" target=\"_blank\"");


        // 將解析出來的值存放在用戶創建出來的對象中
        qluNew.setTitle(title);
        qluNew.setDate(time);

        // 大家可以看到上面的圖片,只用標題,時間,新聞體在二級url中,需要用戶在這里完成拼接
        String perfix = "http://www.qlu.edu.cn";
        String targetUrl = perfix + url1;



        // 推薦大家在解析拼接二級url時多加幾層判斷,保證二級url的正確性
        // 我們學校的新聞模塊,就存在使用中不同的url的現象, 拼接出來的效果是這個樣"http://www.qlu.edu.cnhttp://2019sdh.qlu.edu.cn/2019/0305/c7334a122472/page.htm";
        // 當時也挺蒙的,不過在700條新聞中,大概存在5條
        // 我的處理是直接跳過這個url, 如果不處理,框架根據錯誤的url下載,解析就終止了
        if (targetUrl.substring(5, targetUrl.length()).contains("http:")) {
            //  說明上面的url拼接從新處理這個 url
            //  url不同很大程度上意味着  resolution2()  按照不同的模板解析
            continue; 
        }

        //最后,別忘了創建的bean添加的容器中,往后傳播
        // 第一步
        spiderContainer.getBeanList().add(qluNew);
        // 第二步
        spiderContainer.getUrlList().add(perfix + url1);

    }
    // 返回容器
    return spiderContainer;
}

resolution2() 並不是抽象方法,只有當存在二級任務時,用戶選擇實現

    @Override
    protected SpiderContainer<News> resolution2(String[] htmltxt, SpiderContainer<News> spiderContainer, SpiderResolutionUtil util) {
        
        // 遍歷入參1中的下載好了的源碼, 從新解析出新聞體的新的字段放入容器中的bean集合
        for (int i = 0; i < htmltxt.length; i++) {
            String body = util.getFirstElementValueByClass(htmltxt[i], "wp_articlecontent");
            spiderContainer.getBeanList().get(i).setBody(body);
        }

        for (News news : spiderContainer.getBeanList()) {
            System.out.println("Thread.name = "+Thread.currentThread().getName()+news);
        }
        // 返回容器
        return spiderContainer;
    }

持久化,用戶根據自己的需求,選擇如何持久化, list中存放的是前面用戶解析出來的bean的集合


    // persistenceUtil可以持久化圖片到本地,前提是bean中僅有一個圖片的url字段
    public void persistence(List<News> list, PersistenceUtil<News> persistenceUtil) {

    }
}

啟動:

public static void main(String[] args) {

    // 創建任務隊列, 任意隊列都可以,不要求線程安全
    LinkedBlockingQueue<String> taskQueue = new LinkedBlockingQueue();
    // 假設在准備任務
        String url ="http://www.xxx.edu.cn/38/list.htm";
        taskQueue.offer(url);

        for (int i=2;i<50;i++){
        String url2 = "http://www.xxx.edu.cn/38/list"+i+".htm";
        taskQueue.offer(url2);
        }

    SpiderBootStrap spiderBootStrap = new SpiderBootStrap();
    spiderBootStrap
            .initThreadExcutorGroup(10,MyThreadExcutor.class)
            .setTaskUrlQueue(taskQueue)
            .build();
}

重要的事情說三遍

使用工具方法,需要的 前后綴 是需要從編譯器的控制台復制過來的,直接賦值網頁上的無效

使用工具方法,需要的 前后綴 是需要從編譯器的控制台復制過的,直接賦值網頁上的無效

使用工具方法,需要的 前后綴 是需要從編譯器的控制台復制過的,直接賦值網頁上的無效


筆者水平有限,請大佬批評指教!, 有任何issue請聯系筆者, 如果您覺得還不錯,歡迎star


免責聲明!

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



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