# Spring Boot + WebMagic 实现网页爬虫
# 实现 PageProcessor
- 爬虫的配置、页面元素的抽取和链接的发现
public class GithubRepoPageProcessor implements PageProcessor {
// 部分一:抓取网站的相关配置,包括编码、抓取间隔、重试次数等
private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
@Override
// process是定制爬虫逻辑的核心接口,在这里编写抽取逻辑
public void process(Page page) {
// 部分二:定义如何抽取页面信息,并保存下来
page.putField("author", page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString());
page.putField("name", page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()").toString());
if (page.getResultItems().get("name") == null) {
//skip this page
page.setSkip(true);
}
page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()"));
// 部分三:从页面发现后续的url地址来抓取
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/[\\w\\-]+/[\\w\\-]+)").all());
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
Spider.create(new GithubRepoPageProcessor())
//从"https://github.com/code4craft"开始抓
.addUrl("https://github.com/code4craft")
//开启5个线程抓取
.thread(5)
//启动爬虫
.run();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 爬虫的配置
第一部分关于爬虫的配置,包括编码、抓取间隔、超时时间、重试次数等,也包括一些模拟的参数,例如 User Agent、cookie,以及代理的设置,在这里我们先简单设置一下:重试次数为 3 次,抓取间隔为一秒。
# 页面元素的抽取
第二部分是爬虫的核心部分:对于下载到的 Html 页面,你如何从中抽取到你想要的信息?WebMagic 里主要使用了三种抽取技术:XPath、正则表达式和 CSS 选择器。另外,对于 JSON 格式的内容,可使用 JsonPath 进行解析。
- XPath
XPath 本来是用于 XML 中获取元素的一种查询语言,但是用于 Html 也是比较方便的。例如:
page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()")
这段代码使用了 XPath,它的意思是“查找所有 class 属性为'entry-title public'的 h1 元素,并找到他的 strong 子节点的 a 子节点,并提取 a 节点的文本信息”。
- CSS 选择器
CSS 选择器是与 XPath 类似的语言。如果大家做过前端开发,肯定知道\$('h1.entry-title')这种写法的含义。客观的说,它比 XPath 写起来要简单一些,但是如果写复杂一点的抽取规则,就相对要麻烦一点。
- 正则表达式
正则表达式则是一种通用的文本抽取语言。
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
这段代码就用到了正则表达式,它表示匹配所有https://github.com/code4craft/webmagic这样的链接。
- JsonPath
JsonPath 是于 XPath 很类似的一个语言,它用于从 Json 中快速定位一条内容。WebMagic 中使用的 JsonPath 格式可以参考这里:https://code.google.com/p/json-path/
# 链接的发现
有了处理页面的逻辑,我们的爬虫就接近完工了!
但是现在还有一个问题:一个站点的页面是很多的,一开始我们不可能全部列举出来,于是如何发现后续的链接,是一个爬虫不可缺少的一部分。
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
这段代码的分为两部分,page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all()用于获取所有满足"(https:/ /github\.com/\w+/\w+)"这个正则表达式的链接,page.addTargetRequests()则将这些链接加入到待抓取的队列中去。
# 使用 Selectable 抽取元素
相关的抽取元素链式 API 是 WebMagic 的一个核心功能。使用 Selectable 接口,你可以直接完成页面元素的链式抽取,也无需去关心抽取的细节。
page.getHtml()返回的是一个 Html 对象,它实现了 Selectable 接口。这个接口包含一些重要的方法,我将它分为两类:抽取部分和获取结果部分。
# 抽取部分 API
| 方法 | 说明 | 示例 |
|---|---|---|
xpath(String xpath) | 使用 XPath 选择 | html.xpath("//div[@class='title']") |
$(String selector) | 使用 Css 选择器选择 | html.$("div.title") |
$(String selector,String attr) | 使用 Css 选择器选择 | html.$("div.title","text") |
css(String selector) | 功能同$(),使用 Css 选择器选择 | html.css("div.title") |
links() | 选择所有链接 | html.links() |
regex(String regex) | 使用正则表达式抽取 | html.regex("\(.\*?)\") |
regex(String regex,int group) | 使用正则表达式抽取,并指定捕获组 | html.regex("\(.\*?)\",1) |
replace(String regex, String replacement) | 替换内容 | html.replace("\","") |
部分抽取 API 返回的都是一个 Selectable 接口,意思是说,抽取是支持链式调用的
# 获取结果的 API
当链式调用结束时,我们一般都想要拿到一个字符串类型的结果。这时候就需要用到获取结果的 API 了。我们知道,一条抽取规则,无论是 XPath、CSS 选择器或者正则表达式,总有可能抽取到多条元素
| 方法 | 说明 | 示例 |
|---|---|---|
| get() | 返回一条 String 类型的结果 | String link= html.links().get() |
| toString() | 功能同 get(),返回一条 String 类型的结果 | String link= html.links().toString() |
| all() | 返回所有抽取结果 | List links= html.links().all() |
| match() | 是否有匹配结果 | if (html.links().match()){ xxx; } |
# 使用 Pipeline 保存结果
通过“控制台输出结果”这件事也是通过一个内置的 Pipeline 完成的,它叫做 ConsolePipeline。那么,我现在想要把结果用 Json 的格式保存下来,怎么做呢?我只需要将 Pipeline 的实现换成"JsonFilePipeline"就可以了。
public static void main(String[] args) {
Spider.create(new GithubRepoPageProcessor())
//从"https://github.com/code4craft"开始抓
.addUrl("https://github.com/code4craft")
.addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
//开启5个线程抓取
.thread(5)
//启动爬虫
.run();
}
2
3
4
5
6
7
8
9
10
# 爬虫的配置、启动和终止
# Spider
Spider 是爬虫启动的入口。在启动爬虫之前,我们需要使用一个 PageProcessor 创建一个 Spider 对象,然后使用 run()进行启动。同时 Spider 的其他组件(Downloader、Scheduler、Pipeline)都可以通过 set 方法来进行设置。
| 方法 | 说明 | 示例 |
|---|---|---|
| create(PageProcessor) | 创建 Spider | Spider.create(new GithubRepoProcessor()) |
| addUrl(String…) | 添加初始的 URL | spider .addUrl("http://webmagic.io/docs/") |
| addRequest(Request...) | 添加初始的 Request | spider .addRequest("http://webmagic.io/docs/") |
| thread(n) | 开启 n 个线程 | spider.thread(5) |
| run() | 启动,会阻塞当前线程执行 | spider.run() |
| start()/runAsync() | 异步启动,当前线程继续执行 | spider.start() |
| stop() | 停止爬虫 | spider.stop() |
| test(String) | 抓取一个页面进行测试 | spider .test("http://webmagic.io/docs/") |
| addPipeline(Pipeline) | 添加一个 Pipeline,一个 Spider 可以有多个 Pipeline | spider .addPipeline(new ConsolePipeline()) |
| setScheduler(Scheduler) | 设置 Scheduler,一个 Spider 只能有个一个 Scheduler | spider.setScheduler(new RedisScheduler()) |
| setDownloader(Downloader) | 设置 Downloader,一个 Spider 只能有个一个 Downloader | spider .setDownloader(new SeleniumDownloader()) |
| get(String) | 同步调用,并直接取得结果 | ResultItems result = spider .get("http://webmagic.io/docs/") |
| getAll(String…) | 同步调用,并直接取得一堆结果 | List<ResultItems> results = spider .getAll("http://webmagic.io/docs/", "http://webmagic.io/xxx") |
# Site
对站点本身的一些配置信息,例如编码、HTTP 头、超时时间、重试策略等、代理等,都可以通过设置 Site 对象来进行配置。
| 方法 | 说明 | 示例 |
|---|---|---|
| setCharset(String) | 设置编码 | site.setCharset("utf-8") |
| setUserAgent(String) | 设置 UserAgent | site.setUserAgent("Spider") |
| setTimeOut(int) | 设置超时时间,单位是毫秒 | site.setTimeOut(3000) |
| setRetryTimes(int) | 设置重试次数 | site.setRetryTimes(3) |
| setCycleRetryTimes(int) | 设置循环重试次数 | site.setCycleRetryTimes(3) |
| addCookie(String,String) | 添加一条 cookie | site.addCookie("dotcomt_user","code4craft") |
| setDomain(String) | 设置域名,需设置域名后,addCookie 才可生效 | site.setDomain("github.com") |
| addHeader(String,String) | 添加一条 addHeader | site.addHeader("Referer","https://github.com") |
| setHttpProxy(HttpHost) | 设置 Http 代理 | site.setHttpProxy(new HttpHost("127.0.0.1",8080)) |
循环重试 cycleRetry 是 0.3.0 版本加入的机制。
该机制会将下载失败的 url 重新放入队列尾部重试,直到达到重试次数,以保证不因为某些网络原因漏抓页面。
# 爬虫的监控
为项目添加监控 添加监控非常简单,获取一个 SpiderMonitor 的单例 SpiderMonitor.instance(),并将你想要监控的 Spider 注册进去即可。你可以注册多个 Spider 到 SpiderMonitor 中。
public class MonitorExample {
public static void main(String[] args) throws Exception {
Spider oschinaSpider = Spider.create(new OschinaBlogPageProcessor())
.addUrl("http://my.oschina.net/flashsword/blog");
Spider githubSpider = Spider.create(new GithubRepoPageProcessor())
.addUrl("https://github.com/code4craft");
SpiderMonitor.instance().register(oschinaSpider);
SpiderMonitor.instance().register(githubSpider);
oschinaSpider.start();
githubSpider.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# WebMagic 已经提供的几个 Pipeline
WebMagic 中已经提供了将结果输出到控制台、保存到文件和 JSON 格式保存的几个 Pipeline:
| 类 | 说明 | 备注 |
|---|---|---|
| ConsolePipeline | 输出结果到控制台 | 抽取结果需要实现 toString 方法 |
| FilePipeline | 保存结果到文件 | 抽取结果需要实现 toString 方法 |
| JsonFilePipeline | JSON 格式保存结果到文件 | |
| ConsolePageModelPipeline | (注解模式)输出结果到控制台 | |
| FilePageModelPipeline | (注解模式)保存结果到文件 | |
| JsonFilePageModelPipeline | (注解模式)JSON 格式保存结果到文件 | 想要持久化的字段需要有 getter 方法 |