一 简介
webmagic的是一个无须配置、便于二次开发的爬虫框架,它提供简单灵活的API,只需少量代码即可实现一个爬虫
webmagic采用完全模块化的设计,功能覆盖整个爬虫的生命周期(链接提取、页面下载、内容抽取、持久化),支持多线程抓取,分布式抓取,并支持自动重试、自定义UA/cookie等功能
webmagic包含强大的页面抽取功能,开发者可以便捷的使用css selector、xpath和正则表达式进行链接和内容的提取,支持多个选择器链式调用
注:官方中文文档:http://webmagic.io/docs/zh/
二 使用webmagic实现一个简单爬虫的全部步骤
(1)jar包下载:
可以使用maven构建依赖,如:
1 2 3 4 5 6 7 8 9 10 | <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.6.1</version> </dependency> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.6.1</version> </dependency> |
当然,也可以自行下载jar包,其地址是:http://webmagic.io/
(2)观察目标数据所处的HTML页面位置:
一般来说,如果我们需要抓取的目标数据不是通过ajax异步加载进来的话,那么我们都可以在页面的HTML源代码中的某个位置找到我们所需要的数据
注:如果数据是通过异步加载到页面中,那么一般有以下两种方式来获取数据:
- 观察页面加载完成之前请求的所有URL(F12 –> Network选项),然后找到加载数据的那些json请求,最后直接请求那些URL获取数据即可
- 模拟浏览器请求,等待一定时间之后从完全加载完成的页面中获取数据即可。这类爬虫通常需要内嵌浏览器内核,如:webmagic、phantom.js、HttpUnit等
下面,我将以抓取我博客首页的所有文章的标题、作者、文章链接、标签等信息举例说明:
可以发现,所有的文章都在class为“col-md-8”的section标签之下,每篇文章摘要都是一个“article”标签
从上图可以看出,我们是可以直观地从首页的HTML源代码中找到我们所需要的标题、作者、文章链接、标签等信息的。那么接下来我们可以通过哪种方式将这些目标数据提取出来呢?
其实,关于页面元素的抽取webmagic框架主要支持以下三种方式:
- XPath
- 正则表达式
- CSS选择器
当然,选择哪种方式来抽取数据需要根据具体页面具体分析。在这个例子中,很显然使用XPath来抽取数据是最方便的
注:关于XPath的一些基本概念可以参考以下内容:http://www.w3school.com.cn/xpath/index.asp
因此,接下来我就直接给出我们需要抓取的数据的所在XPath路径了:
- 文章摘要所有内容://article[@class=’well clearfix’]
- 文章标题://h1[@class=’entry-title’]/a/text()
- 文章链接://h1[@class=’entry-title’]/a/@href
- 作者://span[@class=’fa fa-user’]/a/text()
- 标签://div[@class=’pull-left footer-tag’]/a/text()
注:“//”表示从相对路径开始,最前面以“/”开始则表示从页面的跟路经开始;后面的两个/之间的内容表示一个元素,中括号里面的内容则表示该元素的执行属性,如:h1[@class=’entry-title’] 表示:拥有class属性为“entry-title”的h1元素
(3)页面数据抽取:
使用webmagic抽取页面数据时需要自定义一个类实现PageProcessor接口。这个实现了PageProcessor接口的类主要完成以下三个方面的工作:
- 爬虫的配置:抓取页面的相关配置,包括编码、抓取间隔、重试次数等
- 页面元素的抽取:使用正则表达式或者XPath等方式来抽取页面元素
- 新链接的发现:从一个页面发现待爬取的其他目标页面的链接
1 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package cn.zifangsky.webmagic; import java.util.List; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.Spider; import us.codecraft.webmagic.processor.PageProcessor; import us.codecraft.webmagic.selector.Html; public class FirstPageProcessor implements PageProcessor { // 抓取网站相关配置 private Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8"); @Override public Site getSite() { return site; } /** * 核心抽取逻辑 */ @Override public void process(Page page) { System.out.println("Requesting: " + page.getUrl()); List<String> list = page.getHtml().xpath("//article[@class='well clearfix']").all(); for(String header : list){ Html tmp = Html.create(header); // System.out.println(tmp); System.out.println(tmp.xpath("//h1[@class='entry-title']/a/text()").toString().trim()); System.out.println(tmp.xpath("//span[@class='fa fa-user']/a/text()").toString()); System.out.println(tmp.xpath("//h1[@class='entry-title']/a/@href").toString()); System.out.println(tmp.xpath("//div[@class='pull-left footer-tag']/a/text()").all().toString()); System.out.println("------------------------------"); } if (list.size() <=0) { // 忽略这个页面 page.setSkip(true); } //从页面发现后续的url地址来抓取 // page.addTargetRequests(page.getHtml().links().regex("(https://www.zifangsky.cn/page/\\d*)").all()); } public static void main(String[] args) { Spider.create(new FirstPageProcessor()) .addUrl("https://www.zifangsky.cn") .thread(5) .run(); } } |
这里的代码的核心逻辑在上面都已经说过了,因此就不多做解释了
(4)爬虫的启动与停止:
Spider是爬虫启动的入口。在启动爬虫之前,我们需要使用一个PageProcessor创建一个Spider对象,然后使用run()进行启动。同时Spider的其他组件(Downloader、Scheduler、Pipeline)都可以通过set方法来进行设置
注:更多详细参数介绍可以参考这里的官方文档:http://webmagic.io/docs/zh/posts/ch4-basic-page-processor/spider-config.html
(5)测试:
运行爬虫后输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | Requesting: https://www.zifangsky.cn 基于Spring的远程过程调用(RPC):RMI、Hessian/Burlap、Spring HttpInvoker四种实现方式详解 admin https://www.zifangsky.cn/847.html [Burlap, Hessian, RMI, RPC, Spring] ------------------------------ Quartz框架入门一:在Spring中集成Quartz的两种方式详解 admin https://www.zifangsky.cn/844.html [Quartz, Spring] ------------------------------ Redis集群(cluster)的安装与集群节点的基本操作 admin https://www.zifangsky.cn/831.html [Redis, 集群] ------------------------------ ... |
三 基于注解的webmagic爬虫配置
对于抽取逻辑比较复杂的爬虫我们通常像上面那样实现PageProcessor接口自己来写页面元素的抽取逻辑。但是对于抽取逻辑比较简单的爬虫来说,这时我们可以选择在实体类上添加注解的方式来构建轻量型的爬虫
(1)实体类的构造:
1 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | package cn.zifangsky.webmagic; import java.util.List; import us.codecraft.webmagic.model.annotation.ExtractBy; import us.codecraft.webmagic.model.annotation.TargetUrl; import us.codecraft.webmagic.model.annotation.ExtractByUrl; import us.codecraft.webmagic.model.annotation.HelpUrl; @TargetUrl(value="https://www.zifangsky.cn/\\d+.html") @HelpUrl(value="https://www.zifangsky.cn(/page/\\d*)?") public class ArticleExtra{ /** * 标题 */ @ExtractBy(value="//h1[@class='entry-title']/tidyText()",notNull=true) private String title; /** * 作者 */ @ExtractBy("//span[@class='fa fa-user']/a/text()") private String author; /** * 文章URL */ @ExtractByUrl("https://www.zifangsky.cn/\\d+.html") private String url; /** * 文章标签 */ @ExtractBy(value="//div[@class='footer-tag clearfix']/div/a/text()") private List<String> tags; /** * 文章正文 * @return */ @ExtractBy("//div[@class='entry-content clearfix']/tidyText()") private String content; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public List<String> getTags() { return tags; } public void setTags(List<String> tags) { this.tags = tags; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String toString() { return "ArticleExtra [title=" + title + ", author=" + author + ", url=" + url + ", tags=" + tags + ", content=" + content + "]"; } } |
从上面的代码可以看出,这里除了添加了几个注解之外,这个实体类就是一个普通的POJO,没有依赖其他任何东西。关于上面使用到的几个注解的大概含义是:
- @TargetUrl:我们需要抽取的数据所在的目标页面,其值是一个正则表达式
- @HelpUrl:为了得到目标页面的链接所需要访问的页面,这里也就是文章列表页
- @ExtractBy:一个用于抽取元素的注解,它描述了一种抽取规则。它表示“使用这个抽取规则,将抽取到的结果保存到这个字段中”。可以使用XPath、CSS选择器、正则表达式和JsonPath等方式来抽取元素
- @ExtractByUrl:其含义类似于@ExtractBy,不同的是它表示从URL中抽取
关于上面代码中的XPath规则我这里就不多做解释了,这里就留给大家自己在浏览器中多观察练习下吧,毕竟方法已经在上面介绍过了
(2)数据的持久化:
虽然在PageProcessor中我们可以实现数据的持久化(PS:基于注解的爬虫可以实现AfterExtractor 接口达到类似的目的),将爬虫抓取到的数据保存到文件、数据库、缓存等地方。但是很显然PageProcessor或者实体类主要负责的是页面元素的抽取工作,因此更好的处理方式是在另一个地方单独做数据的持久化。这个地方也就是——Pipeline
为了实现数据的持久化,我们通常需要实现Pipeline 或者PageModelPipeline接口。普通爬虫使用前一个接口,基于注解的爬虫则使用后一个接口
1 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 38 39 40 41 42 43 44 45 | package cn.zifangsky.webmagic; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import us.codecraft.webmagic.Task; import us.codecraft.webmagic.pipeline.PageModelPipeline; /** * 自定义Pipeline以实现数据的保存 * @author zifangsky * */ public class CustomPipeline2 implements PageModelPipeline<ArticleExtra> { @Override public void process(ArticleExtra articleExtra, Task task) { try { BufferedWriter writer = new BufferedWriter(new FileWriter(new File("C:/Users/Administrator/Desktop/article.txt"),true)); writer.write(articleExtra.getTitle().trim()); writer.newLine(); writer.write(articleExtra.getUrl()); writer.newLine(); writer.write(articleExtra.getAuthor()); writer.newLine(); writer.newLine(); // writer.write(articleExtra.getContent()); // writer.newLine(); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } } |
这里我们需要复写process( ) 方法中的内容,我们需要将获取到的数据保存到什么地方都将在这里完成。在上面示例中我将数据保存到了一个txt文件中
(3)爬虫的启动:
基于注解的爬虫,其启动类就不是Spider了,而是OOSpider类了,当然二者的使用方式类似。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 | @Test public void test(){ Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8"); OOSpider.create(site,new CustomPipeline2(), ArticleExtra.class) .addUrl("https://www.zifangsky.cn") // .addPipeline(new ConsolePipeline()) .thread(5) .run(); } |
运行这个单元测试之后,其结果文件中的内容是这样的:
至此,关于webmagic框架的基本使用就到此为止了。如果想要了解更多内容可以自行参考webmagic的官方文档
参考: