爬虫入门2—爬虫框架webmagic
爬虫入门1—谈谈网络爬虫
爬虫入门2—爬虫框架webmagic
爬虫入门3—爬虫实战
2 爬虫框架Webmagic
2.1 架构解析
WebMagic是一个简单灵活的Java爬虫框架。基于WebMagic,你可以快速开发出一个高效、易维护的爬虫。
WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件,并由Spider将它们彼此组织起来。这四大组件对应爬虫生命周期中的下载、处理、管理和持久化等功能。而Spider则将这几个组件组织起来,让它们可以互相交互,流程化的执行,可以认为Spider是一个大的容器,它也是WebMagic逻辑的核心。
四大组件
- Downloader Downloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了ApacheHttpClient作为下载工具。
- PageProcessor PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。
- Scheduler Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。
- Pipeline Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。
2.2 PageProcessor
2.2.1 爬取页面全部内容
下面手把手带大家搭建爬虫系统,爬取csdn中博客的内容 https://blog.csdn.net/
(1)创建工程,引入依赖
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic‐core</artifactId>
<version>0.7.3</version>
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic‐extension</artifactId>
<version>0.7.3</version>
</dependency>
(2)编写类实现网页内容的爬取
/**
* 爬取类
*/
public class MyProcessor implements PageProcessor {
public void process(Page page) {
//打印页面内容
System.out.println( page.getHtml().toString() );
}
public Site getSite() {
return Site.me().setSleepTime(100).setRetryTimes(3);
}
public static void main(String[] args) {
Spider.create( new MyProcessor() ).addUrl("https://blog.csdn.net").run();
}
}
Spider是爬虫启动的入口。在启动爬虫之前,我们需要使用一个PageProcessor创建一个Spider对象,然后使用run()进行启动。
同时Spider的其他组件(Downloader、Scheduler、Pipeline)都可以通过set方法来进行设置。
Page代表了从Downloader下载到的一个页面——可能是HTML,也可能是JSON或者其他文本格式的内容。Page是WebMagic抽取过程的核心对象,它提供一些方法可供抽取、结果保存等。
Site用于定义站点本身的一些配置信息,例如编码、HTTP头、超时时间、重试策略等、代理等,都可以通过设置Site对象来进行配置。
2.2.2 爬取指定内容(XPath)
如果我们想爬取网页中部分的内容,需要指定xpath。XPath,即为XML路径语言(XMLPathLanguage),它是一种用来确定XML文档中某部分位置的语言。XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
我们通过指定xpath来抓取网页的部分内容
System.out.println(page.getHtml().xpath("//*[@id=\"nav\"]/div/div/ul/li[5]/a").toString());
学习过前端css的童鞋可以看出,以上代码的含义:id为nav的节点下的div节点下的div节点下的ul下的第5个li节点下的a节点,看一下输出结果:
<a href="/nav/ai">人工智能</a>
2.2.3 添加目标地址
我们可以通过添加目标地址,从种子页面爬取到更多的页面
public void process(Page page) {
page.addTargetRequests(page.getHtml().links().regex("https://blog.csdn.net/[a-z 0-9 -]+/article/details/[0-9]{8}").all());//将当前页面里的所有链接都添加到目标页面中
System.out.println(page.getHtml().xpath("//*[@id=\"nav\"]/div/div/ul/li[5]/a" ).toString() );
}
运行后发现好多地址都出现在控制台
2.2.4 目标地址正则匹配
那如何只提取博客的文章详细页内容,并提取标题?
/**
* 爬取类
*/
public class MyProcessor implements PageProcessor {
public void process(Page page) {
//page.addTargetRequests( page.getHtml().links().all() );//将当前页面里的所有链接都添加到目标页面中
page.addTargetRequests( page.getHtml().links().regex("https://blog.csdn.net/[a-z 0-9 -]+/article/details/[0-9]{8}").all());
System.out.println(page.getHtml().xpath("//* [@id=\"mainBox\"]/main/div[1]/div[1]/h1/text()").toString());
}
public Site getSite() {
return Site.me().setSleepTime(100).setRetryTimes(3);
}
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net/")
.run();
}
}
2.3 Pipeline
2.3.1 ConsolePipeline 控制台输出
/**
* 爬取类
*/
public class MyProcessor implements PageProcessor {
public void process(Page page) {
//打印页面内容
//System.out.println( page.getHtml().toString() );
//page.addTargetRequests( page.getHtml().links().all() );
page.addTargetRequests( page.getHtml().links().regex("https://blog.csdn.net/[a-z 0-9 -]+/article/details/[0-9]{8}").all());
//System.out.println( page.getHtml().xpath("//*[@id=\"nav\"]/div/div/ul/li[5]/a" ).toString() );
//System.out.println(page.getHtml().xpath("//*[@id=\"mainBox\"]/main/div[1]/div/div/div[1]/h1"));
page.putField("title" ,page.getHtml().xpath("//*[@id=\"mainBox\"]/main/div[1]/div/div/div[1]/h1").toString() );
}
public Site getSite() {
return Site.me().setSleepTime(100).setRetryTimes(3);
}
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net/")
.addPipeline(new ConsolePipeline())
.run();
}
}
2.3.2 FilePipeline 文件保存
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net/")
.addPipeline(new ConsolePipeline())
.addPipeline(new FilePipeline("e:/data"))
.run();
}
2.3.3 JsonFilePipeline
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net/")
.addPipeline(new ConsolePipeline())
.addPipeline(new FilePipeline("e:/data"))
.addPipeline(new JsonFilePipeline("e:/json"))
.run();
}
2.3.4 定制Pipeline
如果以上Pipeline都不能满足你的需要,你可以定制Pipeline
(1)创建类MyPipeline实现接口Pipeline
public class MyPipeline implements Pipeline {
public void process(ResultItems resultItems, Task task) {
String title = resultItems.get("title");
System.out.println("定制 title:"+title);
}
}
(2)修改main方法
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net/")
.addPipeline(new ConsolePipeline())
.addPipeline(new FilePipeline("e:/data"))
.addPipeline(new JsonFilePipeline("e:/json"))
.addPipeline(new MyPipeline())
.run();
}
2.4 Scheduler
我们刚才完成的功能,每次运行可能会爬取重复的页面,这样做是没有任何意义的。Scheduler(URL管理) 最基本的功能是实现对已经爬取的URL进行标示。可以实现URL的增量去重。
目前scheduler主要有三种实现方式:
- 内存队列 QueueScheduler
- 文件队列 FileCacheQueueScheduler
- Redis队列 RedisScheduler
2.4.1 内存队列
使用setScheduler来设置Scheduler
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net/")
.setScheduler(new QueueScheduler())
.run();
}
2.4.2 文件队列
使用文件保存抓取URL,可以在关闭程序并下次启动时,从之前抓取到的URL继续抓取
(1)创建文件夹D:\scheduler
(2)修改代码
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net")
//.setScheduler(new QueueScheduler())//设置内存队列
.setScheduler(new FileCacheQueueScheduler("D:\\scheduler"))//设置文件队列
.run();
}
运行后文件夹D:\scheduler会产生两个文件blog.csdn.net.urls.txt和blog.csdn.net.cursor.txt
2.4.3 Redis队列
使用Redis保存抓取队列,可进行多台机器同时合作抓取,这就是里所谓的分布式爬虫
(1)运行redis服务端
(2)修改代码
public static void main(String[] args) {
Spider.create( new MyProcessor() )
.addUrl("https://blog.csdn.net")
//.setScheduler(new QueueScheduler())//设置内存队列
//.setScheduler(new FileCacheQueueScheduler("D:\\scheduler"))//设置文件队列
.setScheduler(new RedisScheduler("127.0.0.1"))//设置Redis队列
.run();
}