Gecco爬虫框架-利用Javassist动态生成SpiderBean

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.htmlBean("com.geccocrawler.gecco.demo.dynamic.MyGithub", false)

.gecco("https://github.com/xtuhcy/gecco", "consolePipeline")

.field("title").htmlField(".repository-meta-content").text(false).build()

.field("star").htmlField(".pagehead-actions li:nth-child(2) .social-count").text(false).build()

.field("fork").htmlField(".pagehead-actions li:nth-child(3) .social-count").text().build()

.field("contributors").htmlField("ul.numbers-summary > li:nth-child(4) > a").href().build()

.field("request").request().build()

.field("user").requestParameter("user").build()

.field("project").requestParameter().build()

.loadClass();

以上动态注解的添加等同于:

@Gecco(matchUrl="https://github.com/xtuhcy/gecco", 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.htmlBean("com.geccocrawler.gecco.demo.dynamic.MyGithub2", true)、

.gecco("https://github.com/xtuhcy/gecco", "consolePipeline")

.field("title", FieldType.stringType).htmlField(".repository-meta-content").text(false).build()

.field("star", FieldType.intType).htmlField(".pagehead-actions li:nth-child(2) .social-count").text(false).build()

.field("fork", FieldType.intType).htmlField(".pagehead-actions li:nth-child(3) .social-count").text().build()

.loadClass();

以上方法等同于创建了一个这样的类:

@Gecco(matchUrl="https://github.com/xtuhcy/gecco", pipelines="consolePipeline")
public class MyGithub2 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...

}

SpiderBean完全自动生成时Pipeline的写法

由于SpiderBean在运行时才生成,因此Pipeline:

public void process(T bean)

在编译器不知道SpiderBean的存在,这种请求我们通常将SpiderBean转为JsonObject来进行处理:

@Override
public void process(SpiderBean bean) {
    JSONObject jo = JSON.parseObject(JSON.toJSONString(bean));
    HttpRequest currRequest = (HttpGetRequest)JSON.toJavaObject(jo.getJSONObject("request"), HttpGetRequest.class);
    //下一页继续抓取
    int currPage = jo.getIntValue("currPage");
    int nextPage = currPage + 1;
    int totalPage = jo.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

更复杂的Deom可以参考Gecco的源码中关于动态SpiderBean的代码,位于com.geccocrawler.gecco.demo.dynamic包下,类JDDynamic是一个JD列表页的抓取例子,通过动态的方式,在没有任何SpiderBean的定义情况下将列表(分页)抓取下来。

 

动态生成SpiderBean是采用javassist进行字节码编程,有兴趣的朋友可以fork源码详细查看,核心的代码位于com.geccocrawler.gecco.annotation.dynamic包下。

你可能感兴趣的:(爬虫,javassist,gecco,字节码编程)