注解开发简介
WebMagic支持使用独有的注解风格编写一个爬虫,引入webmagic-extension包即可使用此功能。
在注解模式下,使用一个简单对象加上注解,可以用极少的代码量就完成一个爬虫的编写。对于简单的爬虫,这样写既简单又容易理解,并且管理起来也很方便。
注解模式的开发方式是这样的:
1、首先定义你需要抽取的数据,并编写类。
2、在类上写明@TargetUrl注解,定义对哪些URL进行下载和抽取。
3、在类的字段上加上@ExtractBy注解,定义这个字段使用什么方式进行抽取。
4、定义结果的存储方式。
目标
爬取的网址为 http://blog.sina.com.cn/s/blog_58ae76e80100to5q.html 的内容
具体步骤
一、定义抽取的内容
编写Model类
一个github项目的名称、作者和简介三个信息
public class SinaBlogRepo {
private String title;
private String text;
private String date;
}
二、定义如何发现URL
@TargetUrl 和 @HelpUrl
HelpUrl/TargetUrl是一个非常有效的爬虫开发模式,TargetUrl是我们最终要抓取的URL,最终想要的数据都来自这里;而HelpUrl则是为了发现这个最终URL,我们需要访问的页面。几乎所有垂直爬虫的需求,都可以归结为对这两类URL的处理:
- TargetUrl是文章页,HelpUrl是列表页。
在这个例子中,TargetUrl是最终的项目内容页,HelpUrl则是项目列表页,它会展示所有项目的链接。
有了这些知识,我们就为这个例子定义URL格式:
@TargetUrl("http://blog.sina.com.cn/s/blog_\\w+.html")
public class SinaBlogRepo {
……
}
注意:TargetUrl中的自定义正则表达式
1、WebMagic自己定制的适合URL的正则表达式,主要由两点改动:
- 将URL中常用的字符 " . " 默认做了转义【 不需要用 " \. ", 可以直接使用 “.”】
- 将URL中常用的字符 " * " 默认做了转义 【直接使用 " * " 可表示通配符,不需要 " .* "】
例如,https://github.com/*在这里是一个合法的表达式,它表示https://github.com/下的所有URL。
在WebMagic中,从TargetUrl页面得到的URL,只要符合TargetUrl的格式,也是会被下载的。
所以即使不指定HelpUrl也是可以的——例如某些博客页总会有“下一篇”链接,这种情况下无需指定HelpUrl。
2、sourceRegion
TargetUrl还支持定义sourceRegion,这个参数是一个XPath表达式,指定了这个URL从哪里得到——不在sourceRegion的URL不会被抽取。
三、定义如何抽取元素(单)
1、 @ExtractBy
一个用于抽取元素的注解,它描述了一种抽取规则。
主要作用于字段,它表示“使用这个抽取规则,将抽取到的结果保存到这个字段中”。例如:
- 1、XPath
@ExtractBy(value = "//div[@class='articalTitle']/h2")
private String title;
这里"//div[@id=‘readme’]/text()"是一个XPath表示的抽取规则,而抽取到的结果则会保存到readme字段中。
- 2、其它
包括CSS选择器、正则表达式和JsonPath,在注解中指明type之后即可。
@ExtractBy(value = "div.BlogContent", type = ExtractBy.Type.Css)
private String content;
属性:notNull(默认为false)
@ExtractBy包含一个notNull属性,此字段不允许为空。如果为空,这条抽取到的结果会被丢弃。对于一些页面的关键性属性(例如文章的标题等),设置notnull为true,可以有效的过滤掉无用的页面。
2、 @ExtractByUrl
@ExtractByUrl是一个单独的注解,它的意思是“从URL中进行抽取”。
@ExtractByUrl("https://github\\.com/(\\w+)/.*")
private String author;
三*、定义如何在一个页面抽取多条结果
在类上使用@ExtractBy注解可以解决这个问题。
使用这个结果抽取一个区域,让这块区域对应一个结果。
@ExtractBy(value = "//ul[@id=\"promos_list2\"]/li")
public class QQMeishi {
……
}
四、结果的类型转换
Formatter机制
因为抽取到的内容总是String,而我们想要的内容则可能是其他类型。Formatter可以将抽取到的内容,自动转换成一些基本类型,而无需手动使用代码进行转换。
直接定义元素时候,定义成所需的元素,WebMagic可以完成自动转换。
@ExtractBy("//ul[@class='pagehead-actions']/li[1]//a[@class='social-count js-social-count']/text()")
private int star;
- 自动转换支持的类型
自动转换支持所有基本类型和装箱类型
- java.util.Date类型的转换。
在转换时,需要指定Date的格式。格式按照JDK的标准来定义,具体规范可以看这里
@Formatter("yyyy-MM-dd HH:mm")
@ExtractBy("//div[@class='BlogStat']/regex('\\d+-\\d+-\\d+\\s+\\d+:\\d+')")
private Date date;
自定义Formatter(TODO)
实际上,除了自动类型转换之外,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;
五、爬虫的创建和启动
1、OOSpider
注解模式的入口是OOSpider,它继承了Spider类,提供了特殊的创建方法,其他的方法是类似的。创建一个注解模式的爬虫需要一个或者多个Model类,以及一个或者多个PageModelPipeline——定义处理结果的方式。
public static OOSpider create(Site site, PageModelPipeline pageModelPipeline, Class... pageModels);
2、PageModelPipeline
注解模式下,处理结果的类叫做PageModelPipeline,通过实现它,你可以自定义自己的结果处理方式。
public interface PageModelPipeline<T> {
public void process(T t, Task task);
}
PageModelPipeline与Model类是对应的,多个Model可以对应一个PageModelPipeline。可以通过addPageModel方法,在添加一个Model的同时,可以添加一个PageModelPipeline。
public OOSpider addPageModel(PageModelPipeline pageModelPipeline, Class... pageModels)
完整代码:
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.model.ConsolePageModelPipeline;
import us.codecraft.webmagic.model.OOSpider;
import us.codecraft.webmagic.model.annotation.ExtractBy;
import us.codecraft.webmagic.model.annotation.ExtractByUrl;
import us.codecraft.webmagic.model.annotation.HelpUrl;
import us.codecraft.webmagic.model.annotation.TargetUrl;
import us.codecraft.webmagic.pipeline.FilePipeline;
import us.codecraft.webmagic.pipeline.JsonFilePageModelPipeline;
//在类上写明@TargetUrl注解,定义对哪些URL进行下载和抽取。
@TargetUrl("http://blog.sina.com.cn/s/blog_\\w+.html")
// @HelpUrl("http://blog\\.sina\\.com\\.cn/s/articlelist_1487828712_0_\\d+\\.html")
public class SinaBlogRepo {
//在类的字段上加上@ExtractBy注解,定义这个字段使用什么方式进行抽取。
@ExtractBy(value = "//div[@class='articalTitle']/h2")
private String title;
@ExtractBy("//div[@id='articlebody']//div[@class='articalContent']")
private String text;
@ExtractBy("//div[@class='articalTitle']/h2")
private String date;
public static void main(String[] args) {
SinaBlogRepo ans = OOSpider.create(Site.me(), SinaBlogRepo.class).<SinaBlogRepo>get("http://blog.sina.com.cn/s/blog_58ae76e80100to5q.html");
// OOSpider.create(Site.me().setSleepTime(1000), new ConsolePageModelPipeline(), SinaBlogRepo.class).addUrl("http://blog.sina.com.cn/s/blog_58ae76e80100to5q.html").thread(5).run();
System.out.println(ans);
}
public String getTitle() {
return title;
}
public String getText() {
return text;
}
public String getDate() {
return date;
}
@Override
public String toString() {
return "GithubRepo{" +
"title='" + title + '\'' +
", text='" + text + '\'' +
", date='" + date + '\'' +
'}';
}
}
部分打印结果:
问题:WebMagic抓HTTPS时出现SSLExceptio
解决方法见:这里
修改源码中的类 buildSSLConnectionSocketFactory
SSLContext sc = SSLContext.getInstance("TLS");//将SSLv3改为TLS
参考文献
- http://webmagic.io/docs/zh/posts/ch5-annotation/
- https://blog.csdn.net/yan3013216087/article/details/84398608