WebMagic 爬虫框架 注解用法

JAVA herman 1103浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

自从java注解出来之后,就无框架不注解。同样的 WebMagic 爬虫框架也支持注解的方式实现网页的抓取,今天为大家分享一下 WebMagic 爬虫的注解教程。

@TargetUrl和@HelpUrl

HelpUrl/TargetUrl是一个非常有效的爬虫开发模式,TargetUrl是我们最终要抓取的URL,最终想要的数据都来自这里;而HelpUrl则是为了发现这个最终URL,我们需要访问的页面。几乎所有垂直爬虫的需求,都可以归结为对这两类URL的处理:

  • 对于博客页,HelpUrl是列表页,TargetUrl是文章页。
  • 对于论坛,HelpUrl是帖子列表,TargetUrl是帖子详情。
  • 对于电商网站,HelpUrl是分类列表,TargetUrl是商品详情。

下面的例子中,TargetUrl是最终的项目页,而HelpUrl则是项目搜索页,它会展示所有项目的链接。

@TargetUrl("https://github.com/\\w+/\\w+")
@HelpUrl("https://github.com/\\w+")
public class GithubRepo {
    ……
}

TargetUrl中的自定义正则表达式

这里我们使用的是正则表达式来规定URL范围。可能细心的朋友,会知道.是正则表达式的保留字符,那么这里是不是写错了呢?其实是这里为了方便,WebMagic自己定制的适合URL的正则表达式,主要由两点改动:

  • 将URL中常用的字符.默认做了转义,变成了\.
  • 将"*"替换成了".*",直接使用可表示通配符。

例如,https://github.com/*在这里是一个合法的表达式,它表示https://github.com/下的所有URL。

在WebMagic中,从TargetUrl页面得到的URL,只要符合TargetUrl的格式,也是会被下载的。所以即使不指定HelpUrl也是可以的——例如某些博客页总会有“下一篇”链接,这种情况下无需指定HelpUrl。

sourceRegion

TargetUrl还支持定义sourceRegion,这个参数是一个XPath表达式,指定了这个URL从哪里得到——不在sourceRegion的URL不会被抽取。

@ExtractBy

@ExtractBy是一个用于抽取元素的注解,它描述了一种抽取规则。@ExtractBy注解主要作用于字段,它表示“使用这个抽取规则,将抽取到的结果保存到这个字段中”。例如:

@ExtractBy("//div[@id='readme']/text()")
private String readme;

这里"//div[@id=’readme’]/text()"是一个XPath表示的抽取规则,而抽取到的结果则会保存到readme字段中。

除了XPath,还可以使用其他抽取方式来进行抽取,包括CSS选择器、正则表达式和JsonPath,在注解中指明type之后即可。

@ExtractBy(value = "div.BlogContent", type = ExtractBy.Type.Css)
private String content;

notnull

@ExtractBy包含一个notNull属性,如果熟悉mysql的同学一定能明白它的意思:此字段不允许为空。如果为空,这条抽取到的结果会被丢弃。对于一些页面的关键性属性(例如文章的标题等),设置notnull为true,可以有效的过滤掉无用的页面。notNull默认为false。

@ExtractByUrl

@ExtractByUrl是一个单独的注解,它的意思是“从URL中进行抽取”。它只支持正则表达式作为抽取规则。

在类上使用ExtractBy

在之前的注解模式中,我们一个页面只对应一条结果。如果一个页面有多个抽取的记录呢?例如在“QQ美食”的列表页面,我想要抽取所有商户名和优惠信息,该怎么办呢?
在类上使用@ExtractBy注解可以解决这个问题。
在类上使用这个注解的意思很简单:使用这个结果抽取一个区域,让这块区域对应一个结果。

@TargetUrl("http://meishi.qq.com/beijing/c/all[\\-p2]*")
@ExtractBy(value = "//ul[@id=\"promos_list2\"]/li",multi = true)
public class QQMeishi {
    @ExtractBy("//div[@class=info]/a[@class=title]/h4/text()")
    private String shopName;
    @ExtractBy("//div[@class=info]/a[@class=title]/text()")
    private String promo;
    public static void main(String[] args) {
        OOSpider.create(Site.me(), new ConsolePageModelPipeline(), QQMeishi.class).addUrl("http://meishi.qq.com/beijing/c/all").thread(4).run();
    }
}

@Formatter

@Formatter可以将抽取到的内容,自动转换成一些基本类型,而无需手动使用代码进行转换。

@Formatter("yyyy-MM-dd HH:mm")
@ExtractBy("//div[@class='BlogStat']/regex('\\d+-\\d+-\\d+\\s+\\d+:\\d+')")
private Date date;

自定义Formatter

实际上,除了自动类型转换之外,Formatter还可以做一些结果的后处理的事情。例如,我们有一种需求场景,需要将抽取的结果作为结果的一部分,拼接上一部分字符串来使用。在这里,我们定义了一个StringTemplateFormatter。

public class StringTemplateFormatter implements ObjectFormatter<String> {
    private String template;
    @Override
    public String format(String raw) throws Exception {
        return String.format(template, raw);
    }
    @Override
    public Class<String> clazz() {
        return String.class;
    }
    @Override
    public void initParam(String[] extra) {
        template = extra[0];
    }
}

那么,我们就能在抽取之后,做一些简单的操作了!

@Formatter(value = "author is %s",formatter = StringTemplateFormatter.class)
@ExtractByUrl("https://github\\.com/(\\w+)/.*")
private String author;

AfterExtractor

有的时候,注解模式无法满足所有需求,我们可能还需要写代码完成一些事情,这个时候就要用到AfterExtractor接口了。

public interface AfterExtractor {
    public void afterProcess(Page page);
}

afterProcess方法会在抽取结束,字段都初始化完毕之后被调用,可以处理一些特殊的逻辑。

//TargetUrl的意思是只有以下格式的URL才会被抽取出生成model对象
//这里对正则做了一点改动,'.'默认是不需要转义的,而'*'则会自动被替换成'.*',因为这样描述URL看着舒服一点...
//继承jfinal中的Model
//实现AfterExtractor接口可以在填充属性后进行其他操作
@TargetUrl("http://my.oschina.net/flashsword/blog/*")
public class OschinaBlog extends Model<OschinaBlog> implements AfterExtractor {
    //用ExtractBy注解的字段会被自动抽取并填充
    //默认是xpath语法
    @ExtractBy("//title")
    private String title;
    //可以定义抽取语法为Css、Regex等
    @ExtractBy(value = "div.BlogContent", type = ExtractBy.Type.Css)
    private String content;
    //multi标注的抽取结果可以是一个List
    @ExtractBy(value = "//div[@class='BlogTags']/a/text()", multi = true)
    private List<String> tags;
    @Override
    public void afterProcess(Page page) {
        //jfinal的属性其实是一个Map而不是字段,没关系,填充进去就是了
        this.set("title", title);
        this.set("content", content);
        this.set("tags", StringUtils.join(tags, ","));
        //保存
        save();
    }
    public static void main(String[] args) {
        C3p0Plugin c3p0Plugin = new C3p0Plugin("jdbc:mysql://127.0.0.1/blog?characterEncoding=utf-8", "blog", "password");
        c3p0Plugin.start();
        ActiveRecordPlugin activeRecordPlugin = new ActiveRecordPlugin(c3p0Plugin);
        activeRecordPlugin.addMapping("blog", OschinaBlog.class);
        activeRecordPlugin.start();
        //启动webmagic
        OOSpider.create(Site.me().addStartUrl("http://my.oschina.net/flashsword/blog/145796"), OschinaBlog.class).run();
    }
}

在WebMagic里,注解模式其实是完全基于webmagic-core中的PageProcessor和Pipeline扩展实现的,有兴趣的朋友可以去看看代码。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » WebMagic 爬虫框架 注解用法