基于Crawler4j的WEB爬蟲
一、WEB爬蟲介紹
爬蟲,Crawler,最早被用于搜尋引擎收錄頁面,例如百度蜘蛛等等。說簡單點,原理就是根據一些規則,擷取url和頁面,再從擷取到的頁面中繼續提取url,一直進行下去。
現在爬蟲不僅僅用于搜尋引擎抓取頁面,也大量用于資料分析、資料挖掘等方面,在大資料的今天,爬蟲的作用越來越重要。WEB爬蟲的具體作用可以參考以下知乎上的一篇文章:
有哪些網站用爬蟲爬取能得到很有價值的資料?
當然隻是擷取到資料往往是不夠的,還要對資料進行分析,提取出有用的、有價值的資訊,這才是爬蟲的正真目的。
二、Crawler4j介紹
爬蟲工具有很多,比如nutch等等,就不再這裡列舉了,可以參考以下一篇文章:
GitHub 上有哪些優秀的 Java 爬蟲項目?
Crawler4j是一個Java版的多線程爬蟲工具,簡單易用。以下是Crawler4j的github:
yasserg/crawler4j · GitHub
通過官方的示例就能很快寫出一個簡單的爬蟲。而且它的配置也很簡單和實用。
三、使用Crawler4j擷取URL和HTML
現在我們來用Crawler4j實作一個簡單的爬蟲(以下代碼摘自官方github,并做了部分解釋)。
首先要引入依賴,我比較喜歡用maven,是以依賴如下:
<dependency>
<groupId>edu.uci.ics</groupId>
<artifactId>crawler4j</artifactId>
<version>4.4.0</version>
</dependency>
然後定義一個我們自己的爬蟲,隻需要繼承WebCrawler即可:
public class MyCrawler extends WebCrawler {
//定義抓取規則,這裡過濾了css、js等等非html的字尾
private final static Pattern FILTERS = Pattern.compile(".*(\\.(css|js|gif|jpg"
+ "|png|mp3|mp4|zip|gz))$");
//shouldVisit,應當被擷取的url
@Override
public boolean shouldVisit(Page referringPage, WebURL url) {
String href = url.getURL().toLowerCase();
return !FILTERS.matcher(href).matches()
&& href.startsWith("http://www.ics.uci.edu/");
}
//當擷取到比對的URL時,進行處理,我們可以在這裡寫我們的處理邏輯
@Override
public void visit(Page page) {
String url = page.getWebURL().getURL();
System.out.println("URL: " + url);
if (page.getParseData() instanceof HtmlParseData) {
HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
String text = htmlParseData.getText();
String html = htmlParseData.getHtml();
Set<WebURL> links = htmlParseData.getOutgoingUrls();
System.out.println("Text length: " + text.length());
System.out.println("Html length: " + html.length());
System.out.println("Number of outgoing links: " + links.size());
}
}
}
這樣我們就建立好了一個可以隻擷取html連結的爬蟲,但是它還是死的,需要一個類來啟動和配置其他的抓取規則。
public class Controller {
public static void main(String[] args) throws Exception {
//爬蟲狀态存儲檔案夾,可以從這裡邊讀取資料,以邊恢複之前的爬取狀态
String crawlStorageFolder = "/data/crawl/root";
//爬蟲數量,也就是線程數,一般不超過CPU線程數
int numberOfCrawlers = 7;
//爬蟲配置
CrawlConfig config = new CrawlConfig();
config.setCrawlStorageFolder(crawlStorageFolder);
/*
* Instantiate the controller for this crawl.
*/
PageFetcher pageFetcher = new PageFetcher(config);
RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcher);
CrawlController controller = new CrawlController(config, pageFetcher, robotstxtServer);
//要爬取的起始位址
controller.addSeed("http://www.ics.uci.edu/~lopes/");
controller.addSeed("http://www.ics.uci.edu/~welling/");
controller.addSeed("http://www.ics.uci.edu/");
//啟動
controller.start(MyCrawler.class, numberOfCrawlers);
}
}
四、擷取多線程爬蟲資料
Crawler4J是多線程的,是以就設計到多線程下的如何收集資料的問題。
好在Crawler4j為我們提供了一個方法,可以傳回一個線程結束的時候收集到的資料:
/**
* The CrawlController instance that has created this crawler instance will
* call this function just before terminating this crawler thread. Classes
* that extend WebCrawler can override this function to pass their local
* data to their controller. The controller then puts these local data in a
* List that can then be used for processing the local data of crawlers (if needed).
*
* @return currently NULL
*/
public Object getMyLocalData() {
return null;
}
大意如下:
建立此爬行器執行個體的爬行控制器執行個體将在終止此爬行器線程之前調用此函數。擴充WebRe爬行器的類可以重寫該函數,以将它們的本地資料傳遞給它們的控制器。然後控制器将這些本地資料放在一個清單中,然後該清單可以用來處理爬蟲的本地資料(如果需要的話)。
是以我們可以傳入一個list或者map來存放這些資料:
public class HtmlCrawler extends WebCrawler {
private Map<String,Page> map;
public HtmlCrawler() {
this.map = new HashMap<>();
}
@Override
public void visit(Page page) {
if(page.getParseData() instanceof HtmlParseData) {
String url = page.getWebURL().getURL();
log.debug("URL: {}", url);
page.setContentData(null);
this.map.put(url, page);
}
}
@Override
public Object getMyLocalData() {
return map;
}
}
我們以目前url為key,将擷取的到的資料page放入map,然後我們就可以在爬取結束的時候調用CarwlerController的getLocalData()來擷取這些資料。
五、測試版爬蟲系統
基于Crawler4j,我設計了一個簡單的帶界面的爬蟲系統,整合到我的個人部落格中,位址如下:
老吳 - 爬蟲
其中UserKey暫時為随機生成的字元串,也可以用唯一的固定值作為UserKey,這樣即使在關閉浏覽器的情況下,也會在背景自動抓取,并且當你在此輸入相同的UserKey時,将擷取到上一次的抓取結果。
因為伺服器資源有限,是以限制抓取頁面數量,并且會每半小時自動清理已經抓取完成但是使用者并沒有請求擷取的資料。
另外,當你選中生成報表時,抓取的資料将會生成一個excel表格,可以下載下傳到本地檢視。
