1. 程式人生 > >DynamicGecco實現爬取規則的動態載入

DynamicGecco實現爬取規則的動態載入

Gecco爬蟲框架中的SpiderBean

Gecco是一個快速爬蟲開發框架,能讓開發人員快速的將爬取下來的頁面轉換為一個簡單的java bean。所有的java bean都需要繼承同一個介面SpiderBean。根據返回資料格式的不同可以將SpiderBean分成兩個子介面——HtmlBean和JsonBean。SpiderBean的定義通常如下:

@Gecco(matchUrl="...")
public class TestHtmlBean implements HtmlBean {

    @HtmlField(cssPath=".title")
    private String title;

    public void setTitle(String title) {
        this.title=title;
    }

    public String getTitle() {
        return title;
    }
}

@Gecco(matchUrl="...")
pulbic class TestJsonBean implements JsonBean {

    @JSONPath("$.title")
    private String title;

    public void setTitle(String title) {
        this.title=title;
    }

    public String getTitle() {
        return title;
    }
}

詳細的Gecco框架使用手冊可以參考這裡

為什麼要動態生成SpiderBean

  • 已經定義了ORM(如:hiberante)的bean,將註解動態的載入到ORM的bean中,可以很方便的將頁面格式化後入庫
  • 很多類似的網站的抓取,SpiderBean都一樣,只是提取元素的cssPath不一樣,為了不構建很多重複的SpiderBean,可以考慮動態生成SpiderBean
  • 通過配置的方式抓取頁面,通過後臺管理系統、配置檔案等配置抓取規則,動態的將配置規則轉換成SpiderBean
  • 利用動態SpiderBean可以構建視覺化爬蟲,利用視覺化工具構建抓取規則,將規則動態轉換為SpiderBean

動態生成SpiderBean的註解

這裡介紹bean已經存在的情況下,如何動態的將註解注入到bean中,程式碼如下:

//動態增加註解
DynamicGecco.html("com.geccocrawler.gecco.demo.dynamic.MyGithub", false)
.gecco("https://github.com/{user}/{project}", "consolePipeline")
.existField("title").htmlField(".repository-meta-content").text(false).build()
.existField("star").htmlField(".pagehead-actions li:nth-child(2) .social-count").text(false).build()
.existField("fork").htmlField(".pagehead-actions li:nth-child(3) .social-count").text().build()
.existField("contributors").htmlField("ul.numbers-summary > li:nth-child(4) > a").href().build()
.existField("request").request().build()
.existField("user").requestParameter("user").build()
.existField("project").requestParameter().build()
.register();

以上動態註解的新增等同於:

@Gecco(matchUrl="https://github.com/{user}/{project}", pipelines="consolePipeline")
public class MyGithub implements HtmlBean {

    @Request
    private HttpRequest request;

    @RequestParameter("user")
    private String user;

    @RequestParameter("project")
    private String project;

    @Text(own=false)
    @HtmlField(cssPath=".repository-meta-content")
    private String title;

    @Text(own=false)
    @HtmlField(cssPath=".pagehead-actions li:nth-child(2) .social-count")
    private int star;

    @Text
    @HtmlField(cssPath=".pagehead-actions li:nth-child(3) .social-count")
    private int fork;

    @Href
    @HtmlField(cssPath="ul.numbers-summary > li:nth-child(4) > a")
    private String contributors;

    ...setter/getter...

}

注意:這種情況下,由於要對SpiderBean的註解進行動態生成,所有不要將動態生成註解的方法放到任何SpiderBean類中,最好單獨寫一個新的類用來生成動態註解。

動態生成類、屬性和註解

這裡介紹的是如何在沒有任何Bean的情況下動態生成SpiderBean的全部內容,程式碼如下:

DynamicGecco.html()
.gecco("https://github.com/{user}/{project}", "consolePipeline")
.stringField("title", FieldType.stringType).htmlField(".repository-meta-content").text(false).build()
.intField("star", FieldType.intType).htmlField(".pagehead-actions li:nth-child(2) .social-count").text(false).build()
.intField("fork", FieldType.intType).htmlField(".pagehead-actions li:nth-child(3) .social-count").text().build()
.register();

以上方法等同於建立了一個這樣的類:

@Gecco(matchUrl="https://github.com/{user}/{project}", pipelines="consolePipeline")
public class MyGithub implements HtmlBean {

    @Text(own=false)
    @HtmlField(cssPath=".repository-meta-content")
    private String title;

    @Text(own=false)
    @HtmlField(cssPath=".pagehead-actions li:nth-child(2) .social-count")
    private int star;

    @Text
    @HtmlField(cssPath=".pagehead-actions li:nth-child(3) .social-count")
    private int fork;

    ...setter/getter...

}

JsonPipeline的使用

由於編譯器不知道執行時生成的SpiderBean的存在,這種請求我們通常將SpiderBean轉為JSONObject來進行處理,通過繼承JsonPipeline實現:

public class ProductListJsonPipeline extends JsonPipeline {

    @Override
    public void process(JSONObject productList) {
        HttpRequest currRequest = HttpGetRequest.fromJson(productList.getJSONObject("request"));
        //下一頁繼續抓取
        int currPage = productList.getIntValue("currPage");
        int nextPage = currPage + 1;
        int totalPage = productList.getIntValue("totalPage");
        if(nextPage <= totalPage) {
            String nextUrl = "";
            String currUrl = currRequest.getUrl();
            if(currUrl.indexOf("page=") != -1) {
                nextUrl = StringUtils.replaceOnce(currUrl, "page=" + currPage, "page=" + nextPage);
            } else {
                nextUrl = currUrl + "&" + "page=" + nextPage;
            }
            SchedulerContext.into(currRequest.subRequest(nextUrl));
        }
    }
}

Demo

全部demo位於原始碼下的com.geccocrawler.gecco.demo.dynamic包下,請感興趣的同學自行下載。