精华内容
下载资源
问答
  • webmagic
    2022-06-25 15:05:30

    webmagic学习笔记

    官网地址: http://webmagic.io/

    核心思路:

    1. 自定义BookPageProcess类,实现 PageProcessor接口,并重写process() 。方法内主要解析html页面标签,获取需要的数据,将数据存入Pipeline
    2. 自定义BookPageContentPipeline类,实现Pipeline接口,并重写process()。方法内主要是进行数据持久化,本文使用mybatis进行持久化入库。
    3. 通过定时任务,定时拉取数据
    
    1.准备工作
    1.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>
    
    1.2SQL
    
    DROP TABLE IF EXISTS `bookdata`;
    CREATE TABLE `bookdata` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `picUrl` varchar(128) DEFAULT NULL,
      `name` varchar(64) DEFAULT NULL,
      `author` varchar(64) DEFAULT NULL,
      `readPeopleNum` varchar(24) DEFAULT NULL,
      `chapter` varchar(24) DEFAULT NULL,
      `wordNum` varchar(24) DEFAULT NULL,
      `status` varchar(24) DEFAULT NULL,
      `lastUpdateTime` datetime DEFAULT NULL,
      `downloadUrl` varchar(128) DEFAULT NULL,
      `note` varchar(255) DEFAULT NULL,
      `likeBook` text,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=401 DEFAULT CHARSET=utf8;
    
    1.3yml
    spring:
      datasource:
        type: org.apache.commons.dbcp.BasicDataSource
        url: jdbc:mysql://localhost:3306/pachong?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
        driverClassName: com.mysql.jdbc.Driver
    
    2.java代码

    BookVo类

    package com.easyexcel.demo.vo;
    
    import lombok.Data;
    
    import java.util.Date;
    
    @Data
    public class BookVo {
        private Integer id;
        private String picUrl;
        private String name;
        private String author;
        private String readPeopleNum;
        private String chapter;
        private String wordNum;
        private String status;
        private Date lastUpdateTime;
        private String downloadUrl;
        private String note;
        private String likeBook;
    }
    
    

    BookDataMapper类

    package com.easyexcel.demo.mapper;
    
    import com.easyexcel.demo.vo.BookVo;
    import org.springframework.stereotype.Component;
    
    @Component
    public interface BookDataMapper {
    
        public int insertBookVo(BookVo bookVo);
    }
    
    

    BookDataMapper.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.easyexcel.demo.mapper.BookDataMapper">
        <resultMap id="BaseResultMap" type="com.easyexcel.demo.vo.BookVo">
            <id column="id" property="id"/>
            <result column="picUrl" property="picUrl"/>
            <result column="name" property="name"/>
            <result column="author" property="author"/>
            <result column="readPeopleNum" property="readPeopleNum"/>
            <result column="chapter" property="chapter"/>
            <result column="detail_url" property="detail_url"/>
            <result column="wordNum" property="wordNum"/>
            <result column="status" property="status"/>
            <result column="lastUpdateTime" property="lastUpdateTime"/>
            <result column="downloadUrl" property="downloadUrl"/>
            <result column="note" property="note"/>
            <result column="likeBook" property="likeBook"/>
        </resultMap>
        <insert id="insertBookVo" parameterType="com.easyexcel.demo.vo.BookVo">
            <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
                SELECT LAST_INSERT_ID()
            </selectKey>
            insert into bookdata
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="picUrl != null">
                    picUrl,
                </if>
                <if test="name != null">
                    name,
                </if>
                <if test="author != null">
                    author,
                </if>
                <if test="readPeopleNum != null">
                    readPeopleNum,
                </if>
                <if test="chapter != null">
                    chapter,
                </if>
                <if test="wordNum != null">
                    wordNum,
                </if>
                <if test="status != null">
                    status,
                </if>
                <if test="lastUpdateTime != null">
                    lastUpdateTime,
                </if>
                <if test="downloadUrl != null">
                    downloadUrl,
                </if>
                <if test="note != null">
                    note,
                </if>
                <if test="likeBook != null">
                    likeBook
                </if>
            </trim>
            <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="picUrl != null">
                    #{picUrl},
                </if>
                <if test="name != null">
                    #{name},
                </if>
                <if test="author != null">
                    #{author},
                </if>
                <if test="readPeopleNum != null">
                    #{readPeopleNum},
                </if>
                <if test="chapter != null">
                    #{chapter},
                </if>
                <if test="wordNum != null">
                    #{wordNum},
                </if>
                <if test="status != null">
                    #{ status},
                </if>
                <if test="lastUpdateTime != null">
                    #{lastUpdateTime},
                </if>
                <if test="downloadUrl != null">
                    #{ downloadUrl},
                </if>
                <if test="note != null">
                    #{ note},
                </if>
                <if test="likeBook != null">
                    #{ likeBook}
                </if>
            </trim>
        </insert>
    </mapper>
    
    

    BookDataService类

    package com.easyexcel.demo.Service;
    
    import com.easyexcel.demo.mapper.BookDataMapper;
    import com.easyexcel.demo.vo.BookVo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class BookDataService {
    
        @Autowired
        private BookDataMapper bookDataMapper;
    
        public int insertBookVo(BookVo bookVo) {
            return bookDataMapper.insertBookVo(bookVo);
        }
    }
    
    

    BookPageProcess类

    package com.easyexcel.demo.pageprocessor;
    
    import com.alibaba.fastjson.JSONObject;
    import com.easyexcel.demo.vo.BookVo;
    import org.jsoup.Jsoup;
    import org.jsoup.select.Elements;
    import us.codecraft.webmagic.Page;
    import us.codecraft.webmagic.Site;
    import us.codecraft.webmagic.processor.PageProcessor;
    import us.codecraft.webmagic.selector.Selectable;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    
    public class BookPageProcess implements PageProcessor {
    
        public static final String DOMAIN = "https://www.iqisuu.com";
        private Site site = Site.me().setRetryTimes(3)
                .setTimeOut(10 * 1000)
                .setRetryTimes(3)
                .setRetrySleepTime(3000);
    
        @Override
        public void process(Page page) {
            List<Selectable> nodes = page.getHtml().css("div.l ul ").nodes();
            if (nodes != null && nodes.size() > 0) {
    
                List<String> detailUrls = page.getHtml().css("div#newscontent div.l ul li span.s2 a", "href").all();
                detailUrls.forEach(page::addTargetRequest);
                List<String> pageUrls = page.getHtml().css("div.pages div#pagelink a", "href").all();
                String nextString = pageUrls.get(pageUrls.size() - 2).substring(0, pageUrls.get(pageUrls.size() - 2).length() - 1);
    
                page.addTargetRequest(DOMAIN + nextString);
            } else {
                this.saveData(page);
            }
        }
    
        private void saveData(Page page) {
    
            BookVo bookVo = new BookVo();
            bookVo.setPicUrl(page.getHtml().css("div#sidebar div#fmimg img", "data-original").toString());
            bookVo.setName(page.getHtml().css("div#info h1", "text").toString());
            bookVo.setAuthor(page.getHtml().css("div#info a", "text").all().get(0));
    
            String text = page.getHtml().css("div#info p.hidden-xs", "text").all().get(0);
            String replace = text.replace(" ", "");
            String[] split = replace.split("\\|");
            bookVo.setReadPeopleNum(split[0]);
            bookVo.setChapter(split[1]);
            bookVo.setWordNum(split[2]);
            bookVo.setStatus(split[3]);
            String timeString = page.getHtml().css("div#info p.hidden-xs", "text").all().get(2);
            String substring = timeString.substring(timeString.indexOf(":") + 1, timeString.length());
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date parse = simpleDateFormat.parse(substring);
                bookVo.setLastUpdateTime(parse);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            //  格式: /down/70888/
            String downHtml = page.getHtml().css("div#maininfo div.readbtn a.addbookcase", "href").toString();
            String bookId = downHtml.substring(6, downHtml.length() - 1);
            // 下载地址格式 : https://www.iqisuu.com/api/txt_down.php?articleid=53694&articlename=古老之风云再起
            bookVo.setDownloadUrl(DOMAIN + "/api/txt_down.php?articleid=" + bookId + "&articlename=" + bookVo.getName());
            bookVo.setNote(Jsoup.parse(page.getHtml().css("div#intro").toString()).text());
            HashMap<String, String> map = new HashMap<>();
            List<String> likeBookHtmlList = page.getHtml().css("div.hidden-xs p a").all();
            likeBookHtmlList.forEach(entry -> {
                Elements a = Jsoup.parse(entry).select("a");
                String href = DOMAIN + a.attr("href");
                String text1 = a.text();
                map.put(text1, href);
            });
            String jsonString = JSONObject.toJSONString(map);
            bookVo.setLikeBook(jsonString);
            page.putField("bookVo", bookVo);
        }
    
        @Override
        public Site getSite() {
            return site;
        }
    }
    
    

    BookPageContentPipeline类

    package com.easyexcel.demo.listerner;
    
    import com.easyexcel.demo.Service.BookDataService;
    import com.easyexcel.demo.vo.BookVo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.ResultItems;
    import us.codecraft.webmagic.Task;
    import us.codecraft.webmagic.pipeline.Pipeline;
    
    @Component
    public class BookPageContentPipeline implements Pipeline {
        @Autowired
        private BookDataService bookDataService;
    
        @Override
        public void process(ResultItems resultItems, Task task) {
            BookVo bookVo = (BookVo) resultItems.get("bookVo");
            if (bookVo != null) {
                bookDataService.insertBookVo(bookVo);
            }
        }
    }
    
    

    BookTask类

    package com.easyexcel.demo.task;
    
    import com.easyexcel.demo.listerner.BookPageContentPipeline;
    import com.easyexcel.demo.pageprocessor.BookPageProcess;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.Spider;
    
    @Component
    @EnableScheduling
    public class BookTask {
    
        private static final String URL= "https://www.iqisuu.com/fenlei/xuanhuan/1/";
    
        @Autowired
        BookPageContentPipeline bookPageContentPipeline;
    
        @Scheduled(cron = "* * * * * *")
        public void simpleRead() {
            Spider.create(new BookPageProcess()).addUrl(URL).addPipeline(bookPageContentPipeline).thread(4)
                    .run();
    
        }
    }
    

    扫描公众号:回复9003亦可查看

    在这里插入图片描述

    更多相关内容
  • WebMagic

    2022-03-17 08:39:59
    1 WebMagic介绍 今天我们要学习一款爬虫框架的使用就是WebMagic。其底层用到了我们上一天课程所使用的HttpClient和Jsoup,让我们能够更方便的开发爬虫。 WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-...

    1 WebMagic介绍

    今天我们要学习一款爬虫框架的使用就是WebMagic。其底层用到了我们上一天课程所使用的HttpClient和Jsoup,让我们能够更方便的开发爬虫。
    WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。
    WebMagic的设计目标是尽量的模块化,并体现爬虫的功能特点。这部分提供非常简单、灵活的API,在基本不改变开发模式的情况下,编写一个爬虫。
    扩展部分(webmagic-extension)提供一些便捷的功能,例如注解模式编写爬虫等。同时内置了一些常用的组件,便于爬虫开发。

    1.1 架构介绍

    WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件,并由Spider将它们彼此组织起来。这四大组件对应爬虫生命周期中的下载、处理、管理和持久化等功能。WebMagic的设计参考了Scapy,但是实现方式更Java化一些。
    而Spider则将这几个组件组织起来,让它们可以互相交互,流程化的执行,可以认为Spider是一个大的容器,它也是WebMagic逻辑的核心。
    WebMagic总体架构图如下:
    在这里插入图片描述

    1.1.1 WebMagic的四个组件

    1.Downloader
    Downloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。
    2.PageProcessor
    PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。
    在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。
    3.Scheduler
    Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。
    4.Pipeline
    Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。
    Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。

    1.1.2 用于数据流转的对象

    1. Request
      Request是对URL地址的一层封装,一个Request对应一个URL地址。
      它是PageProcessor与Downloader交互的载体,也是PageProcessor控制Downloader唯一方式。
      除了URL本身外,它还包含一个Key-Value结构的字段extra。你可以在extra中保存一些特殊的属性,然后在其他地方读取,以完成不同的功能。例如附加上一个页面的一些信息等。
    2. Page
      Page代表了从Downloader下载到的一个页面——可能是HTML,也可能是JSON或者其他文本格式的内容。
      Page是WebMagic抽取过程的核心对象,它提供一些方法可供抽取、结果保存等。
    3. ResultItems
      ResultItems相当于一个Map,它保存PageProcessor处理的结果,供Pipeline使用。它的API与Map很类似,值得注意的是它有一个字段skip,若设置为true,则不应被Pipeline处理。

    1.2 入门案例

    1.打开IDEA如图所示的界面,点击Create New Project。在这里插入图片描述
    2.选择Maven和JDK版本,点击Next。如图所示:在这里插入图片描述
    3.填写项目相关内容,点击Finish。如图所示:在这里插入图片描述
    4.pom.xml的代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.txw</groupId>
        <artifactId>crawler-webmagic</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
        <dependencies>
            <!--WebMagic-->
            <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>
        </dependencies>
    </project>
    

    如图所示:在这里插入图片描述
    注意:0.7.3版本对SSL的并不完全,如果是直接从Maven中央仓库下载依赖,在爬取只支持SSL v1.2的网站会有SSL的异常抛出。
    解决方案:
    1.等作者的0.7.4的版本发布
    2.直接从github上下载最新的代码,安装到本地仓库
    也可以参考以下资料自己修复
    https://github.com/code4craft/webmagic/issues/701
    5.WebMagic使用slf4j-log4j12作为slf4j的实现。
    添加log4j.properties配置文件的代码如下:

    log4j.rootLogger=INFO,A1
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
    

    如图所示:在这里插入图片描述
    6.演示的代码如下:

    package com.txw.test;
    
    import us.codecraft.webmagic.Page;
    import us.codecraft.webmagic.Site;
    import us.codecraft.webmagic.Spider;
    import us.codecraft.webmagic.processor.PageProcessor;
    /**
     * 案例实现
     * @author Adair
     * @date 2022/3/17 上午 9:25
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")  // 注解警告信息
    public class JobProcessor implements PageProcessor {
    
        private Site site = Site.me();
        /**
         * 解析页面
         * @param page
         */
        @Override
        public void process(Page page) {
            // 解析返回数据page,并且把解析的结果放到ResultItems中
            page.putField("author", page.getHtml().css("div.mt>h1").all());
        }
    
        @Override
        public Site getSite() {
            return site;
        }
    
        /**
         * 主函数执行爬虫
         * @param args
         */
        public static void main(String[] args) {
            Spider.create(new JobProcessor())
                    // 初始访问url地址
                    .addUrl("https://www.jd.com/moreSubject.aspx")
                    // 执行爬虫
                    .run();
        }
    }
    

    如图所示:在这里插入图片描述

    2 WebMagic功能

    2.1 实现PageProcessor

    2.1.1 抽取元素Selectable

    WebMagic里主要使用了三种抽取技术:XPath、正则表达式和CSS选择器。另外,对于JSON格式的内容,可使用JsonPath进行解析。
    1.XPath
    以上是获取属性class=mt的div标签,里面的h1标签的内容

    page.getHtml().xpath("//div[@class=mt]/h1/text()")
    

    也可以参考课堂资料的W3School离线手册(2017.03.11版).chm。
    2.CSS选择器
    CSS选择器是与XPath类似的语言。在上一次的课程中,我们已经学习过了Jsoup的选择器,它比XPath写起来要简单一些,但是如果写复杂一点的抽取规则,就相对要麻烦一点。
    div.mt>h1表示class为mt的div标签下的直接子元素h1标签

    page.getHtml().css("div.mt>h1").toString()
    

    可是使用:nth-child(n)选择第几个元素,如下选择第一个元素

    page.getHtml().css("div#news_div > ul > li:nth-child(1) a").toString()
    

    注意:需要使用>,就是直接子元素才可以选择第几个元素。
    3.正则表达式
    正则表达式则是一种通用的文本抽取语言。在这里一般用于获取url地址。
    正则表达式学习难度要大一些,大家可以参考课堂资料《正则表达式系统教程.CHM》
    在这里插入图片描述

    3.1.2 抽取元素API

    Selectable相关的抽取元素链式API是WebMagic的一个核心功能。使用Selectable接口,可以直接完成页面元素的链式抽取,也无需去关心抽取的细节。
    在刚才的例子中可以看到,page.getHtml()返回的是一个Html对象,它实现了Selectable接口。这个接口包含的方法分为两类:抽取部分和获取结果部分。
    在这里插入图片描述
    这部分抽取API返回的都是一个Selectable接口,意思是说,是支持链式调用的。例如访问https://www.jd.com/moreSubject.aspx页面
    在这里插入图片描述

    // 先获取class为news_div的div
    // 再获取里面的所有包含文明的元素
    List<String> list = page.getHtml()
            .css("div#news_div")
            .regex(".*文明.*").all();
    
    

    2.1.3 获取结果API

    当链式调用结束时,我们一般都想要拿到一个字符串类型的结果。这时候就需要用到获取结果的API了。
    我们知道,一条抽取规则,无论是XPath、CSS选择器或者正则表达式,总有可能抽取到多条元素。WebMagic对这些进行了统一,可以通过不同的API获取到一个或者多个元素。
    在这里插入图片描述
    当有多条数据的时候,使用get()和toString()都是获取第一个url地址。代码如下:

     String str = page.getHtml()
                    .css("div#news_div")
                    .links().regex(".*[0-3]$").toString();
    
            String get = page.getHtml()
                    .css("div#news_div")
                    .links().regex(".*[0-3]$").get();
    

    在这里插入图片描述
    这里selectable.toString()采用了toString()这个接口,是为了在输出以及和一些框架结合的时候,更加方便。因为一般情况下,我们都只需要选择一个元素!
    selectable.all()则会获取到所有元素。

    2.1.4 获取链接

    有了处理页面的逻辑,我们的爬虫就接近完工了,但是现在还有一个问题:一个站点的页面是很多的,一开始我们不可能全部列举出来,于是如何发现后续的链接,是一个爬虫不可缺少的一部分。

    下面的例子就是获取https://www.jd.com/moreSubject.aspx这个页面中所有符合https://www.jd.com/news.\w+?.*正则表达式的url地址并将这些链接加入到待抓取的队列中去。
    演示的代码如下:

    public void process(Page page) {
        page.addTargetRequests(page.getHtml().links()
                .regex("(https://www.jd.com/news.\\w+?.*)").all());
        System.out.println(page.getHtml().css("div.mt>h1").all());
    }
    
    public static void main(String[] args) {
        Spider.create(new JobProcessor())
                .addUrl("https://www.jd.com/moreSubject.aspx")
                .run();
    }
    

    如图所示:在这里插入图片描述

    2.2 使用Pipeline保存结果

    WebMagic用于保存结果的组件叫做Pipeline。我们现在通过“控制台输出结果”这件事也是通过一个内置的Pipeline完成的,它叫做ConsolePipeline。
    那么,我现在想要把结果用保存到文件中,怎么做呢?只将Pipeline的实现换成"FilePipeline"就可以了。
    演示的代码如下:

        /**
         * 主函数执行爬虫
         * @param args
         */
        public static void main(String[] args) {
            Spider.create(new JobProcessor())
                    // 初始访问url地址
                    .addUrl("https://www.jd.com/moreSubject.aspx")
                    .addPipeline(new FilePipeline("D:/webmagic/"))
                    // 设置线程数
                    .thread(5)
                    // 执行爬虫
                    .run();
        }
    

    如图所示:在这里插入图片描述

    2.3 爬虫的配置、启动和终止

    2.3.1 Spider

    Spider是爬虫启动的入口。在启动爬虫之前,我们需要使用一个PageProcessor创建一个Spider对象,然后使用run()进行启动。
    同时Spider的其他组件(Downloader、Scheduler、Pipeline)都可以通过set方法来进行设置。在这里插入图片描述

    2.3.2 爬虫配置Site

    Site.me()可以对爬虫进行一些配置配置,包括编码、抓取间隔、超时时间、重试次数等。在这里我们先简单设置一下:重试次数为3次,抓取间隔为一秒。
    演示的代码如下:

        private Site site = Site.me()
                // 编码
                .setCharset("UTF-8")
                // 抓取间隔时间
                .setSleepTime(1)
                // 超时时间
                .setTimeOut(1000 * 10)
                // 重试时间
                .setRetrySleepTime(3000)
                // 重试次数
                .setRetryTimes(3);
    

    如图所示:在这里插入图片描述
    站点本身的一些配置信息,例如编码、HTTP头、超时时间、重试策略等、代理等,都可以通过设置Site对象来进行配置。
    在这里插入图片描述

    3 爬虫分类

    网络爬虫按照系统结构和实现技术,大致可以分为以下几种类型:通用网络爬虫、聚焦网络爬虫、增量式网络爬虫、深层网络爬虫。 实际的网络爬虫系统通常是几种爬虫技术相结合实现的。

    3.1通用网络爬虫

    通用网络爬虫又称全网爬虫(Scalable Web Crawler),爬行对象从一些种子 URL 扩充到整个 Web,主要为门户站点搜索引擎和大型 Web 服务提供商采集数据。
    这类网络爬虫的爬行范围和数量巨大,对于爬行速度和存储空间要求较高,对于爬行页面的顺序要求相对较低,同时由于待刷新的页面太多,通常采用并行工作方式,但需要较长时间才能刷新一次页面。
    简单的说就是互联网上抓取所有数据。

    3.2 聚焦网络爬虫

    聚焦网络爬虫(Focused Crawler),又称主题网络爬虫(Topical Crawler),是指选择性地爬行那些与预先定义好的主题相关页面的网络爬虫。
    和通用网络爬虫相比,聚焦爬虫只需要爬行与主题相关的页面,极大地节省了硬件和网络资源,保存的页面也由于数量少而更新快,还可以很好地满足一些特定人群对特定领域信息的需求 。
    简单的说就是互联网上只抓取某一种数据。

    3.3 增量式网络爬虫

    增量式网络爬虫(Incremental Web Crawler)是 指 对 已 下 载 网 页 采 取 增量式更新和只爬行新产生的或者已经发生变化网页的爬虫,它能够在一定程度上保证所爬行的页面是尽可能新的页面。
    和周期性爬行和刷新页面的网络爬虫相比,增量式爬虫只会在需要的时候爬行新产生或发生更新的页面 ,并不重新下载没有发生变化的页面,可有效减少数据下载量,及时更新已爬行的网页,减小时间和空间上的耗费,但是增加了爬行算法的复杂度和实现难度。
    简单的说就是互联网上只抓取刚刚更新的数据。

    3.4 Deep Web 爬虫

    Web 页面按存在方式可以分为表层网页(Surface Web)和深层网页(Deep Web,也称 Invisible Web Pages 或 Hidden Web)。
    表层网页是指传统搜索引擎可以索引的页面,以超链接可以到达的静态网页为主构成的 Web 页面。
    Deep Web 是那些大部分内容不能通过静态链接获取的、隐藏在搜索表单后的,只有用户提交一些关键词才能获得的 Web 页面。

    4 案例开发分析

    我们已经学完了WebMagic的基本使用方法,现在准备使用WebMagic实现爬取数据的功能。这里是一个比较完整的实现。
    在这里我们实现的是聚焦网络爬虫,只爬取招聘的相关数据。

    4.1.业务分析

    今天要实现的是爬取https://www.51job.com/上的招聘信息。只爬取“计算机软件”和“互联网电子商务”两个行业的信息。
    首先访问页面并搜索两个行业。结果如下:在这里插入图片描述
    点击职位详情页,我们分析发现详情页还有一些数据需要抓取:
    职位、公司名称、工作地点、薪资、发布时间、职位信息、公司联系方式、公司信息。如图所示:在这里插入图片描述
    在这里插入图片描述

    4.2 数据库表

    根据以上信息,设计数据库表的语句如下:

    use crawler;
    CREATE TABLE `job_info` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
      `company_name` varchar(100) DEFAULT NULL COMMENT '公司名称',
      `company_addr` varchar(200) DEFAULT NULL COMMENT '公司联系方式',
      `company_info` text COMMENT '公司信息',
      `job_name` varchar(100) DEFAULT NULL COMMENT '职位名称',
      `job_addr` varchar(50) DEFAULT NULL COMMENT '工作地点',
      `job_info` text COMMENT '职位信息',
        `salary_min` int(10) DEFAULT NULL COMMENT '薪资范围,最小',
      `salary_max` int(10) DEFAULT NULL COMMENT '薪资范围,最大',
      `url` varchar(150) DEFAULT NULL COMMENT '招聘信息详情页',
      `time` varchar(10) DEFAULT NULL COMMENT '职位最近发布时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='招聘信息';
    

    如图所示:在这里插入图片描述

    4.3 实现流程

    我们需要解析职位列表页,获取职位的详情页,再解析页面获取数据。
    获取url地址的流程如下:
    在这里插入图片描述
    但是在这里有个问题:在解析页面的时候,很可能会解析出相同的url地址(例如商品标题和商品图片超链接,而且url一样),如果不进行处理,同样的url会解析处理多次,浪费资源。所以我们需要有一个url去重的功能。

    4.3.1 Scheduler组件

    WebMagic提供了Scheduler可以帮助我们解决以上问题。
    Scheduler是WebMagic中进行URL管理的组件。一般来说,Scheduler包括两个作用:
    1.对待抓取的URL队列进行管理。
    2.对已抓取的URL进行去重。
    WebMagic内置了几个常用的Scheduler。如果你只是在本地执行规模比较小的爬虫,那么基本无需定制Scheduler,但是了解一下已经提供的几个Scheduler还是有意义的。在这里插入图片描述
    去重部分被单独抽象成了一个接口:DuplicateRemover,从而可以为同一个Scheduler选择不同的去重方式,以适应不同的需要,目前提供了两种去重方式。
    在这里插入图片描述
    RedisScheduler是使用Redis的set进行去重,其他的Scheduler默认都使用HashSetDuplicateRemover来进行去重。
    如果要使用BloomFilter,必须要加入以下依赖:

    <!--WebMagic对布隆过滤器的支持-->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>16.0</version>
    </dependency>
    

    如图所示:在这里插入图片描述
    修改代码,添加布隆过滤器的代码如下:

     /**
         * 主函数执行爬虫
         * @param args
         */
        public static void main(String[] args) {
            Spider.create(new JobProcessor())
                    // 初始访问url地址
                    .addUrl("https://www.jd.com/moreSubject.aspx")
                    .addPipeline(new FilePipeline("D:/webmagic/"))
                    .setScheduler(new QueueScheduler()
                            // 参数设置需要对多少条数据去重
                            .setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)))
                    // 设置线程数
                    .thread(1)
                    // 执行爬虫
                    .run();
        }
    

    如图所示:在这里插入图片描述
    修改public void process(Page page)方法,添加一下代码:

     	/**
         * 解析页面
         * @param page
         */
        @Override
        public void process(Page page) {
            // 每次加入相同的url,测试去重
            page.addTargetRequest("https://www.jd.com/news.html?id=36480");
        }
    

    如图所示:在这里插入图片描述
    打开布隆过滤器BloomFilterDuplicateRemover,在下图处打断点测试。如图所示:在这里插入图片描述
    4.3.2.三种去重方式
    去重就有三种实现方式,那有什么不同呢?
    1.HashSet
    使用java中的HashSet不能重复的特点去重。优点是容易理解。使用方便。
    缺点:占用内存大,性能较低。
    2.Redis去重
    使用Redis的set进行去重。优点是速度快(Redis本身速度就很快),而且去重不会占用爬虫服务器的资源,可以处理更大数据量的数据爬取。
    缺点:需要准备Redis服务器,增加开发和使用成本。
    3.布隆过滤器(BloomFilter)
    使用布隆过滤器也可以实现去重。优点是占用的内存要比使用HashSet要小的多,也适合大量数据的去重操作。
    缺点:有误判的可能。没有重复可能会判定重复,但是重复数据一定会判定重复。
    布隆过滤器 (Bloom Filter)是由Burton Howard Bloom于1970年提出,它是一种space efficient的概率型数据结构,用于判断一个元素是否在集合中。在垃圾邮件过滤的黑白名单方法、爬虫(Crawler)的网址判重模块中等等经常被用到。
    哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就能成同样的问题。布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,误报率越大,但是漏报是不可能的。
    原理:
    布隆过滤器需要的是一个位数组(和位图类似)和K个映射函数(和Hash表类似),在初始状态时,对于长度为m的位数组array,它的所有位被置0。
    在这里插入图片描述
    对于有n个元素的集合S={S1,S2…Sn},通过k个映射函数{f1,f2,…fk},将集合S中的每个元素Sj(1<=j<=n)映射为K个值{g1,g2…gk},然后再将位数组array中相对应的array[g1],array[g2]…array[gk]置为1:在这里插入图片描述
    如果要查找某个元素item是否在S中,则通过映射函数{f1,f2,…fk}得到k个值{g1,g2…gk},然后再判断array[g1],array[g2]…array[gk]是否都为1,若全为1,则item在S中,否则item不在S中。
    布隆过滤器会造成一定的误判,因为集合中的若干个元素通过映射之后得到的数值恰巧包括g1,g2,…gk,在这种情况下可能会造成误判,但是概率很小。

    4.3.3 布隆过滤器实现(了解)

    以下是一个布隆过滤器的实现,可以参考如下:

    // 布隆过滤器
    public class BloomFilter {
    
       /* BitSet初始分配2^24个bit */
       private static final int DEFAULT_SIZE = 1 << 24;
    
       /* 不同哈希函数的种子,一般应取质数 */
       private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37 };
    
       private BitSet bits = new BitSet(DEFAULT_SIZE);
    
       /* 哈希函数对象 */
       private SimpleHash[] func = new SimpleHash[seeds.length];
    
       public BloomFilter() {
          for (int i = 0; i < seeds.length; i++) {
             func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
          }
       }
    
       // 将url标记到bits中
       public void add(String str) {
          for (SimpleHash f : func) {
             bits.set(f.hash(str), true);
          }
       }
    
       // 判断是否已经被bits标记
       public boolean contains(String str) {
          if (StringUtils.isBlank(str)) {
             return false;
          }
    
          boolean ret = true;
          for (SimpleHash f : func) {
             ret = ret && bits.get(f.hash(str));
          }
    
          return ret;
       }
    
      /* 哈希函数类 */
       public static class SimpleHash {
          private int cap;
          private int seed;
    
          public SimpleHash(int cap, int seed) {
             this.cap = cap;
             this.seed = seed;
          }
    
          // hash函数,采用简单的加权和hash
          public int hash(String value) {
             int result = 0;
             int len = value.length();
             for (int i = 0; i < len; i++) {
                result = seed * result + value.charAt(i);
             }
             return (cap - 1) & result;
          }
       }
    }
    

    5 案例实现

    5.1 开发准备

    1.pom.xml的代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.2.RELEASE</version>
        </parent>
        <groupId>com.txw</groupId>
        <artifactId>crawler-webmagic</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
        <dependencies>
            <!--SpringMVC-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--SpringData Jpa-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <!--MySQL连接包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--WebMagic核心包-->
            <dependency>
                <groupId>us.codecraft</groupId>
                <artifactId>webmagic-core</artifactId>
                <version>0.7.3</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-log4j12</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--WebMagic扩展-->
            <dependency>
                <groupId>us.codecraft</groupId>
                <artifactId>webmagic-extension</artifactId>
                <version>0.7.3</version>
            </dependency>
            <!--WebMagic对布隆过滤器的支持-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>16.0</version>
            </dependency>
            <!--工具包-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
        </dependencies>
    </project>
    

    如图所示:在这里插入图片描述
    2.添加application.properties配置文件的代码如下:

    #数据库配置
    spring.datasource.driverClassName=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://192.168.56.10:3306/crawler?characterEncoding=utf-8&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=123456
    #JPA配置
    spring.jpa.database=MySQL
    spring.jpa.show-sql=true
    spring.jpa.open-in-view=false
    

    如图所示:在这里插入图片描述

    5.2 编写pojo

    演示的代码如下:

    package com.txw.pojo;
    
    import javax.persistence.*;
    import javax.persistence.GenerationType;
    /**
     * 工作信息 {@link JobInfo}
     * @author Adair
     * @date 2022/3/17 上午 11:02
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    @Entity
    public class JobInfo {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String companyName;
        private String companyAddr;
        private String companyInfo;
        private String jobName;
        private String jobAddr;
        private String jobInfo;
        private Integer salaryMin;
        private Integer salaryMax;
        private String url;
        private String time;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getCompanyName() {
            return companyName;
        }
    
        public void setCompanyName(String companyName) {
            this.companyName = companyName;
        }
    
        public String getCompanyAddr() {
            return companyAddr;
        }
    
        public void setCompanyAddr(String companyAddr) {
            this.companyAddr = companyAddr;
        }
    
        public String getCompanyInfo() {
            return companyInfo;
        }
    
        public void setCompanyInfo(String companyInfo) {
            this.companyInfo = companyInfo;
        }
    
        public String getJobName() {
            return jobName;
        }
    
        public void setJobName(String jobName) {
            this.jobName = jobName;
        }
    
        public String getJobAddr() {
            return jobAddr;
        }
    
        public void setJobAddr(String jobAddr) {
            this.jobAddr = jobAddr;
        }
    
        public String getJobInfo() {
            return jobInfo;
        }
    
        public void setJobInfo(String jobInfo) {
            this.jobInfo = jobInfo;
        }
    
        public Integer getSalaryMin() {
            return salaryMin;
        }
    
        public void setSalaryMin(Integer salaryMin) {
            this.salaryMin = salaryMin;
        }
    
        public Integer getSalaryMax() {
            return salaryMax;
        }
    
        public void setSalaryMax(Integer salaryMax) {
            this.salaryMax = salaryMax;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getTime() {
            return time;
        }
    
        public void setTime(String time) {
            this.time = time;
        }
    
        @Override
        public String toString() {
            return "JobInfo{" +
                    "id=" + id +
                    ", companyName='" + companyName + '\'' +
                    ", companyAddr='" + companyAddr + '\'' +
                    ", companyInfo='" + companyInfo + '\'' +
                    ", jobName='" + jobName + '\'' +
                    ", jobAddr='" + jobAddr + '\'' +
                    ", jobInfo='" + jobInfo + '\'' +
                    ", salaryMin=" + salaryMin +
                    ", salaryMax=" + salaryMax +
                    ", url='" + url + '\'' +
                    ", time='" + time + '\'' +
                    '}';
        }
    }
    

    如图所示:在这里插入图片描述

    5.2 编写dao

    演示的代码如下:

    package com.txw.dao;
    
    import com.txw.pojo.JobInfo;
    import org.springframework.data.jpa.repository.JpaRepository;
    /**
     * 工作信息持久层接口
     * @author Adair
     * @date 2022/3/17 上午 11:10
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    public interface JobInfoDao extends JpaRepository<JobInfo,Long> {
    }
    

    如图所示:在这里插入图片描述

    5.3 编写service

    1.演示的代码如下:

    package com.txw.service;
    
    import com.txw.pojo.JobInfo;
    import java.util.List;
    /**
     * 工作信息业务层接口
     * @author Adair
     * @date 2022/3/17 0017上午 11:12
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    public interface JobInfoService {
    
        /**
         * 保存工作信息
         * @param jobInfo
         */
        public void save(JobInfo jobInfo);
    
        /**
         * 根据条件查询工作信息
         * @param jobInfo
         * @return
         */
        public List<JobInfo> findJobInfo(JobInfo jobInfo);
    }
    

    如图所示:在这里插入图片描述
    2.演示的代码如下:

    package com.txw.service.impl;
    
    import com.txw.dao.JobInfoDao;
    import com.txw.pojo.JobInfo;
    import com.txw.service.JobInfoService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Example;
    import org.springframework.stereotype.Service;
    import javax.transaction.Transactional;
    import java.util.List;
    /**
     * 工作信息业务层实现类 {@link JobInfoServiceImpl}
     * @author Adair
     * @date 2022/3/17 上午 11:20
     * @email 1578533828@qq.com
     */
    @Service
    @SuppressWarnings("all")   // 注解警告信息
    public class JobInfoServiceImpl  implements JobInfoService {
    
        @Autowired
        private JobInfoDao jobInfoDao;
    
        /**
         * 保存工作信息
         * @param jobInfo
         */
        @Override
        @Transactional
        public void save(JobInfo jobInfo){
            // 先从数据库查询数据,根据发布日期查询和url查询
            JobInfo param = new JobInfo();
            param.setUrl(jobInfo.getUrl());
            param.setTime(jobInfo.getTime());
            // 执行查询
            List<JobInfo> list = this.findJobInfo(param);
            if (list.size() == 0) {
                // 没有查询到数据则新增或者修改数据
                this.jobInfoDao.saveAndFlush(jobInfo);
            }
        }
    
        /**
         * 根据条件查询工作信息
         * @param jobInfo
         * @return
         */
        @Override
        public List<JobInfo> findJobInfo(JobInfo jobInfo){
            // 设置查询条件
            Example example = Example.of(jobInfo);
            // 执行查询
            List<JobInfo> list = this.jobInfoDao.findAll(example);
            return list;
        }
    }
    

    如图所示:在这里插入图片描述

    5.4 编写引导类

    演示的代码如下:

    package com.txw;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableScheduling;
    /**
     * 主启动类
     * @author Adair
     * @date 2022/3/17 上午 11:28
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    @SpringBootApplication
    @EnableScheduling   // 开启定时任务
    public class CrawlerWebmagicApplication {
        public static void main(String[] args) {
            SpringApplication.run(CrawlerWebmagicApplication.class,args);
        }
    }
    

    如图所示:在这里插入图片描述

    5.5 编写url解析功能

    演示的代码如下:

    package com.txw.task;
    
    import com.txw.pojo.JobInfo;
    import com.txw.utils.MathSalary;
    import org.apache.commons.lang3.StringUtils;
    import org.jsoup.Jsoup;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    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.scheduler.BloomFilterDuplicateRemover;
    import us.codecraft.webmagic.scheduler.QueueScheduler;
    import us.codecraft.webmagic.selector.Html;
    import us.codecraft.webmagic.selector.Selectable;
    import java.util.List;
    /**
     * 工作信息定时任务 {@link JobProcessor}
     * @author Adair
     * @date 2022/3/17 下午 12:34
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    @Component
    public class JobProcessor  implements PageProcessor {
        private String url = "https://search.51job.com/list/000000,000000,0000,01%252C32,9,99,java,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=";
    
        private Site site = Site.me()
                // 设置编码
                .setCharset("gbk")
                // 抓取间隔时间
                .setSleepTime(1)
                // 设置超时时间
                .setTimeOut(1000 * 10)
                // 设置重试间隔时间
                .setRetrySleepTime(3000)
                // 设置重试的次数
                .setRetryTimes(3);
    
        /**
         * 解析页面
         * @param page
         */
        @Override
        public void process(Page page) {
            // 解析页面,获取招聘信息详情的url地址
    
            List<Selectable> list = page.getHtml().css("div.e").nodes();
            // 判断获取到的集合是否为空
            if (list.size() == 0) {
                try {
                    //如果为空,表示这是招聘信息详情页,保存数据
                    this.saveJobInfo(page);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // 如果不为空,表示这是招聘信息列表页,解析出详情页的url地址,放到任务队列中
                for (Selectable selectable : list) {
                    // 获取招聘信息详情页url
                    String jobInfoUrl = selectable.links().toString();
                    // System.out.println(jobInfoUrl);
                    // 添加到url任务列表,等待下载
                    page.addTargetRequest(jobInfoUrl);
                    // 获取下一页的url
                    String bkUrl = page.getHtml().css("div.p_in li.bk").nodes().get(1).links().toString();
                    System.out.println(bkUrl);
                    // 添加到任务列表中
                    page.addTargetRequest(bkUrl);
                }
            }
        }
    
        /**
         * 解析页面,获取招聘详情信息,保存数据
         * @param page
         */
        private void saveJobInfo(Page page) {
            //  创建招聘详情对象
            JobInfo jobInfo = new JobInfo();
            //  解析页面
            Html html = page.getHtml();
    
            //  获取数据,封装到对象中
            // 公司名称a.catn
            jobInfo.setCompanyName(html.css("div.cn p.cname a.catn", "text").toString());
            // 公司地址
            String cAddr = null;
            if (cAddr != null) {
                cAddr = Jsoup.parse(html.css("div.cn p.ltype", "text").toString()).text().replace("-","");
                cAddr = cAddr.substring(0,6);
                jobInfo.setCompanyAddr(cAddr);
                // 公司信息
                jobInfo.setCompanyInfo(html.css("div.tmsg", "text").toString());
                // 工作名字
                jobInfo.setJobName(html.css("div.cn h1", "text").toString());
                //  工作地址
                String jobAddr = Jsoup.parse(html.css("div.bmsg").nodes().get(1).toString()).text();
                //	部分公司暂没有填写公司详细地址,得非空判断
                if (StringUtils.isBlank(jobAddr)){
                    jobInfo.setJobAddr(jobInfo.getCompanyAddr());
                }else {
                    jobAddr = jobAddr.replace("地图","");
                    jobInfo.setJobAddr(jobAddr);
                }
                //  工作信息
                jobInfo.setJobInfo(Jsoup.parse(html.css("div.job_msg").toString()).text());
                // 个人薪水
                Integer[] salary = MathSalary.getSalary(html.css("div.cn strong", "text").toString());
                jobInfo.setSalaryMin(salary[0]);
                jobInfo.setSalaryMax(salary[1]);
                // 发布时间
                String time = html.css("div.cn p.msg", "text").toString();
                int length = time.lastIndexOf("发布");
                jobInfo.setTime(time.substring(length-5, length));
                // url地址
                jobInfo.setUrl(page.getUrl().toString());
                // 把结果保存起来,等待 ResultItem获取 获取
                page.putField("jobInfo",jobInfo);
            }
        }
    
        @Override
        public Site getSite() {
            return site;
        }
    
        // initialDelay当任务启动后,等待多久才执行方法
        // fixedDelay每隔多久执行方法
        @Scheduled(initialDelay = 1000,fixedDelay = 100 * 1000)
        public void process(){
            Spider.create(new JobProcessor())
                    // 初始访问url地址
                    .addUrl(url)
                    // 参数设置需要对多少条数据去重
                    .setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)))
                    // 设置线程数
                    .thread(10)
                    // 执行爬虫
                    .run();
        }
    }
    

    如图所示:在这里插入图片描述

    5.6 工具类

    演示的代码如下:

    package com.txw.utils;
    
    /**
     * 工资转换工具类
     * @author Adair
     * @date 2022/3/17 下午 1:46
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    public class MathSalary {
    
        /**
         * 获取薪水范围
         * @param salaryStr
         * @return
         */
        public static Integer[] getSalary(String salaryStr) {
            // 声明存放薪水范围的数组
            Integer[] salary = new Integer[2];
            // "500/天"
            // 0.8-1.2万/月
            // 5-8千/月
            // 5-6万/年
            String date = salaryStr.substring(salaryStr.length() - 1, salaryStr.length());
            // 如果是按天,则直接乘以240进行计算
            if (!"月".equals(date) && !"年".equals(date)) {
                salaryStr = salaryStr.substring(0, salaryStr.length() - 2);
                salary[0] = salary[1] = str2Num(salaryStr, 240);
                return salary;
            }
            String unit = salaryStr.substring(salaryStr.length() - 3, salaryStr.length() - 2);
            String[] salarys = salaryStr.substring(0, salaryStr.length() - 3).split("-");
            salary[0] = mathSalary(date, unit, salarys[0]);
            salary[1] = mathSalary(date, unit, salarys[1]);
            return salary;
        }
    
        // 根据条件计算薪水
        private static Integer mathSalary(String date, String unit, String salaryStr) {
            Integer salary = 0;
            // 判断单位是否是万
            if ("万".equals(unit)) {
                // 如果是万,薪水乘以10000
                salary = str2Num(salaryStr, 10000);
            } else {
                // 否则乘以1000
                salary = str2Num(salaryStr, 1000);
            }
    
            // 判断时间是否是月
            if ("月".equals(date)) {
                // 如果是月,薪水乘以12
                salary = str2Num(salary.toString(), 12);
            }
            return salary;
        }
    
        private static int str2Num(String salaryStr, int num) {
            try {
                // 把字符串转为小数,必须用Number接受,否则会有精度丢失的问题
                Number result = Float.parseFloat(salaryStr) * num;
                return result.intValue();
            } catch (Exception e) {
            }
            return 0;
        }
    }
    

    如图所示:在这里插入图片描述

    5.7 使用和定制Pipeline

    在WebMagic中,Pileline是抽取结束后,进行处理的部分,它主要用于抽取结果的保存,也可以定制Pileline可以实现一些通用的功能。在这里我们会定制Pipeline实现数据导入到数据库中。
    5.7.1 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里,一个Spider可以有多个Pipeline,使用Spider.addPipeline()即可增加一个Pipeline。这些Pipeline都会得到处理,例如可以使用

    spider.addPipeline(new ConsolePipeline()).addPipeline(new FilePipeline())
    

    实现输出结果到控制台,并且保存到文件的目标。

    5.7.2 已有的Pipeline

    WebMagic中就已经提供了控制台输出、保存到文件、保存为JSON格式的文件几种通用的Pipeline。
    在这里插入图片描述

    5.7.3 案例自定义Pipeline导入数据

    自定义SpringDataPipeline
    演示的代码如下:

    package com.txw.task;
    
    import com.txw.pojo.JobInfo;
    import com.txw.service.JobInfoService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.ResultItems;
    import us.codecraft.webmagic.Task;
    import us.codecraft.webmagic.pipeline.Pipeline;
    /**
     * 自定义SpringDataPipeline
     * @author Adair
     * @date 2022/3/17 0017下午 3:05
     * @email 1578533828@qq.com
     */
    @Component
    @SuppressWarnings("all")   // 注解警告信息
    public class SpringDataPipeline implements Pipeline {
    
        @Autowired
        private JobInfoService jobInfoService;
    
        @Override
        public void process(ResultItems resultItems, Task task) {
            // 获取封装好的招聘详情对象
            JobInfo jobInfo = resultItems.get("jobInfo");
            // 判断数据是否不为空
            if (jobInfo != null) {
                // 如果不为空把数据保存到数据库中
                this.jobInfoService.save(jobInfo);
            }
        }
    }
    

    如图所示:在这里插入图片描述
    在JobProcessor中修改process()启动的逻辑,添加的代码如下:

    package com.txw.task;
    
    import com.txw.pojo.JobInfo;
    import com.txw.utils.MathSalary;
    import org.apache.commons.lang3.StringUtils;
    import org.jsoup.Jsoup;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    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.scheduler.BloomFilterDuplicateRemover;
    import us.codecraft.webmagic.scheduler.QueueScheduler;
    import us.codecraft.webmagic.selector.Html;
    import us.codecraft.webmagic.selector.Selectable;
    import java.util.List;
    /**
     * 工作信息定时任务 {@link JobProcessor}
     * @author Adair
     * @date 2022/3/17 下午 12:34
     * @email 1578533828@qq.com
     */
    @SuppressWarnings("all")   // 注解警告信息
    @Component
    public class JobProcessor  implements PageProcessor {
    
        @Autowired
        private SpringDataPipeline springDataPipeline;
    
        private String url = "https://search.51job.com/list/000000,000000,0000,01%252C32,9,99,java,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=";
    
        private Site site = Site.me()
                // 设置编码
                .setCharset("gbk")
                // 抓取间隔时间
                .setSleepTime(1)
                // 设置超时时间
                .setTimeOut(1000 * 10)
                // 设置重试间隔时间
                .setRetrySleepTime(3000)
                // 设置重试的次数
                .setRetryTimes(3);
    
        /**
         * 解析页面
         * @param page
         */
        @Override
        public void process(Page page) {
            // 解析页面,获取招聘信息详情的url地址
    
            List<Selectable> list = page.getHtml().css("div.e").nodes();
            // 判断获取到的集合是否为空
            if (list.size() == 0) {
                try {
                    //如果为空,表示这是招聘信息详情页,保存数据
                    this.saveJobInfo(page);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // 如果不为空,表示这是招聘信息列表页,解析出详情页的url地址,放到任务队列中
                for (Selectable selectable : list) {
                    // 获取招聘信息详情页url
                    String jobInfoUrl = selectable.links().toString();
                    // System.out.println(jobInfoUrl);
                    // 添加到url任务列表,等待下载
                    page.addTargetRequest(jobInfoUrl);
                    // 获取下一页的url
                    String bkUrl = page.getHtml().css("div.p_in li.bk").nodes().get(1).links().toString();
                    System.out.println(bkUrl);
                    // 添加到任务列表中
                    page.addTargetRequest(bkUrl);
                }
            }
        }
    
        /**
         * 解析页面,获取招聘详情信息,保存数据
         * @param page
         */
        private void saveJobInfo(Page page) {
            //  创建招聘详情对象
            JobInfo jobInfo = new JobInfo();
            //  解析页面
            Html html = page.getHtml();
    
            //  获取数据,封装到对象中
            // 公司名称a.catn
            jobInfo.setCompanyName(html.css("div.cn p.cname a.catn", "text").toString());
            // 公司地址
            String cAddr = null;
            if (cAddr != null) {
                cAddr = Jsoup.parse(html.css("div.cn p.ltype", "text").toString()).text().replace("-","");
                cAddr = cAddr.substring(0,6);
                jobInfo.setCompanyAddr(cAddr);
                // 公司信息
                jobInfo.setCompanyInfo(html.css("div.tmsg", "text").toString());
                // 工作名字
                jobInfo.setJobName(html.css("div.cn h1", "text").toString());
                //  工作地址
                String jobAddr = Jsoup.parse(html.css("div.bmsg").nodes().get(1).toString()).text();
                //	部分公司暂没有填写公司详细地址,得非空判断
                if (StringUtils.isBlank(jobAddr)){
                    jobInfo.setJobAddr(jobInfo.getCompanyAddr());
                }else {
                    jobAddr = jobAddr.replace("地图","");
                    jobInfo.setJobAddr(jobAddr);
                }
                //  工作信息
                jobInfo.setJobInfo(Jsoup.parse(html.css("div.job_msg").toString()).text());
                // 个人薪水
                Integer[] salary = MathSalary.getSalary(html.css("div.cn strong", "text").toString());
                jobInfo.setSalaryMin(salary[0]);
                jobInfo.setSalaryMax(salary[1]);
                // 发布时间
                String time = html.css("div.cn p.msg", "text").toString();
                int length = time.lastIndexOf("发布");
                jobInfo.setTime(time.substring(length-5, length));
                // url地址
                jobInfo.setUrl(page.getUrl().toString());
                // 把结果保存起来,等待 ResultItem获取 获取
                page.putField("jobInfo",jobInfo);
            }
        }
    
        @Override
        public Site getSite() {
            return site;
        }
    
        // initialDelay当任务启动后,等待多久才执行方法
        // fixedDelay每隔多久执行方法
        @Scheduled(initialDelay = 1000,fixedDelay = 100 * 1000)
        public void process(){
            Spider.create(new JobProcessor())
                    // 初始访问url地址
                    .addUrl(url)
                    // 参数设置需要对多少条数据去重
                    .setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)))
                    // 设置线程数
                    .thread(10)
                    .addPipeline(this.springDataPipeline)
                    // 执行爬虫
                    .run();
        }
    }
    

    如图所示:在这里插入图片描述

    展开全文
  • webmagic垂直爬虫-其他

    2021-06-12 06:23:58
    webmagic是一个开源的Java垂直爬虫框架,目标是简化爬虫的开发流程,让开发者专注于逻辑功能的开发。webmagic的核心非常简单,但是覆盖爬虫的整个流程,也是很好的学习爬虫开发的材料。webmagic的主要特色:1、完全...
  • 文件是本人将webmagic、mybatis-plus、HikariCP、hutool等开源项目到一个springboot项目中搭建起来的一个爬虫框架,使用了Spring的@Scheduled注解开启定时任务去开启一个爬虫,processor类负责网站的爬取,pipline类...
  • :爬虫功能实现模块,使用WebMagic + SpringBoot + MyBatis基础架构,NLP工具包是,定制抽取逻辑,将爬取的数据持久化到MySQL数据库中,本仓库中的代码示例爬取的是虎扑步行街。 :数据分析及可视化模块,使用Spring...
  • webmagic:https

    2021-05-14 11:24:25
    安装: 将依赖项添加到您的pom.xml中:< dependency> < groupId>us.codecraft</ groupId> < artifactId>webmagic-core</ artifactId> < version>0.5.2</ version></ dependency>< dependency> < groupId>us....
  • webmagic 最新包 修复protocol_version,Received close_notify during handshake
  • webmagic爬虫

    2017-12-20 17:16:57
    里面的task包下,是所有的爬虫程序,每个类代表一个城市的网站,学习的朋友可以先看成都,自贡,攀枝花的代码,自己亲自写的。主要使用webmagic,及xpath,css,jsoup,正则。解析页面
  • webmagic-core-0.7.3.jar

    2020-05-10 14:41:46
    下载0.7.3中的maven仓库中的不支持https的问题包问题解决及其包下载,目前提供的下载包收费比较贵,应该直接从git仓库下载打包的问题需要这么高的积分不合理,在git上重新打包,帮助积分不够初次学习爬虫项目的同学
  • webMagic 爬虫demo 1.没有使用代理,导致出现多次ip访问异常,免费的代理不稳定,几乎不能用 2.多线程这块爬虫还是做的不错的 3.对知乎 csdn 天天基金都有爬过,结构还是挺好爬的 4.基本使用jsoup来解析html,但也...
  • webmagic为内核的分布式可视化爬虫框架,参考webmagic-avalon的架构,目标是提供可分散部署的爬虫框架,以及可视化的爬虫管理界面。 目前分为三个模块: maple-admin,为webmagic-avalon中的管理员中断,对应一个...
  • webmagic修复

    2018-12-24 17:04:54
    webmagic修复HTTPS下无法抓取只支持TLSv1.2的站点的bug重新打包
  • webmagic-0.7.3.zip

    2020-03-12 09:04:22
    webmagic框架所需要的jar包,相关文档资料请参考官方网站。http://webmagic.io/docs/zh/posts/ch2-install/without-maven.html
  • WebMagic源码

    2018-04-13 15:30:54
    WebMagic是一个优秀的爬虫框架。webmagic-WebMagic-0.7.3.zip 当前官网最新版本 。爬取暴走:https://blog.csdn.net/diehuang3426/article/details/79903494
  • 优雅的使用WebMagic框架,爬取唐诗别苑网的诗人诗歌数据 涉及动态加载技术的选择:PhantomJS、Selenium、HtmlUnit和JS引擎 由于页面爬取到的是非结构化数据,所以数据保存到MongoDB。 更新 2019.6.24 项目放到github...
  • webmagic0.5.2.rar

    2020-04-11 23:03:51
    webmagic开发所需要的全部jar。如果不使用maven可以下载这个jar来使用。webmagic开发所需要的全部jar 0.5.2
  • webMagicSE基于webMagic的搜索引擎爬虫实现(项目学习)使用webMagic的框架组件:主要用Site和Spider等参数的设置使用了springboot框架进行前后端的交互同时使用bootstrap框架作为前端界面框架,而且使用了其扩展的...
  • WebMagic 实现 微信公众号文章、新闻、csdn、info等网站文章爬取,可以动态设置文章爬取规则、清洗规则,基本实现了爬取大部分网站的文章。 (奇伢爬虫技术讨论群:365155351,大家可以加群一起来讨论哦~) 二.运行...
  • webmagic-core0.7.3.zip

    2020-06-18 14:11:13
    解决webmagic不能加载url为https的网站,将该压缩包解压,将jar替换掉maven的webmagic的core即可使用 名字必须一样即可
  • 最简单的爬虫设置,最好二次开发的爬虫 WebMagic 框架,它提供简单灵活的API,只需少量代码即可实现一个爬虫。webmagic采用完全模块化的设计,功能覆盖整个爬虫的生命周期(链接提取、页面下载、内容抽取、持久化),...
  • Lianjia house spider链家二手房爬虫~ Springboot + Webmagic + Mysql + Redis 简介 - 基于Java8 +、开源爬虫框架WebMagic、Springboot的一个链家爬虫,上手即用,可以指定需要爬取的城市名称、或者爬取全国。 - ...
  • 主要介绍了使用webmagic实现爬虫程序示例,需要的朋友可以参考下
  • webmagic入门demo

    2018-11-21 22:21:43
    webmagic入门demo,需要相关jar包,前往https://blog.csdn.net/qq_40374604
  • webmagic.zip

    2019-08-27 16:35:12
    Webmagic爬虫案例,快速上手,看一遍程序基本可以理解了。
  • webmagic blog: 一次JAVA爬虫之旅 前言        好早就想做一下爬虫,最近终于完成了。爬了知乎用户信息,现在知乎确实正规了好多,各种反爬虫机制,遇到了不少坑,还好都克服了,现在来记录下这个过程,和把...
  • 基于WebMagic爬虫框架的全部依赖jar包,下载即用,如何使用可以查看我的博客有详细的讲解基于WebMagic爬虫框架的爬虫开发。
  • 该ppt为本人实习期间由于公司项目需求,有幸接触websocket和webmagic,借着转正的时机,整理其概念和入门案例的总结,ppt也涉及了quartz的介绍
  • WebMagic爬虫

    2022-07-12 14:55:59
    Java开发中的一款简单灵活的爬虫框架WebMagic

    WebMagic是一款简单灵活的爬虫框架。基于它你可以很容易的编写一个爬虫。

    简介

    WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。WebMagic的架构设计参照了Scrapy,目标是尽量的模块化,并体现爬虫的功能特点。
    在这里插入图片描述
    WebMagic总体架构图如下:
    在这里插入图片描述

    WebMagic的四个组件

    1.Downloader

    Downloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。

    2.PageProcessor

    PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。

    在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。

    3.Scheduler

    Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。

    除非项目有一些特殊的分布式需求,否则无需自己定制Scheduler。

    4.Pipeline

    Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。

    Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。

    主要部分

    WebMagic主要包括两个包,这两个包经过广泛实用,已经比较成熟:

    webmagic-core

    webmagic-core是WebMagic核心部分,只包含爬虫基本模块和基本抽取器。WebMagic-core的目标是成为网页爬虫的一个教科书般的实现。

    webmagic-extension

    webmagic-extension是WebMagic的主要扩展模块,提供一些更方便的编写爬虫的工具。包括注解格式定义爬虫、JSON、分布式等支持。

    Maven依赖

    WebMagic基于Maven进行构建,推荐使用Maven来安装WebMagic。在你自己的项目(已有项目或者新建一个)中添加以下依赖即可:

            <!--web magic爬虫-->
            <dependency>
                <groupId>us.codecraft</groupId>
                <artifactId>webmagic-core</artifactId>
                <version>0.7.3</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-log4j12</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>us.codecraft</groupId>
                <artifactId>webmagic-extension</artifactId>
                <version>0.7.3</version>
            </dependency>
    

    第一个爬虫项目

    这里我以领导留言板的案例举例
    在这里插入图片描述
    网站页面如上,代码如下

    Spider

    Spider是爬虫启动的入口。在启动爬虫之前,我们需要使用一个PageProcessor创建一个Spider对象,然后使用run()进行启动。同时Spider的其他组件(Downloader、Scheduler、Pipeline)都可以通过set方法来进行设置。

    package com.zcf.spider.crawler;
    
    import com.zcf.common.config.HttpConfig;
    import com.zcf.spider.webmagic.DataPipeline;
    import com.zcf.spider.webmagic.LiuYanBanProcessor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.Request;
    import us.codecraft.webmagic.Spider;
    import us.codecraft.webmagic.model.HttpRequestBody;
    import us.codecraft.webmagic.utils.HttpConstant;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Description TODO
     * @Author zcf
     * @Date 2020/5/20
     **/
    @Component
    public class LiuYanBanCrawler {
        private static final Logger LOG = LoggerFactory.getLogger(LiuYanBanCrawler.class);
    
        @Autowired
        private LiuYanBanProcessor liuYanBanProcessor;
        @Autowired
        private DataPipeline dataPipeline;
    
        /**
         * @Scheduled(cron = "0 0 0/2 * * ? ")          //  每2个小时执行一次
         * @Scheduled(cron = "0 0/1 * * * ?")           //  每1分钟执行一次
         * @Scheduled(cron = "0/30 * * * * ?")          //  每30秒执行一次
         */
        @Async("taskExecutor")
        @Scheduled(cron = "0 0 0/2 * * ? ")
        public void governmentLiuYanBan(){
            LOG.info("执行开始时间:{}",new Date());
    
            int fid = 561;
            Map<String,Object> map = new HashMap<>();
            map.put("fid",fid);
            map.put("lastItem",0);
            String referer_url = "http://liuyan.people.com.cn/threads/list?fid="+fid;
    
            Request request = getRequest(map, referer_url);
            try {
                Thread.sleep(1000*10); // 休眠10秒
    
                Spider.create(liuYanBanProcessor)
                        .addRequest(request)
                        .thread(10)
                        .addPipeline(dataPipeline)
                        .run();
    
                LOG.info("执行结束时间:{}",new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    
        private Request getRequest(Map<String, Object> map, String referer_url){
            Request request = new Request();
            request.setMethod(HttpConstant.Method.POST);
            request.addHeader("Referer",referer_url);
            request.addHeader("Origin","http://liuyan.people.com.cn");
            request.addHeader("User-Agent",HttpConfig.USER_AGENT);
            request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            //构造map用于第一条请求的请求体
            request.setRequestBody(HttpRequestBody.form(map,"utf-8"));
            request.setUrl("http://liuyan.people.com.cn/threads/queryThreadsList");
            return request;
        }
    }
    
    Site

    Site对站点本身的一些配置信息,例如编码、HTTP头、超时时间、重试策略等、代理等,都可以通过设置Site对象来进行配置。

    package com.zcf.spider.webmagic;
    
    import com.alibaba.fastjson.JSONObject;
    import com.zcf.common.config.HttpConfig;
    import com.zcf.utils.DateUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.Page;
    import us.codecraft.webmagic.Request;
    import us.codecraft.webmagic.Site;
    import us.codecraft.webmagic.model.HttpRequestBody;
    import us.codecraft.webmagic.processor.PageProcessor;
    
    import java.util.*;
    
    /**
     * @Description TODO
     * @Author zcf
     * @Date 2020/5/19
     **/
    @Component
    public class LiuYanBanProcessor implements PageProcessor {
        private static final Logger LOG = LoggerFactory.getLogger(LiuYanBanProcessor.class);
    
        @Override
        public void process(Page page) {
            List<Map<String,Object>> infoList = new ArrayList<>();
            // 获取返回结果
            JSONObject result  = JSONObject.parseObject(page.getJson().toString());
            Boolean flag = (Boolean) result.get("success");
            if(flag) {
                List<Map<String,Object>> responseData = (List) result.get("responseData");
                for (Map<String, Object> map : responseData) {
                    Map<String, Object> info = new HashMap<>();
                    Integer threadsCheckTime = (Integer) map.get("threadsCheckTime"); //时间戳
                    String subject = (String) map.get("subject"); //标题
                    Integer tid = (Integer) map.get("tid"); //文章ID
                    String url = "http://liuyan.people.com.cn/threads/content?tid=" + tid;  //文章链接
                    info.put("eventDate",threadsCheckTime);
                    info.put("subject",subject);
                    info.put("id",tid);
                    info.put("url",url);
                    LOG.info("时间戳的信息:{}",threadsCheckTime);
                    LOG.info("标题的信息:{}",subject);
                    LOG.info("文章ID的信息:{}",tid);
                    LOG.info("文章链接的信息:{}",url);
                    LOG.info("--------------------------------------------------------");
                    infoList.add(info);
                }
                
                Map<String,Object> map = new HashMap<>();
                map.put("fid",responseData.get(responseData.size()-1).get("fid"));
                map.put("lastItem",responseData.get(responseData.size()-1).get("tid"));
                Integer threadsCheckTime = (Integer) responseData.get(responseData.size() - 1).get("threadsCheckTime");
                Date date1 = DateUtil.timestampToDateBySecond(Long.valueOf(threadsCheckTime));
                Date date2 = DateUtil.formatDateToDate(new Date());
                if(DateUtil.compareToDate(date1, date2)){ 
                    Request request = page.getRequest();
                    request.setRequestBody(HttpRequestBody.form(map,"utf-8"));
                    page.addTargetRequest(request);
                    LOG.info("开始下一页");
                }
    
            }
            page.putField("data",infoList);
        }
    
        //配置爬虫信息
        private Site site = Site.me()
                .setUserAgent(HttpConfig.USER_AGENT)
                .setCharset("utf8")
                .setTimeOut(10 * 1000)
                .setRetryTimes(3)
                .setRetrySleepTime(3000);
    
        @Override
        public Site getSite() {
            return site;
        }
    }
    
    Pipeline

    WebMagic用于保存结果的组件叫做Pipeline。

    package com.zcf.spider.webmagic;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.ResultItems;
    import us.codecraft.webmagic.Task;
    import us.codecraft.webmagic.pipeline.Pipeline;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * @Description TODO
     * @Author zcf
     * @Date 2020/5/19
     **/
    @Component
    public class DataPipeline implements Pipeline {
        private static final Logger LOG = LoggerFactory.getLogger(DataPipeline.class);
    
        @Override
        public void process(ResultItems resultItems, Task task) {
            List<Map<String,Object>> data = resultItems.get("data");
            LOG.info("总数据一共有{}条",data.size());
            // 后续数据的存储到数据库
            for (Map<String, Object> datum : data) {
                Set set = datum.keySet();
                LOG.info("--------------------------------------------------------");
                for (Object o : set){
                    LOG.info("数据参数:{}的信息:{}",o,datum.get(o));
                }
            }
        }
    }
    

    运行一下,查看一下效果
    在这里插入图片描述
    在这里我想获取的数据都已经获取到了,当然每个页面的解析都不一样。
    Jsoup是一个简单的HTML解析器,同时它支持使用CSS选择器的方式查找元素。
    Xsoup是基于Jsoup开发的一款XPath解析器。
    具体我们也可以参考官网

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,557
精华内容 1,822
关键字:

webmagic