[原創]一款基於Reactor執行緒模型的java網路爬蟲框架
阿新 • • 發佈:2019-07-30
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);
解析拓展:
如果使用者存在二級任務,需要使用者重寫SpiderSingleThreadExecutor
的resolution2
,使用方式和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請聯絡筆者, 如果您覺得還不錯,歡迎s