

老狗啃爬虫-从抓取到存储之Pipeline
source link: http://www.veiking.cn/blog/1049-page.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在爬虫框架WebMagic中,用于保存结果的组件叫做Pipeline。在WebMagic已经实现了的Pipeline接口中,如果我们仅仅是想把抓取数据进行控制台输出,我们可以借助它的ConsolePipeline;如果我们想将数据以文件的形式进行存储,即可借助它的FilePipeline。如果我们想实现自己想要的具体功能,我们就得定制我们所需的Pipeline
经过前面的几篇文章,我们已经可以将页面上的信息成功的抓取到手,接下来就要面临存储问题。
这里我们准备使用的数据库是Mysql,持久化将借助Mybatis,即springboot集成mybatis的形式来存储我们的数据。
Pipeline
在爬虫框架WebMagic中,用于保存结果的组件叫做Pipeline,我们需要定制Pipeline来实现我们需要的功能。
我们可以看到源码,在WebMagic已经实现了的Pipeline接口中,如果仅仅是想把抓取数据进行控制台输出,我们可以借助它的ConsolePipeline;如果只是想将数据以文件的形式进行存储,即可借助它的FilePipeline,如此等等。如果我们想实现自己想要的其他功能,我们就得定制我们自己的Pipeline。
由于我们要用Mysql和Mybatis,所以,在原有的基础上,我们还需要一些支持这些功能的jar包,这时候我们就需要修改下pom.xml文件,dependencies里添加如下代码:
<!-- mysql-mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
留意下这里引入的依赖包,其中mybatis-spring-boot-starter的引入,同时会自动引入诸如mybatis、mybatis-spring、mysql-connector-java等在功能支撑上所需的很多依赖包,免去了我们注意配置的很多麻烦,这种方式相对来说就方便得多。
此外,还要留意下druid-spring-boot-starter,Druid是由阿里提供的一个JDBC组件,借助其可以帮助程序高效的管理数据库连接池,同时还提供数据库访问性能的监控等功能等。这里我们用到的只是它数据库连接池的这块功能,其他的感兴趣可以去深入去了解下。
改完pom.xml配置文件,更新项目,jar包依赖这块准备工作已经完成。
接下来再看数据存储,既然我们要用到数据库了,肯定得找地方给项目设置数据库访问的地址、用户密码等参数,springboot有两种参数配置的形式,一种是在程序代码中,一种是配置文件。Springboot启动默认读取的配置文件是需要在src/main/resources文件目录下设置的application文件,文件类型可以是传统的.properties文件格式,也可以是.yml文件格式,我们选用层级结构上可视化更为友好的.yml,当然两种配置文件格式功能上完全相同。
于是我们创建application.yml文件,并填入以下内容:
spring:
# 数据库参数
datasource:
url: jdbc:mysql://192.168.0.101:3306/v_webmagic?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
username: root
password: ******
这里,.yml文件格式我们要注意下他的格式缩进形式,我们用的是空格缩进。先配置上数据库所需的这些参数,然后进行下一步的准备。
此外,这里还要补加一个小功能,就是lombok,由于接下来我们要涉及到java数据对象的操作,lombok即可帮助我们在Java 对象(POJO)的使用上让代码更为简洁,可能一大串的get、set代码,我们借助lombok只需要一个@Data标签即可,总之一句话:代码会漂亮很多。额外提示,eclipse中lombok的使用,除了代码中需要引入依赖,还需要安装一下插件,这个自行安排下,也很简单。
好了,基本上开发程序的准备工作都已完成,接下来我们进入编程环节。
回顾我们之前的爬虫例子,抓取了一个屈原屈先生的资料页面,这次我们要用数据库存储,就不能只抓这一个页面了,我们就用屈原先生页面的链接,作为种子URL,顺藤摸瓜,爬取10个不同人物的信息,然后存储至数据库。
第一步:数据库建表
我们再看看我们的抓取目标,人物页面,里面可以提出来的有效信息,我们就取主要的三个,即人物名称、朝代、简介。然后,我们在名为v_webmagic数据库里创建一张,用来承接存储功能的表,创建代码如下:
DROP TABLE IF EXISTS `aigufeng_celebrity`;
CREATE TABLE `aigufeng_celebrity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`dynasty` varchar(16) DEFAULT NULL,
`content` varchar(512) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
库表创建完成,我们就要准备java程序部分了。
第二步:Java 对象
既然要做数据持久化,我们就要抽象出来一个Java 对象(POJO),于是我们根据页面人物信息的人特点,创建一个人物信息类:
package cn.veiking.biz.model;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author :Veiking
* @version :2020年12月5日
* 说明 :爱古风人物信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AigufengCelebrity implements Serializable{
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private String dynasty;
private String content;
}
这时候,我们留意下这三个标签:@Data、@NoArgsConstructor、@AllArgsConstructor,其中@Data的含义是,lombok已经帮我们处理了getter、setter,@NoArgsConstructor表示提供了无参构造;@AllArgsConstructor表示提供了全参构造。简洁明了的三个标签,省去了很多代码,是不是很方便。
第三步:DAO
接着我们创建一个dao文件,即AigufengCelebrityDao.java,专门来处理从爱古风的人物信息页面抓取的数据持久化,我们用注解式的编程写法,代码如下:
package cn.veiking.biz.dao;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import cn.veiking.biz.model.AigufengCelebrity;
/**
* @author :Veiking
* @version :2020年12月5日
* 说明 :爱古风-人物信息数据持久化接口
*/
@Mapper
public interface AigufengCelebrityDao {
/************** BASE **************/
@Insert(value = " INSERT INTO aigufeng_celebrity (`name`, `dynasty`, `content`) VALUES (#{model.name}, #{model.dynasty}, #{model.content}) ")
public boolean add(@Param("model") AigufengCelebrity model);
}
有没有发现,相对以前习惯性的配置一个mapper.xml文件,注解式编程的代码是不是看起来更为简洁?其实绝大部分使用场景,注解式是完全可取的,其SQL的使用方式跟xml是一样的,考虑到这种代码形式展示直观上的一些局限性,太过复杂的数据库操作、或者可能频繁变动的功能,还是可以结合配置相对应mapper.xml的方式来实现,两种形式也是可以并存互相配合补充的。
第四步:Pipeline,创建数据处理管道
这一步,我们要创建处理数据的Pipeline,我们要将一个得到的数据对象,通过DAO,保存至数据库,实现代码如下:
package cn.veiking.spider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.veiking.base.common.logs.SimLogger;
import cn.veiking.biz.dao.AigufengCelebrityDao;
import cn.veiking.biz.model.AigufengCelebrity;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
/**
* @author :Veiking
* @version :2020年12月6日6
* 说明 :爱古风-人物信息数据持久化管道
*/
@Service
public class AigufengCelebrityPipeline implements Pipeline{
SimLogger logger = new SimLogger(this.getClass());
@Autowired
private AigufengCelebrityDao aigufengCelebrityDao;
@Override
public void process(ResultItems resultItems, Task task) {
AigufengCelebrity model = resultItems.get("aigufengCelebrity");
logger.info("AigufengCelebrityPipeline process [model={}] ", model);
if(model != null) {
this.aigufengCelebrityDao.add(model);
logger.info("AigufengCelebrityPipeline insert into DB ... ");
}
}
}
这里注意 @Service标签,我们在程序启动的时候,要将此类以一个服务的形式注册到spring容器中。在这个Pipeline中,如有相应的AigufengCelebrity数据,我们将执行数据入库的操作。
第五步:PageProcessor大升级
我们前面的例子,写过一个简单的PageProcessor,成功抓取了页面内容,这时候我们就在其基础上稍稍升级一下,以便能突出爬虫的功能特点,以及Pipeline在这里扮演的作用代码如下:
package cn.veiking.spider;
import java.util.List;
import org.springframework.stereotype.Service;
import cn.veiking.base.common.logs.SimLogger;
import cn.veiking.biz.model.AigufengCelebrity;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
/**
* @author :Veiking
* @version :2020年12月6日
* 说明 :爱古风-人物信息数据获取
*/
@Service
public class AigufengCelebrityProcessor implements PageProcessor {
SimLogger logger = new SimLogger(this.getClass());
// 抓取目标也
private static final String TargetUrl = "http://www\\.aigufeng\\.com/celebrity/article-\\d+/page\\.html";
// 抓取统计,我们只抓取10条记录
private static int count = 0;
@Override
public Site getSite() {
Site site = Site.me().setRetryTimes(5).setSleepTime(1000).setTimeOut(10000);
return site;
}
//
@Override
public void process(Page page) {
logger.info("AigufengCelebrityProcessor process");
if(page.getUrl().regex(TargetUrl).match()) {
// 获取前后翻页页面连接并添加至待抓序列
List links= page.getHtml().xpath("//*[@id=\"content\"]/div/div[1]/div[2]/div[2]/div[1]").links().regex(TargetUrl).all();
page.addTargetRequests(links);
// 获取页面信息
String name = page.getHtml().xpath("//*[@id=\"content\"]/div/div[1]/div[2]/div[1]/h1/a/text()").get();
String dynasty = page.getHtml().xpath("//*[@id=\"content\"]/div/div[1]/div[2]/div[1]/h2/span/a/text()").get().replace("(", "").replace(")", "");
String content = page.getHtml().xpath("//*[@id=\"icontentContent\"]/text()").get();
// 页面信息加工备用
AigufengCelebrity model = new AigufengCelebrity();
model.setName(name);
model.setDynasty(dynasty);
model.setContent(content);
page.putField("aigufengCelebrity", model);
count ++;
// 程序自爆,运行终止
if(count>10) {
System.exit(0);
}
}
}
}
这里设置的参数TargetUrl,是通过正则表达式,用来做URL校验匹配的,如获取到的链接符合此规则,则添加至待抓取序列。
这里又设置了个标记,因为我们是测试,抓取十条满足测试需要即可;实际上如果不做逻辑限制,理论上这个程序会沿着前后翻页的逻辑,顺藤摸瓜穷尽爱古风网站所有人物信息的抓取,这里我们重在测试功能,不做验证。并且WebMagic这里也已经处理了简单的URL重复问题,我们只需要将Processor的重点关注在具体的数据抓取操作上。
第六步:完成测试
本次测试我们添加了功能组件Pipeline,又增加了数据持久化的接口服务,如想顺利启动并使用,我们须将这些组件注册至spring容器中,这里叫要在测试启动入口类StartTest这里添加几个注解,整个代码升级如下:
package cn.veiking;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* @author :Veiking
* @version :2020年11月30日
* 说明 :测试启动入口
*/
@SpringBootApplication
//开启通用注解扫描
@MapperScan(value = {"cn.veiking.biz.dao"})
@ComponentScan(value = {"cn.veiking.processor"})
@ComponentScan(value = {"cn.veiking.spider.*"})
public class StartTest {
public static void main(String[] args) {
SpringApplication.run(StartTest.class, args);
}
}
@MapperScan的意思是扫描这个路径下的java类并注入spring容器,对应的接口文件亦须添加与之对应的@Mapper标签;
@ComponentScan之前我们已经了解过了,即需要添加包路径扫描cn.veiking.spider.*,所有spider下的java类都将在spring容器中注册成相应的服务。
接下来我们升级测试主程序,代码如下:
package cn.veiking.aigufeng;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import cn.veiking.StartTest;
import cn.veiking.base.common.logs.SimLogger;
import cn.veiking.spider.AigufengCelebrityPipeline;
import cn.veiking.spider.AigufengCelebrityProcessor;
import us.codecraft.webmagic.Spider;
/**
* @author :Veiking
* @version :2020年12月8日
* 说明 :aigufengCelebrityPipeline、aigufengCelebrityProcessor 测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartTest.class)
public class AigufengCelebrityTest {
SimLogger logger = new SimLogger(this.getClass());
@Autowired
private AigufengCelebrityPipeline aigufengCelebrityPipeline;
@Autowired
private AigufengCelebrityProcessor aigufengCelebrityProcessor;
private static final String StartUrl = "http://www.aigufeng.com/celebrity/article-1000056/page.html";
@Test
public void testSpider() {
long startTime, endTime;
logger.info("AigufengCelebrityTest testSpider [start={}] ", "开始爬取数据");
startTime = System.currentTimeMillis();
Spider.create(aigufengCelebrityProcessor)
.addUrl(StartUrl)
.addPipeline(aigufengCelebrityPipeline)
.thread(3)
.run();
endTime = System.currentTimeMillis();
logger.info("AigufengCelebrityTest testSpider [end={}] ", "爬取结束,耗时约" + ((endTime - startTime) / 1000) + "秒");
}
}
这里,留意.addPipeline()方法,已经将Pipeline添加至Spider。
此外,根据PageProcessor里的升级变动,这里的StartUrl,已经具有种子URL的功能。
所有程序代码完成准备,运行测试…
我们看到Console窗口滚动的日志打印,数据已经陆陆续续被成功抓取,添加至数据库:
好了,我们针对爬虫框架WebMagic的Pipeline学习测试已经完成。
本篇我们借助Pipeline实现了抓取数据的入库,通过对Pipeline的学习了解,我们可以发现,在WebMagic处理数据的过程中,PageProcessor和Pipeline在逻辑上是相互独立的:PageProcessor专注于处理数据的解析抓取;Pipeline负责所得数据的处理,Spider像计算机的CPU一样,将他们这些功能组件组合起来,来处理整个程序的创建和运行等。
接下来之后,我们再看看WebMagic其他组件的使用和爬虫相关的使用技巧。
Recommend
-
88
NodeJs爬虫抓取古代典籍,共计16000个页面心得体会总结及项目分享 之前研究数据,零零散散的写过一些数据抓取的爬虫,不过写的比较随意。有很多地方现在看起来并不是很合理 这...
-
17
之前讲了很多关于webmagic的爬虫实现方法,都是基于静态网页的,我们只需考虑根据链接下载页面,然后解析html提取目标数据即可。然而,很多网站的页面数据是动态的,那么简单的下载解析将毫无意义,这时候我们就得借助额外的技术方案...
-
10
老狗啃爬虫-爬虫学习总结(示例源码)_老狗啃骨头_Veiking百草园-知识点滴,日常分享 爬虫技术也不是局限于某种编程语言的应用技术,语种上没有优劣之分,有时候我们遇到问题,除了参考网上的案例,去读源码,...
-
8
网络爬虫是一个比较成熟的技术应用,目前,从技术角度说,市面上的爬虫框架有很多种选择,不同种开发语言又有许多不同的种类,这就让不少人在开发预研的时候陷入选择上的迷茫,接下来我们收集了一下市面上主流的开发语言中对一...
-
7
老狗啃爬虫-开发准备之Maven动员_老狗啃骨头_Veiking百草园-知识点滴,日常分享 本次关于爬虫框架WebMagic的开发学习,将基于java语言的Spring框架,Spring框架是java语言诸多优秀框架里脱颖而出的一个功能完...
-
10
WebMagic是一个简单灵活的Java爬虫框架。其简单的API,容易上手,模块化的结构,便于轻松扩展;同时也功能完备,且提供多线程和分布式支持。基于WebMagic,我们可以快速开发出一个高效、易维护的爬虫。WebMagic框架主要由Downloader、...
-
4
爬虫爬取内容,本质就是把网站页面下载、读取过来,然后其核心工作就是解析定位,提取数据。这里说的Jsoup、Xsoup、CSS选择器、Xpath、JsonPath,包括正则表达式的应用,都是数据处理过程中必不可少的基础性技术。我们使用的爬虫框架W...
-
7
老狗啃爬虫-便捷的元素定位之Selectable 老狗啃骨头 @V...
-
6
老狗啃爬虫-Cookies的使用之Selenium 老狗啃骨头 @Veik...
-
4
老狗啃爬虫-URL去重之Scheduler_老狗啃骨头_Veiking百草园-知识点滴,日常分享 读过源代码,再回顾我们之前所学所述,WebMagic的工作机制,以及之后我们如何设计具体的爬虫程序,思路会更加明了清晰。我们知道...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK