如果你現在接到一個任務,獲取某某行業下的分類。
作為一個非該領域專家,沒有深厚的運營經驗功底,要提供一套擺的上台面且讓人信服的行業分類,恐怕不那么簡單。
找不到專家沒有關系,我們可以爬蟲。把那些專家的心血抽絲剝繭爬出來再統計即可。
確定好思路,我和即將要說的爬蟲框架Gecco打了一天的交道。
Gecco簡介
Gecco是一款用java語言開發的輕量化的易用的網絡爬蟲。Gecco整合了jsoup、httpclient、fastjson、spring、htmlunit、redission等優秀框架,讓您只需要配置一些jquery風格的選擇器就能很快的寫出一個爬蟲。Gecco框架有優秀的可擴展性,框架基於開閉原則進行設計,對修改關閉、對擴展開放。同時Gecco基於十分開放的MIT開源協議,無論你是使用者還是希望共同完善Gecco的開發者(摘自GitHub上的介紹)
主要特征
-
簡單易用,使用jquery風格的選擇器抽取元素
-
支持爬取規則的動態配置和加載
-
支持頁面中的異步ajax請求
-
支持頁面中的javascript變量抽取
-
利用Redis實現分布式抓取,參考gecco-redis
-
支持結合Spring開發業務邏輯,參考gecco-spring
-
支持htmlunit擴展,參考gecco-htmlunit
-
支持插件擴展機制
-
支持下載時UserAgent隨機選取
-
支持下載代理服務器隨機選取
GitHub:https://github.com/xtuhcy/gecco
中文參考手冊:http://www.geccocrawler.com/
同時GitHub上也提供了使用Gecco的實例,用於抓取京東商城分類以及分類下的商品信息。看到例子的第一眼就發現Gecco特別適合抓取這種分類以及分類下詳情列表的數據。
下面通過實例,邊實戰邊說明Gecco的用法。
Gecco爬取分類數據
爬取思路
首先明確爬取的種子網站:http://news.iresearch.cn/
爬取區域如下圖所示
爬取思路:先選取最上面的“互聯網+”分類,然后爬取下面的各個子分類(移動互聯網+電子商務+互聯網+網絡銷售+網絡游戲),再爬取各個子分類下的所有文章,最后提取所有文章的文本信息(提取文本后需要使用IKanalyzer或ansj分詞,然后進行詞頻統計,本篇不做詳述)。
編寫爬蟲啟動入口
我新建的是maven項目,所以要使用Gecco,第一步是添加maven依賴
<dependency>
<groupId>com.geccocrawler</groupId>
<artifactId>gecco</artifactId>
<version>1.0.8</version>
</dependency>
然后編寫一個main函數作為爬蟲的入口
public class Main {
public static void main(String[] rags) {
System.out.println("=======start========");
HttpGetRequest startUrl = new HttpGetRequest("http://news.iresearch.cn/");
startUrl.setCharset("GBK");
GeccoEngine.create()
//Gecco搜索的包路徑
.classpath("com.crawler.gecco")
//開始抓取的頁面地址
.start(startUrl)
//開啟幾個爬蟲線程
.thread(1)
//單個爬蟲每次抓取完一個請求后的間隔時間
.interval(2000)
.run();
}
}
-
HttpGetRequest用於包裹種子網站,同時可以設置編碼,這里設置的是“GBK”(一開始當時沒有設置該參數時,爬出的文本都是亂碼的)
-
classpath是一個掃描路徑,類似於Spring中的component-scan,用於掃描注解的類。這里主要用於掃描注解“@Gecco”所在的類。
解析獲取所有子分類
package com.crawler.gecco;
import com.geccocrawler.gecco.annotation.Gecco;
import com.geccocrawler.gecco.annotation.HtmlField;
import com.geccocrawler.gecco.annotation.Request;
import com.geccocrawler.gecco.request.HttpRequest;
import com.geccocrawler.gecco.spider.HtmlBean;
import java.util.List;
/**
* Created by jackie on 18/1/15.
*/
@Gecco(matchUrl="http://news.iresearch.cn/", pipelines={"consolePipeline", "allSortPipeline"})
public class AllSort implements HtmlBean {
private static final long serialVersionUID = 665662335318691818L;
@Request
private HttpRequest request;
// 移動互聯網
@HtmlField(cssPath="#tab-list > div:nth-child(1)")
private List<Category> mobileInternet;
// 電子商務
@HtmlField(cssPath="#tab-list > div:nth-child(2)")
private List<Category> electric;
// 互聯網
@HtmlField(cssPath="#tab-list > div:nth-child(3)")
private List<Category> internet;
// 網絡營銷
@HtmlField(cssPath="#tab-list > div:nth-child(4)")
private List<Category> netMarket;
// 網絡游戲
@HtmlField(cssPath="#tab-list > div:nth-child(5)")
private List<Category> netGame;
public List<Category> getMobileInternet() {
return mobileInternet;
}
public void setMobileInternet(List<Category> mobileInternet) {
this.mobileInternet = mobileInternet;
}
public List<Category> getElectric() {
return electric;
}
public void setElectric(List<Category> electric) {
this.electric = electric;
}
public List<Category> getInternet() {
return internet;
}
public void setInternet(List<Category> internet) {
this.internet = internet;
}
public List<Category> getNetMarket() {
return netMarket;
}
public void setNetMarket(List<Category> netMarket) {
this.netMarket = netMarket;
}
public List<Category> getNetGame() {
return netGame;
}
public void setNetGame(List<Category> netGame) {
this.netGame = netGame;
}
public HttpRequest getRequest() {
return request;
}
public void setRequest(HttpRequest request) {
this.request = request;
}
}
-
雖然代碼很長,但是除去set和get方法,剩下的就是獲取子分類標簽的代碼
-
注解@Gecco告知該爬蟲匹配的url格式(matchUrl)和內容抽取后的bean處理類(pipelines處理類采用管道過濾器模式,可以定義多個處理類),這里matchUrl就是http://news.iresearch.cn/,意為從這個網址對應的頁面中解析
-
這里pipelines參數可以添加多個管道處理類,意為下一步該執行哪些管道類,需要說明的是consolePipeline,是專門將過程信息輸出到控制台的管道類,后面會說明
-
注解@HtmlField表示抽取html中的元素,cssPath采用類似jquery的css selector選取元素
舉例說明,現在需要解析“移動互聯網”分類下所有的列表並將列表結果包裝為一個list,供后面進一步解析列表的具體內容
// 移動互聯網
@HtmlField(cssPath="#tab-list > div:nth-child(1)")
private List<Category> mobileInternet;
這里cssPath是用於指定需要解析的目標元素的css位置。
如何獲取這個區塊的位置,先看頁面
我們要獲取的是“移動互聯網”下的所有列表,並將其包裝為一個list集合。打開Chrome開發者工具,可以看到該列表模塊被div標簽包裹,只要定位到該模塊的位置即可。
如果通過人肉的方式獲取cssPath確實有點傷眼,所以我們可以使用Chrome自帶的工具獲取css路徑,在上圖箭頭所在位置右鍵,按照如下圖所示操作,粘貼即可得到cssPath
依次操作,可以獲取其他四個分類的分類列表。
獲取分類列表對應的url
通過上面的解析,我們得到了各個分類下的列表模塊。通過Chrome開發者工具,我們可以發現每個列表項包含的信息很少,我們不應該直接抓取這些僅有的文本做分析,這樣會漏掉很多文本信息。
所以,我們應該先定位解析出所有的href超鏈接,即每個列表項對應的文章詳情地址,然后解析文章詳情的所有文本信息。
所以這里的Category類如下
package com.crawler.gecco;
import com.geccocrawler.gecco.annotation.HtmlField;
import com.geccocrawler.gecco.annotation.Text;
import com.geccocrawler.gecco.spider.HrefBean;
import com.geccocrawler.gecco.spider.HtmlBean;
import java.util.List;
/**
* Created by jackie on 18/1/15.
*/
public class Category implements HtmlBean {
private static final long serialVersionUID = 3018760488621382659L;
@Text
@HtmlField(cssPath="dt a")
private String parentName;
@HtmlField(cssPath="ul li")
private List<HrefBean> categorys;
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public List<HrefBean> getCategorys() {
return categorys;
}
public void setCategorys(List<HrefBean> categorys) {
this.categorys = categorys;
}
}
categorys即用於手機某個分類下所有列表對應的網址
下面實現AllSortPipeline類,用於收集所有分類下的url
package com.crawler.gecco;
import com.geccocrawler.gecco.annotation.PipelineName;
import com.geccocrawler.gecco.pipeline.Pipeline;
import com.geccocrawler.gecco.request.HttpRequest;
import com.geccocrawler.gecco.scheduler.SchedulerContext;
import com.geccocrawler.gecco.spider.HrefBean;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jackie on 18/1/15.
*/
@PipelineName("allSortPipeline")
public class AllSortPipeline implements Pipeline<AllSort> {
@Override
public void process(AllSort allSort) {
System.out.println("-=======-");
List<Category> categorys = new ArrayList<Category>();
categorys.addAll(allSort.getInternet());
categorys.addAll(allSort.getElectric());
categorys.addAll(allSort.getMobileInternet());
categorys.addAll(allSort.getNetGame());
categorys.addAll(allSort.getNetMarket());
for(Category category : categorys) {
List<HrefBean> hrefs = category.getCategorys();
for(HrefBean href : hrefs) {
System.out.println("title: " + href.getTitle() + " url: " + href.getUrl());
String url = href.getUrl();
HttpRequest currRequest = allSort.getRequest();
SchedulerContext.into(currRequest.subRequest(url));
}
}
}
}
-
categorys集合用於添加所有分類下的列表
-
通過遍歷的方式獲取具體的url和每個url對應的title
-
將url信息存儲到SchedulerContext上下文中,用於后面爬蟲
到此為止,我們獲取了所有的分類列表對應的url信息,並將url存儲到上下文中,用於后續爬蟲匹配。下面編寫用於解析詳情也的處理類。
解析文章詳情
新建注解類ProductDetail,用於匹配上邊得到的url
package com.crawler.gecco;
import com.geccocrawler.gecco.annotation.*;
import com.geccocrawler.gecco.spider.HtmlBean;
/**
* Created by jackie on 18/1/15.
*/
@Gecco(matchUrl="http://news.iresearch.cn/content/{yeary}/{month}/{code}.shtml", pipelines={"consolePipeline", "productDetailPipeline"})
public class ProductDetail implements HtmlBean {
private static final long serialVersionUID = -377053120283382723L;
/**
* 文本內容
*/
// @Text
@HtmlField(cssPath="body > div.g-content > div.g-bd.f-mt-auto > div > div.g-mn > div > div.g-article > div.m-article")
private String content;
@RequestParameter
private String code;
@RequestParameter
private String year;
@RequestParameter
private String month;
/**
* 標題
*/
@Text
@HtmlField(cssPath="body > div.g-content > div.g-main.f-mt-auto > div > div > div.title > h1")
private String title;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
this.month = month;
}
}
-
matchUrl是每個文章的url格式,year、month和code是注入的參數
-
同理,我們定位到title所在的cssPath和 content所在的cssPath,用於解析得到具體的title和content值
下面實現ProductDetailPipeline類,用於解析每篇文章的文本信息,並通過正則抽取所有的中文文本存儲到result.txt中
package com.crawler.gecco;
import com.geccocrawler.gecco.annotation.*;
import com.geccocrawler.gecco.pipeline.Pipeline;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Created by jackie on 18/1/15.
*/
@PipelineName("productDetailPipeline")
public class ProductDetailPipeline implements Pipeline<ProductDetail> {
@Override
public void process(ProductDetail productDetail) {
System.out.println("~~~~~~~~~productDetailPipeline~~~~~~~~~~~");
File resultFile = new File("result.txt");
if (!resultFile.exists()) {
try {
resultFile.createNewFile();
} catch (IOException e) {
System.out.println("create result file failed: " + e);
}
}
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("result.txt", true);
} catch (IOException e) {
System.out.println("IOException");
}
try {
fileWriter.write(RegrexUtil.match(productDetail.getContent()));
fileWriter.flush();
} catch (IOException e) {
System.out.println("fileWriter.write failed: " + e);
} finally {
try {
fileWriter.close();
} catch (IOException e) {
System.out.println("fileWriter.close failed");
}
}
}
}
至此,我們通過Gecco獲取到了互聯網行業各分類下的所有文章,並提取到所有的文本信息。
結果如下
項目地址:https://github.com/DMinerJackie/tour-project
如有問題,可以下方留言
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。