当前位置: 首页 > >

WebMagic爬虫演示

发布时间:

1. 什么是WebMagic

WebMagic一款简单灵活的爬虫框架。WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。


? 核心部分提供了一些常用API,直接调用即可完成一个爬虫。扩展部分则提供了一些便捷功能,如实现了在注解模式下开发爬虫。


1.1WebMagic的架构组成


? WebMagic由: Downloader(下*)、PageProcessor(页面处理器)、Scheduler(URL管理器)、Pipeline(结果处理器)四组件组成,并由Spider引擎来调度这四个部分。


DownloaderDownloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。
PageProcessorPageProcessor负责解析页面,抽取有用信息,以及发现新的链接。 PageProcessor对于每个站点每个页面都不一样,需要根据业务需求自行修改。
SchedulerScheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重,在新版本中,去重已经默认关闭了,交由使用者自动控制。也支持使用Redis进行分布式管理。
PipelinePipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。 Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。

1.2数据传输对象
RequestRequest是对URL地址的一层封装,一个Request对应一个URL地址。
PagePage代表了从Downloader下载到的一个页面??可能是HTML,也可能是JSON或者其他文本格式的内容。
ResultItemsResultItems相当于一个Map,它保存PageProcessor处理的结果,供Pipeline使用。 它的API与Map很类似,ResultItems有一个字段skip,若设置为true,则不会被Pipeline处理。

1.3爬虫引擎?Spider

? Spider是WebMagic内部流程的核心。Downloader、PageProcessor、Scheduler、Pipeline都是Spider的一个属性,这些属性是可以自由设置的,通过设置这个属性可以实现不同的功能。Spider也是WebMagic操作的入口,它封装了爬虫的创建、启动、停止、多线程等功能。


1.4项目组成

WebMagic的项目代码主要由两个包组成:webmagic-core和webmagic-extension。


2. 构建一个简单的WebMagic项目

以爬取程序员DD官网的归档笔记为例。


由简入繁,先来爬取任意一个页面中的标题和文章内容,操作如下:


2.1首先通过maven添加jar包

导入core坐标:



us.codecraft
webmagic-core
0.7.3


在日志方面,webMagic使用log4j来实现日志管理。因此,同样需要log4j的jar包依赖:



us.codecraft
webmagic-extension
0.7.3


org.slf4j
slf4j-log4j12




2.2实现PageProcessor页面处理器

PageProcessor是用来处理页面的,因此需要实现其中的process()方法和getSite()方法.


其中process用来设置页面的抽取逻辑,然后通过page.putField()将数据保存到ResultItems中。


Site是用来设置爬虫的信息的。如域名,请求间隔等。


代码如下:


public class DemoMyself implements PageProcessor {

// 1. 设置爬虫的参数信息
Site site = Site.me()
// 指定域名
.setDomain("blog.didispace.com")
// 重新尝试次数
.setRetryTimes(3)
// 每次请求间隔2秒
.setSleepTime(2000)
// 对于某些网站,如京东等,需要设置useragent,否则无法爬取。这个在浏览器中可以查看。
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");

@Override
public void process(Page page) {
// 2. 定义页面抽取逻辑,并进行保存
// 通过XPath的方式抽取数据,获取id为post-body的header标签下的h1标签中的text
page.putField("article-title", page.getHtml().xpath("//*[@id="post-body"]/header/h1/text()").toString());
// 通过XPath的方式抽取数据,获取id为post-body的第二个div标签下的p标签或h2标签或blockquote标签内容
page.putField("article-entry", page.getHtml().xpath("//*[@id="post-body"]/div[2]/p|h2|blockquote").toString());
}

@Override
public Site getSite() {
return site;
}

// 通过main方法启动
public static void main(String[] args) {
Spider.create(new DemoMyself())
.addUrl("http://blog.didispace.com/transactional-not-rollback/")
// 处理爬取结果
.addPipeline(new JsonFilePipeline("D:\BaiduNetdiskDownload"))
.run();
}
}

2.3关于如何快速定位到标签:

在上面的process处理逻辑中,怎么让爬虫知道爬取网页的哪些地方呢?通过标签定位。


要知道页面的静态数据基本都是由html标签构成的。我们可以利用webmagic封装的方法来获取标签内容:



XPath和css选择器都可以用来获取页面元素,不过在复杂的页面标签环境中,使用Xpath更加方便。在浏览器中单击F12开启调试台,通过元素选择器我们可以快速且精准的定位到对应标签位置,鼠标右击选择copy,可以直接复制到Xpath路径。



3. 爬取页面列表中的数据

之前尝试过爬取一个页面的内容,但一个个的爬取文章太麻烦,如何在一个文章列表中,将所有的文章标题和内容都爬取下来?接下来我们尝试一下:


3.1 分析文章列表和详情页

依旧是程序员DD网站,我们可以看到归档笔记是以分页形式展示的。



首页网址没有分页信息,但从第二页开始,网址开始包含页面编号了,除了编号外,其余位置都不变:



我们将网址用正则表达式的方式来展示: 结尾的d{1,2}表示任意字符重复至少1次,至多两次,及表达了0-99之间的任意整数。


private static final String URL_LIST = "http://blog.didispace.com/archives/page/\d{1,2}";

点击一个笔记链接,可以发现文章的请求URL是这样的:



不同种类文章之间,URL是没有规律可循的。不过,在Spring-boot系列的学*文章中,文章是通过编码来排列的。


3.2 URL的正则处理

将Spring-Boot类的文章URL用正则表达式来展示,具体代码如下:


private static final String URL_POST = "(http\:\/\/blog\.didispace\.com\/spring\-boot\-learning\-([1-9]|[1-2][0-9])\-([1-9]|1[0-9])\-([1-9]|1[0-9])\/)";

private static final String URL_LIST = "http://blog.didispace.com/archives/page/\d{1,2}";

private static final String URL_FIRST = "http://blog.didispace.com/archives";

3.3 实现爬虫引擎

// 1. 设置爬虫的参数信息
Site site = Site.me()
// 指定域名
.setDomain("blog.didispace.com")
// 重新尝试次数
.setRetryTimes(3)
// 每次请求间隔2秒
.setSleepTime(2000)
// 对于某些网站,如京东等,需要设置useragent,否则无法爬取。这个在浏览器中可以查看。
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");

@Override
public void process(Page page) {

// 2. 定义页面抽取逻辑,并进行保存
if (page.getUrl().regex(URL_LIST).match() || page.getUrl().regex(URL_FIRST).match()) {
// 判断是否是列表页,是的话在页面中查找id为container下的第二个div标签,查找与请求URL符合的链接,添加到Requests中。
page.addTargetRequests(page.getHtml()
.xpath("//*[@id="container"]/div/section/div[2]").links().regex(URL_POST).all());
// 将当前页面中包含在URL列表中的链接添加到Requests中。
page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all());
}else {
// 内容页,通过XPath的方式抽取数据,获取id为post-body下的header标签下的h1标签文本
page.putField("article-title", page.getHtml()
.xpath("//*[@id="post-body"]/header/h1/text()").toString());

page.putField("article-entry", page.getHtml()
.xpath("//*[@id="post-body"]/div[2]").toString());
}
}
@Override
public Site getSite() {
return site;
}

public static void main(String[] args) {
Spider.create(new DemoMyself())
.addUrl(URL_FIRST)
// 处理数据
.addPipeline(new JsonFilePipeline("D:\BaiduNetdiskDownload"))
.thread(5)
.run();
}

运行后,最终可以在保存路径获取到结果:



通过EditPlus等编辑器打开,即可看到json格式的一条数据信息。



4. 数据对象的封装

在日常使用中,最常用是配合业务需求,将获取到的数据,封装成一个爬虫结果对象,然后和数据库交互。


接下来,将继续爬取程序员DD网站数据,将标题,作者,时间,内容等信息封装成一个数据对象,存入到数据库中。


项目通过SpringBoot和Mybatis-Plus完成。maven依赖如下:



org.springframework.boot
spring-boot-starter-web



org.springframework.boot
spring-boot-starter-test
test


org.junit.vintage
junit-vintage-engine





us.codecraft
webmagic-core
0.7.3




org.projectlombok
lombok
1.18.12
provided




com.baomidou
mybatis-plus-boot-starter
3.4.0




com.google.guava
guava
16.0




mysql
mysql-connector-java


4.1创建数据对象

public class WebData {
/**
* 主键id
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;

/**
* 标题
*/
private String title;

/**
* 日期
*/
private String date;

/**
* 作者
*/
private String author;

/**
* 文章内容
*/
private String content;
}

数据的get/set通过lombok的@Date来完成。


4.2 分析页面

回到页面,分析作者和发布时间等数据的div位置,通过XPath获取到其中的内容,


String title = page.getHtml()
.xpath("//*[@id="post-body"]/header/h1/text()").toString();
String content = page.getHtml()
.xpath("//*[@id="post-body"]/div[2]").toString();
String author = page.getHtml()
.xpath("//*[@id="post-body"]/header/div/div[2]/text()").toString();
String createTime = page.getHtml()
.xpath("//*[@id="post-body"]/header/div/div[1]/a/time/text()").toString();

4.3 实现页面处理器

然后将数据封装到WebData对象中,并由page存储到ResultItems中。


@Component
public class DemoMyself implements PageProcessor {
/** 注入服务类 */
@Resource
MagicService magicService;

private static final String URL_POST = "(http\:\/\/blog\.didispace\.com\/spring\-boot\-learning\-([1-9]|[1-2][0-9])\-([1-9]|1[0-9])\-([1-9]|1[0-9])\/)";

private static final String URL_LIST = "http://blog.didispace.com/archives/page/\d{1,2}";

private static final String URL_FIRST = "http://blog.didispace.com/archives";

List webDatas = new ArrayList<>();

// 1. 设置爬虫的参数信息
Site site = Site.me()
// 指定域名
.setDomain("blog.didispace.com")
// 重新尝试次数
.setRetryTimes(3)
// 每次请求间隔2秒
.setSleepTime(2000)
// 模拟浏览器访问,对于某些网站,如京东等,需要设置useragent,否则无法爬取。
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");

@Override
public void process(Page page) {

// 2. 定义页面抽取逻辑,并进行保存
if (page.getUrl().regex(URL_LIST).match() || page.getUrl().regex(URL_FIRST).match()) {
// 判断是否是列表页,是的话在页面中查找id为container下的第二个div标签,查找与请求URL符合的链接,添加到Requests中。
page.addTargetRequests(page.getHtml()
.xpath("//*[@id="container"]/div/section/div[2]").links().regex(URL_POST).all());
// 将当前页面中包含在URL列表中的链接添加到Requests中。
page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all());
} else {
// 内容页,通过XPath的方式抽取数据,获取id为post-body下的文本
String title = page.getHtml()
.xpath("//*[@id="post-body"]/header/h1/text()").toString();
String content = page.getHtml()
.xpath("//*[@id="post-body"]/div[2]").toString();
String author = page.getHtml()
.xpath("//*[@id="post-body"]/header/div/div[2]/text()").toString();
String createTime = page.getHtml()
.xpath("//*[@id="post-body"]/header/div/div[1]/a/time/text()").toString();

WebData webData = new WebData().setAuthor(author).setContent(content).setTitle(title).setDate(createTime);
// 将数据对象存放到ResultItems中
if (webData != null) {
page.putField("data", webData);
}
}
}

@Override
public Site getSite() {
return site;
}

/**
* 启动方法
**/
public void start() {
// 3. 处理爬取的数据
Pipeline pipeline = (resultItems, task) -> {
Map all = resultItems.getAll();
WebData data = (WebData) all.get("data");
if (data != null) {
webDatas.add(data);
}
};

// 启动爬虫
Spider.create(new DemoMyself())
.addUrl(URL_FIRST)
.setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(100000)))
.addPipeline(pipeline)
.thread(5)
.run();

//4. 批量存储数据
magicService.saveBatch(webDatas);
}

}

4.4 mapper和service的创建

? mapper通过继承BaseMapper来实现CRUD的简单操作。


@Mapper
public interface MagicMapper extends BaseMapper {}

? service:


@Service
public class MagicService{

@Resource
MagicMapper mapper;

/**
* 批量存储
*/
public void saveBatch(List datas) {
datas.forEach(data -> {
if (data != null) {
mapper.insert(data);
}
});
}
}

4.5 在测试类中运行

@SpringBootTest
@RunWith(SpringRunner.class)
public class Test {

@Resource
DemoMyself demoMyself;

@org.junit.Test
public void test() {
// 启动爬虫
demoMyself.start();
}
}

在SpringBoot和Mybatis-plus正常启动之后,爬虫开始爬取页面。最终,我们可以在数据库中找到保存的数据:



爬虫的重点是在于对于数据源的分析,如何精准抽取想要的数据,这还需要后期的练*。



友情链接: 时尚网 总结汇报 幼儿教育 小学教育 初中学习资料网