今天有个网友在博客回帖,能不能用注解来写一个爬虫?想了想,因为Javaer总习惯结果有个对象Model(我在自己用的时候也是这样),ResultItems的key-value形式难免会有点麻烦,何不将抽取和Model合为一体呢?好了!现在爬osc博客只有这点代码了!而且这个对象本身是可以继续使用的!
@TargetUrl("http://my.oschina.net/flashsword/blog/*")public class OschinaBlog { @ExtractBy("//title") private String title; @ExtractBy(value = "div.BlogContent",type = Fetcher.Type.Css) private String content; @ExtractBy(value = "//div[@class='BlogTags']/a/text()", multi = true) private Listtags;}
这里这个TargetUrl有两个意思:一个是符合这个url的交由这个Page处理,另一个是符合这样的url都会被抓取。怎么样?是不是很巧妙?另外为了方便,这里使用了类似正则的通配符形式(其实最终会编译成正则,只不过把"."转义成了"\.")。
考虑到一些更复杂的情况,例如:一个页面可能对应多个实体类(列表页视图),后面又做了一些更新,比如下面是一段抽取oschina问答的所有回答的代码:
@TargetUrl("http://www.oschina.net/question/\\d+_\\d+*")@HelpUrl("http://www.oschina.net/question/*")@ExtractBy(value = "//ul[@class='list']/li[@class='Answer']", multi = true)public class OschinaAnswer{ @ExtractBy("//img/@title") private String user; @ExtractBy("//div[@class='detail']") private String content; public static void main(String[] args) { OOSpider.create(Site.me().addStartUrl("http://www.oschina.net/question/567527_120597"), OschinaAnswer.class).run(); }}
咦?当自己写完这堆处理代码的时候,才发现webmagic完全变成了的一个注解版本。再看了一下Spiderman的,人家还内置了一套表达式引擎!
没关系,回到设计的初衷上来。Spiderman的设计是一个All-in-one的框架,提倡不写代码;而webmagic则是一个easy to build-in的框架,目标就是用代码实现爬虫,只不过少写点代码。不支持表达式引擎?没关系!复杂的逻辑,咱们可以写代码嘛!于是很简单的定义了一个AfterExtractor
接口,在抽取完后,会调用这个接口:
@TargetUrl("http://my.oschina.net/flashsword/blog/*")public class OschinaBlog implements AfterExtractor { @ExtractBy("//title") private String title; @ExtractBy(value = "div.BlogContent", type = ExtractBy.Type.Css) private String content; @ExtractBy(value = "//div[@class='BlogTags']/a/text()", multi = true) private Listtags; @Override public void afterProcess(Page page) { System.out.println("title:\t"+title); System.out.println("content:\t"+content); System.out.println("tags:\t" + tags); } public static void main(String[] args) { OOSpider.create(Site.me().addStartUrl("http://my.oschina.net/flashsword/blog/145796"), OschinaBlog.class).run(); }}
public void afterProcess(Page page) 这个函数能做PageProcessor所有能做的事情。我想这段代码也不用咱说明了吧。个人还是比较满意的。
使用这个方式,结合,轻易实现了结果持久化到数据库的任务,代码:。
值得一提的是,以上代码都没有修改底层的核心模块划分,以前手写PageProcessor的方式依然是有效的。看来一个良好的前期规划是很有必要的!
代码目前在 annotation分支上,后续测试稳定后回合并到主干。