把玩爬蟲框架Gecco


如果你現在接到一個任務,獲取某某行業下的分類。

作為一個非該領域專家,沒有深厚的運營經驗功底,要提供一套擺的上台面且讓人信服的行業分類,恐怕不那么簡單。

找不到專家沒有關系,我們可以爬蟲。把那些專家的心血抽絲剝繭爬出來再統計即可。

確定好思路,我和即將要說的爬蟲框架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的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。


免責聲明!

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



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