爬虫从入门到放弃——组件的使用和定制

2023-11-20

以前我们提到了WebMagic的组件。WebMagic的一大特色就是可以灵活的定制组件功能,实现你自己想要的功能。
在Spider类里,PageProcessor、Downloader、Scheduler和Pipeline四个组件都是Spider的字段。除了PageProcessor是在Spider创建的时候已经指定,Downloader、Scheduler和Pipeline都可以通过Spider的setter方法来进行配置和更改。
在这里插入图片描述
本文,我们会讲到如何定制这些组件,完成我们想要的功能。

使用和定制Pipeline

Pileline是抽取结束后,进行处理的部分,它主要用于抽取结果的保存,也可以定制Pileline可以实现一些通用的功能。


  • Pipeline介绍

Pipeline的接口定义如下:

public interface Pipeline {
    // ResultItems保存了抽取结果,它是一个Map结构,
    // 在page.putField(key,value)中保存的数据,可以通过ResultItems.get(key)获取
    public void process(ResultItems resultItems, Task task);

}

可以看到,Pipeline其实就是将PageProcessor抽取的结果,继续进行了处理的,其实在Pipeline中完成的功能,你基本上也可以直接在PageProcessor实现,那么为什么会有Pipeline?有几个原因:

  1. 为了模块分离。“页面抽取”和“后处理、持久化”是爬虫的两个阶段,分开在独立的线程以至于不同的机器执行。
  2. Pipeline的功能比较固定,更容易做成通用组件。每个页面的抽取方式千变万化,但是后续处理方式则比较固定。WebMagic中就已经提供了控制台输出、保存到文件、保存为JSON格式的文件几种通用的Pipeline。

在WebMagic里,一个Spider可以有多个Pipeline ,使用Spider.addPipeline()即可增加一个Pipeline。这些Pipeline都会得到处理,例如你可以使用spider.addPipeline(new ConsolePipeline()).addPipeline(new FilePipeline())实现输出结果到控制台,并且保存到文件的目标。


  • 将结果输出到控制台

在介绍PageProcessor时,我们使用了GithubRepoPageProcessor作为例子,其中某一段代码中,我们将结果进行了保存(也就是将其保存为键值对的形式):

public void process(Page page) {
    page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
    page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+)").all());
    //保存结果author,这个结果会最终保存到ResultItems中
    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之后,这个页面的结果不会被Pipeline处理
        page.setSkip(true);
    }
    page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()"));
}

现在我们想将结果保存到控制台,要怎么做呢?ConsolePipeline可以完成这个工作:

public class ConsolePipeline implements Pipeline {

    @Override
    public void process(ResultItems resultItems, Task task) {
        System.out.println("get page: " + resultItems.getRequest().getUrl());
        //遍历所有结果,输出到控制台,上面例子中的"author"、"name"、"readme"都是一个key,其结果则是对应的value
        for (Map.Entry<String, Object> entry : resultItems.getAll().entrySet()) {
            System.out.println(entry.getKey() + ":\t" + entry.getValue());
        }
    }
}

  • 将结果保存到MySQL

这里先介绍一个demo项目:jobhunter。它是一个集成了Spring,使用WebMagic抓取招聘信息,并且使用Mybatis持久化到Mysql的例子。我们会用这个项目来介绍如果持久化到Mysql。

在Java里,我们有很多方式将数据保存到MySQL,例如jdbc、dbutils、spring-jdbc、MyBatis等工具。这些工具都可以完成同样的事情,只不过功能和使用复杂程度不一样。如果使用jdbc,那么我们只需要从ResultItems取出数据,进行保存即可。

如果我们会使用ORM框架来完成持久化到MySQL的工作,就会面临一个问题:这些框架一般都要求保存的内容是一个定义好结构的对象,而不是一个key-value形式的ResultItems。以MyBatis为例,我们使用MyBatis-Spring可以定义这样一个DAO:

public interface JobInfoDAO {

    @Insert("insert into JobInfo (`title`,`salary`,`company`,`description`,`requirement`,`source`,`url`,`urlMd5`) values (#{title},#{salary},#{company},#{description},#{requirement},#{source},#{url},#{urlMd5})")
    public int add(LieTouJobInfo jobInfo);
}

我们要做的,就是实现一个Pipeline,将ResultItems和LieTouJobInfo对象结合起来。

到这里,我们其实跳过了一个步骤的介绍,注解模式,其实很简单,就是使用注解进行装配,所以我们在这里一块学习。
注解模式下,WebMagic内置了一个PageModelPipeline:

public interface PageModelPipeline<T> {

    //这里传入的是处理好的对象
    public void process(T t, Task task);

}

这时,我们可以很优雅的定义一个JobInfoDaoPipeline,来实现这个功能:

@Component("JobInfoDaoPipeline")
public class JobInfoDaoPipeline implements PageModelPipeline<LieTouJobInfo> {

    @Resource
    private JobInfoDAO jobInfoDAO;

    @Override
    public void process(LieTouJobInfo lieTouJobInfo, Task task) {
        //调用MyBatis DAO保存结果
        jobInfoDAO.add(lieTouJobInfo);
    }
}

而Model类应该是这样写的:

@TargetUrl("https://github.com/\\w+/\\w+")
@HelpUrl("https://github.com/\\w+")
public class LieTouJobInfo {

    @ExtractBy(value = "//h1[@class='entry-title public']/strong/a/text()", notNull = true)
    private String name;

    @ExtractByUrl("https://github\\.com/(\\w+)/.*")
    private String author;

    @ExtractBy("//div[@id='readme']/tidyText()")
    private String readme;
}

通过注解的方法,手册说明的不是很清晰,如果要使用需要查阅更多的资料。由于不是重点,重心就不在注解这一块了。也可以使用如下的方法进行:

class MysqlPipeline implements Pipeline {

    public MysqlPipeline() {
    }

    public void process(ResultItems resultitems, Task task) {
        Map<String, Object> mapResults = resultitems.getAll();
        Iterator<Entry<String, Object>> iter = mapResults.entrySet().iterator();
        Map.Entry<String, Object> entry;
        // 输出到控制台
        while (iter.hasNext()) {
            entry = iter.next();
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // 持久化
        News news = new News();
        if (!mapResults.get("Title").equals("")) {
            news.setTitle((String) mapResults.get("Title"));
            news.setContent((String) mapResults.get("Content"));
        }
        try {
            InputStream is = Resources.getResourceAsStream("conf.xml");
            SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession session = sessionFactory.openSession();
            session.insert("add", news);
            session.commit();
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

基本Pipeline模式
至此,结果保存就已经完成了。那么如果我们使用原始的Pipeline接口,要怎么完成呢?其实答案也很简单,如果你要保存一个对象,那么就需要在抽取的时候,将它保存为一个对象:

public void process(Page page) {
    page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
    page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+)").all());
    GithubRepo githubRepo = new GithubRepo();
    githubRepo.setAuthor(page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString());
    githubRepo.setName(page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()").toString());
    githubRepo.setReadme(page.getHtml().xpath("//div[@id='readme']/tidyText()").toString());
    if (githubRepo.getName() == null) {
        //skip this page
        page.setSkip(true);
    } else {
        page.putField("repo", githubRepo);
    }
}

在Pipeline中,只要使用GithubRepo githubRepo = (GithubRepo)resultItems.get("repo");就可以获取这个对象了。


  • WebMagic已经提供的几个Pipeline

在这里插入图片描述

使用和定制Scheduler

Scheduler是WebMagic中进行URL管理的组件。一般来说,Scheduler包括两个作用:

  1. 对待抓取的URL队列进行管理。
  2. 对已抓取的URL进行去重。

WebMagic内置了几个常用的Scheduler。如果你只是在本地执行规模比较小的爬虫,那么基本无需定制Scheduler,但是了解一下已经提供的几个Scheduler还是有意义的。
在这里插入图片描述
在0.5.1版本里,对Scheduler的内部实现进行了重构,去重部分被单独抽象成了一个接口:DuplicateRemover,从而可以为同一个Scheduler选择不同的去重方式,以适应不同的需要,目前提供了两种去重方式。
在这里插入图片描述

使用和定制Downloader

WebMagic的默认Downloader基于HttpClient。一般来说,你无须自己实现Downloader,不过HttpClientDownloader也预留了几个扩展点,以满足不同场景的需求。

另外,你可能希望通过其他方式来实现页面下载,例如使用SeleniumDownloader来渲染动态页面。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

爬虫从入门到放弃——组件的使用和定制 的相关文章

随机推荐

  • Eclipse安转主题插件Theme出现问题解决办法

    1 2 3 直接在线安装输入Eclipse Color Theme Update Site 4 疯狂下一步 5 收功
  • 告别3D-DNA试试chromap和yahs做Hi-C辅助组装吧

    之前使用的是3D DNA流程做Hi C的辅助组装 它的最大优势就是输出结果可以对接下游的JBAT juicerbox with Assembly Tools 进行手动矫正 然而它点缺陷也很明显 处理速度不够快 且对植物的优化不行 同时目前许
  • 常微分方程(Ordinary Differential Equation III)

    常微分方程 一阶线性微分方程组 基本概念 解的存在和唯一性定理 齐次线性微分方程组 非齐次线性微分方程组 常系数线性微分方程组 常微分方程 Ordinary Differential Equation I 常微分方程 Ordinary Di
  • C结构体内char[] 数组的初始化

    一种C结构体内char 数组的初始化 include
  • 用户控件自定义 DependencyProperty

    用户控件自定义 DependencyProperty 属性使用教程 DependencyProperty 概念 依赖属性创建 自定义控件的依赖属性 DependencyProperty 概念 依赖属性就是一种可以自己没有值 并能通过使用Bi
  • 断电的方法关闭计算机,win7设置usb关机断电

    win7设置usb关机断电 win7设置usb关机拔出断电的解决方法 小伙伴们可能会经常性的遇到一个问题 就是win7电脑已经关闭了 但是插在计算机usb接口上的硬盘依然会被计算机供电 发出闪亮的光 这就让很多小伙伴们厌烦 为了帮小伙伴们解
  • 第十三篇、基于Arduino uno,获取薄膜压力传感器的值——结果导向

    0 结果 说明 先来看看串口调试助手显示的结果 第一个值是上电后检测到的平均压力值 第二个值是实时的压力值 第三个值是平均压力值和实时压力值的差值 如果是你想要的 可以接着往下看 1 外观 说明 虽然薄膜压力传感器形态各异 但是原理和代码都
  • Spring MVC下篇

    书接上回Spring MVC上篇 前面介绍了Spring MVC的服务器如何获得用户的请求 那么今天就来介绍一下服务器如何将响应返回给客户端 目录 3 服务器将结果返回给用户 前端 3 1 返回一个静态页面 3 2 返回 text html
  • 对wangeditor进行扩展---- 源代码

    看到有人对我做的WangEditor比较感兴趣 问了一些问题 但由于我并不常来 所以就没能及时答复 抱歉了 未避免以后类似问题发生 我将我修改的wangeditor js直接发在这里 有兴趣的可以下载后自己分析 希望能帮到需要的人 扩展后的
  • 【Docker安装部署Kafka+Zookeeper详细教程】

    Docker安装部署Kafka Zookeeper Docker拉取镜像 Docker拉取zookeeper的镜像 docker pull zookeeper Docker拉取kafka的镜像 docker pull wurstmeiste
  • 2022广东省安全员A证第三批(主要负责人)考试试题模拟考试平台操作

    题库来源 安全生产模拟考试一点通公众号小程序 2022广东省安全员A证第三批 主要负责人 培训试题为广东省安全员A证第三批 主要负责人 考试100题全真模拟题 2022广东省安全员A证第三批 主要负责人 考试试题模拟考试平台操作依据广东省安
  • C++ Builder建立COM服务程序的方法

    转 http blog csdn net jaminwm article details 463953 说明 最近学习关于用C Builder建立Activex插件的使用 在网上找到了一些教程 这也是找到这篇文章 但是里边一些细节的东西说明
  • 用rsync同步文件夹

    用rsync命令在不同机器间同步文件夹 及hexo deployer rsync一个BUG的规避方法 命令格式 例如 要把本机public目录与服务器上的 home blog目录同步 用以下命令 rsync delete avz e ssh
  • 对接新浪股票交易接口api需要注意哪些细节 ?

    API接口是一个很抽象的概念 有许多接口 在实际应用中 会产生各种不同的影响 所以 在接入新浪股票交易接口api的过程中 需要注意哪些细节 首先是系统的联结 随着软件的不断发展 很多的软件都被分解成了不同的模块 但在具体的设计中 每个API
  • 无法打开源文件<sys/time.h>,但是用time.h编译就会出错,缺少gettimeofday()

    因为sys time h是uinx系统下的库文件 而现在使用的平台是在windows 由于未指明程序运行的系统 导致找不到对应的头文件 需要重新实现gettimeofday 函数 define WIN32 include
  • Insertion插入排序

    原谅我接着偷懒 是真的没有什么写的内容了啊 好怀疑他们那些大佬是怎么那么多的文章和技术分享的 自闭中ing 最好情况的时间复杂度是 O n 最坏情况的时间复杂度是 O n2 然而时间复杂度这个指标看的是最坏的情况 而不是最好的情况 所以插入
  • 6-4 求自定类型元素的平均 (10分)

    6 4 求自定类型元素的平均 10分 本题要求实现一个函数 求N个集合元素S 的平均值 其中集合元素的类型为自定义的ElementType 函数接口定义 ElementTypeAverage ElementType S intN 其中给定集
  • (一)轻松工作必学:windows bat脚本语法

    记得在初中的时候 电脑刚刚进入了我们的生活 有一天同学发过来一个 xxx bat 的文件 我双击打开之后 电脑就很快就关机了 当时的我还是一头雾水 后来知道这只是一个只需一行代码的批处理文件 批处理文件是一种简化的脚本 可以帮助我们运行一些
  • Node服务器-express框架

    1 Express认识初体验 2 Express中间件使用 3 Express请求和响应 4 Express路由的使用 5 Express的错误处理 6 Express的源码解析 一 手动创建express的过程 1 在项目文件的根目录创建
  • 爬虫从入门到放弃——组件的使用和定制

    以前我们提到了WebMagic的组件 WebMagic的一大特色就是可以灵活的定制组件功能 实现你自己想要的功能 在Spider类里 PageProcessor Downloader Scheduler和Pipeline四个组件都是Spid