本文对应的代码都会放在 GitHub 仓库 WebMagic-aizhan-java-spider 下,如果你觉得本文以及这个项目对你有用,麻烦在 GitHub 上给我 start 一下!感激不尽!


文章目录

  • 1、本文所用技术介绍
  • 2、整体项目搭建以及代码解析
  • 2.1 数据库的创建
  • 2.2 MVC架构的搭建以及配置
  • 2.3 详细代码编写及分析
  • 3 项目测试以及相关问题的解决
  • 3.1 项目测试
  • 3.2 可能出现的问题以及解决方法
  • 4 参考资料


1、本文所用技术介绍

  • Java爬虫框架 Webmagic:我们使用 Java 爬虫框架 Webmagic 来实现爬虫,这个框架的文档为:Webmagic文档 ,详细的使用方法以及代码细节见下文。
  • 项目框架:本项目使用 springboot 整合 mybatis 作为项目的框架,使用 maven 对项目依赖进行管理,具体的项目创建方法,参照我的另一篇文章: 使用 IDEA 搭建 Springboot 整合 mybatis 项目详解,对项目创建熟悉的朋友可以略过。

2、整体项目搭建以及代码解析

2.1 数据库的创建

在本地数据库创建一个存储爬取数据的表 search_result,表的创建代码如下:

CREATE TABLE `search_result_V2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `key_char` varchar(256) NOT NULL DEFAULT '' COMMENT '关键词',
  `rank` varchar(256) NOT NULL DEFAULT '' COMMENT '排名',
  `pc_search_num` varchar(32) NOT NULL DEFAULT '' COMMENT '(PC)搜索量',
  `include_num` varchar(32) NOT NULL DEFAULT '' COMMENT '收录量',
  `page_title` varchar(255) NOT NULL DEFAULT '' COMMENT '网页标题',
  `search_content` varchar(64) NOT NULL DEFAULT '' COMMENT '用户查询的内容(网址)',
  `search_date` date NOT NULL COMMENT '查询日期',
  PRIMARY KEY (`id`),
  index search_data_index (search_content) # 创建查询内容的索引,加快查询速度
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COMMENT='爱站网搜索结果(粒度为天)';

表创建成功,结果如下:

spring boot爬虫框架 springboot写爬虫_spring


spring boot爬虫框架 springboot写爬虫_spring_02

2.2 MVC架构的搭建以及配置

首先,创建一个 SpringBoot 整合 Mybatis 项目 AizhanBaiduCrawler ,如下图:

spring boot爬虫框架 springboot写爬虫_spring_03


其次,在 pom.xml 文件下,导入相应的依赖,如下:

<!-- Springboot 以及 mybatis 依赖-->
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
		<!-- MySQL 依赖-->
		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 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>
		<!-- lombok 依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- slf4j 日志配置-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
        </dependency>

下面我们填写项目总配置文件 application.yml,将 resources 目录下的 application.properties 修改为 application.yml,并填写相应的配置:

# 端口
server:
  port: 8080

### 数据库配置,填写你的数据库以及对应的用户名和密码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/data_crawler?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8
    username: xxxx  
    password: xxxx
    driver-class-name: com.mysql.jdbc.Driver

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml  # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml  # 配置 Mapper XML 地址
  type-aliases-package: com.crawler.aizhan.dto # 配置数据库实体包路径

# slf4j日志配置
logging:
  config: src/main/resources/logback.xml #日志配置文件的位置
  level:
    com.hl.magic: trace

下面在 resources 下创建日志配置文件 logback.xml,这里需要修改的是你日子的存储地址,我这里是保存在 “src/main/resources/log” 目录下。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="src/main/resources/log"/>
    <!-- 定义日志格式  -->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] [%-30.30logger{30}] %msg%n"/>
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/AizhanCrawler_%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <logger name="org.springframework" level="INFO"/>
    <logger name="com.hl.magic" level="INFO"/>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

下面创建 mybatis 的配置文件 mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <!-- 使用驼峰命名法转换字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer"/>
        <typeAlias alias="Long" type="java.lang.Long"/>
        <typeAlias alias="HashMap" type="java.util.HashMap"/>
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
        <typeAlias alias="ArrayList" type="java.util.ArrayList"/>
        <typeAlias alias="LinkedList" type="java.util.LinkedList"/>
    </typeAliases>

</configuration>
2.3 详细代码编写及分析

在 com/crawler/aizhan 包下创建一个包 dto ,在 dto 下创建一个 SearchResult 类用于封装爬取的数据,该类 SearchResult 的字段与数据库中 search_result 表的字段是对应的,代码如下:

package com.crawler.aizhan.dto;

import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class SearchResult {
    /**
     * Id
     */
    private Integer id;

    /**
     * 关键字
     */

    private String keyChar;

    /**
     * 排名
     */
    private String rank;

    /**
     * (PC)搜索量
     */
    private String pcSearchNum;

    /**
     * 收录量
     */
    private String includeNum;

    /**
     * 网页标题
     */
    private String pageTitle;

    /**
     * 用户查询的内容(网址)
     */
    private String searchContent;

    /**
     * 查询日期
     */
    private String searchDate;
}

随后,在 com/crawler/aizhan 包下创建一个 mapper 包,在该包下创建一个 Mapper 层的接口 AizhanMapper.java,这个接口包含2个方法,其代码如下:

package com.crawler.aizhan.mapper;

import com.crawler.aizhan.dto.SearchResult;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

/**
 * @Author ThinkingOverflow
 * @Description
 */
@Mapper
@Repository
public interface AizhanMapper {
    /**
     * 将爬取的数据添加到数据库
     * @param result
     * @return
     */
    int addSearchData(SearchResult result);

    /**
     * 该日期对应的该查询关键词是否存在
     * @param searchContent
     * @param searchDate
     * @return
     */
    int selectByDateAndContent(String searchContent , String searchDate);
}

然后,我们在 resources 目录下添加一个新的文件夹 mapper,在该mapper创建一个 AizhanMapper.xml 配置文件,该文件对应的就是 AizhanMapper.java 的配置文件,代码如下:

<?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">
<!-- 注意命名空间 namespace 的包名 -->
<mapper namespace="com.crawler.aizhan.mapper.AizhanMapper">
    <!-- 添加数据 -->
    <insert id="addSearchData" parameterType="com.crawler.aizhan.dto.SearchResult">
        insert into search_result( key_char , rank , pc_search_num , include_num , page_title , search_content , search_date) values (#{keyChar} , #{rank},#{pcSearchNum},#{includeNum},#{pageTitle},#{searchContent},#{searchDate})
    </insert>
    <!--  该日期对应的该查询关键词是否存在  -->
    <select id="selectByDateAndContent" resultType="Integer" parameterType="string">
        select count(*) from search_result where search_content = #{searchContent} and search_date = #{searchDate};
    </select>

</mapper>

下面在 com/crawler/aizhan 包下创建一个包 service,在 service 再创建一个 process 包,在该包下创建 AizhanProcessor.java ,这个类的代码用于爬取数据,代码如下:

package com.crawler.aizhan.service.process;

import com.crawler.aizhan.dto.SearchResult;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.stereotype.Component;
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.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author ThinkingOverflow
 * @Description
 */
@Component
public class AizhanProcessor implements PageProcessor {
    
    private Site site = Site.me().setDomain("baidurank.aizhan.com")
            .setRetryTimes(3).setSleepTime(1000)
            .setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36")
            //查询输入userid的 cookie 用于登录(不登录无法拉取到数据)
            .addCookie("userId" , "xxxxxx");

    @Override
    public void process(Page page) {
        //获取封装信息对应的 tr
        List<Selectable> trList = page.getHtml().xpath("//div[@class='baidurank-list']/table/tbody/tr").nodes();
        //获取查询的域名
        String searchContent = page.getHtml().xpath("//div[@class='search-wrap']/form/input[@type='text']/@value").toString();
        //将“www"去除避免重复将数据插入数据库
        if(searchContent.contains("www")){
            searchContent = searchContent.substring(4 , searchContent.length());
        }
        ArrayList<SearchResult> aizhanInfoList = Lists.newArrayList();

        //换个简单点的遍历方法
        for (Selectable tr : trList) {
            List<Selectable> tdList = tr.xpath("//td").nodes();
            if(tdList.size()==2 && "未找到信息!".equals(tdList.get(1).xpath("/td/text()").toString())){
                //拉取的url对应的信息不存在不存在,直接结束程序
                return;
            }
            int size = tdList.size();
            Selectable keyCharSele = tdList.get(size-5).xpath("//a/text()");
            Selectable rankSele = tdList.get(size-4).xpath("//span/text()");
            Selectable pcSearchNumSele = tdList.get(size-3).xpath("//a/text()");
            Selectable includeNumSele = tdList.get(size-2).xpath("//a/text()");
            Selectable pageTitleSele = tdList.get(size-1).xpath("//a/text()");

            String keyChar = keyCharSele==null ? "" : keyCharSele.toString().trim();
            String rank = rankSele==null ? "" : rankSele.toString().trim();
            String pcSearchNum = pcSearchNumSele==null ? "" : pcSearchNumSele.toString().trim();
            String includeNum = includeNumSele==null ? "" : includeNumSele.toString().trim();
            String pageTitle = pageTitleSele==null ? "" : pageTitleSele.toString().trim();

            SearchResult result = new SearchResult();
            result.setKeyChar(keyChar);
            result.setRank(rank);
            result.setPcSearchNum(pcSearchNum);
            result.setIncludeNum(includeNum);
            result.setPageTitle(pageTitle);
            result.setSearchContent(searchContent);
            //设置日期格式化样式为:yyyy-MM-dd (mysql中date类型对应 yyyy-MM-dd 格式)
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String formatDate = simpleDateFormat.format(new Date());
            result.setSearchDate(formatDate);

            aizhanInfoList.add(result);
        }

        //原来的遍历方法
//        for (int i = 0; i < trList.size(); i++) {
//            Selectable selectable = trList.get(i);
//            //先判断拉取的网站数据是否存在
//            Selectable notFindInfo = selectable.xpath("/tr/td[2]/text()");
//            if(Objects.nonNull(notFindInfo) && "未找到信息!".contains(notFindInfo.toString())){
//                //拉取的url对应的信息不存在不存在,直接结束程序
//                return;
//            }
//
//            //这个坐标用于解决第一个 <tr> 内有6个 <td> 的情况(其他都只有5个 <td>)
//            int coordinate = 1;
//
//            //遍历第一个 <tr> 的时候,从第二个 <td> 开始
//            if(i==0){
//                coordinate = 2;
//            }
//            Selectable keyCharSele = selectable.xpath("/tr/td[" + coordinate + "]/a[@class='gray']/text()");
//            Selectable rankSele = selectable.xpath("/tr/td[" + (coordinate+1) + "]/span[@class='blue']/text()");
//            Selectable pcSearchNumSele = selectable.xpath("/tr/td[" + (coordinate+2) + "]/a[@rel='nofollow']/text()");
//            Selectable includeNumSele = selectable.xpath("/tr/td[" + (coordinate+3) + "]/a[@class='gray']/text()");
//            Selectable pageTitleSele = selectable.xpath("/tr/td[" + (coordinate+4) + "]/a[@name='baiduLink']/text()");
//
//            String keyChar = keyCharSele==null ? "" : keyCharSele.toString();
//            String rank = rankSele==null ? "" : rankSele.toString();
//            String pcSearchNum = pcSearchNumSele==null ? "" : pcSearchNumSele.toString();
//            String includeNum = includeNumSele==null ? "" : includeNumSele.toString();
//            String pageTitle = pageTitleSele==null ? "" : pageTitleSele.toString();
//
//            SearchResult result = new SearchResult();
//            result.setKeyChar(keyChar);
//            result.setRank(rank);
//            result.setPcSearchNum(pcSearchNum);
//            result.setIncludeNum(includeNum);
//            result.setPageTitle(pageTitle);
//            result.setSearchContent(searchContent);
//            //设置日期格式化样式为:yyyy-MM-dd (mysql中date类型对应 yyyy-MM-dd 格式)
//            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
//            String formatDate = simpleDateFormat.format(new Date());
//            result.setSearchDate(formatDate);
//
//            aizhanInfoList.add(result);
//        }

        //每次加载完一页,先把数据添加到 field 中,这样 pipeline 就可以拿到数据(等 process 执行完就会跳转到 pipeline 中去执行插入数据)
        page.putField("aizhanInfoList" , aizhanInfoList);

        //下面代码的功能:将下一页的数据放入查询链接列表
        List<Selectable> aNodes = page.getHtml().xpath("//div[@class='baidurank-pager']/ul/a").nodes();
        int nextPage = -1;
        for (int i = 0; i < aNodes.size(); i++) {
            Selectable classVal = aNodes.get(i).$("a", "class");
            if(StringUtils.isNotEmpty(classVal.toString())){
                nextPage = i +1;
                break;
            }
        }
        //不是第一页也没有超过最后一页(第一页重新进来的时候已经查找)
        if(nextPage > 0 && nextPage < aNodes.size()){
            page.addTargetRequest(aNodes.get(nextPage).$("a" , "href").toString());
        }else{
            System.out.println("finish");
        }


    }

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

这个类有几个点需要注意,首先是 Site 处需要添加 cookie 用来保存用户的登录态(没有登录的用户无法查询爱站网百度权重数据),这里需要 addCookie(“userId” , “xxxxxx”); ,此处 userId 需要你先注册登录爱站网,然后按 F12 进入开发者模式,找到“应用”,然后找到 Cookie,找到 userId 即可。

spring boot爬虫框架 springboot写爬虫_爬虫_04

在代码方面,有几个地方需要注意,都是获取元素值的代码,如:

//获取封装信息对应的 tr
List<Selectable> trList = page.getHtml().xpath("//div[@class='baidurank-list']/table/tbody/tr").nodes();

这行代码获取的是“ class 属性值为 baidurank-list 的 div 元素下的 table 下的 tbody 下的 tr 元素的集合 ”,即先获取包含每一个要爬取记录的行对象,其元素结构如下图:

spring boot爬虫框架 springboot写爬虫_spring_05


如下代码,获取的是“ class 属性值为 search-wrap 的 div 元素下的 table 下的 from 下的 type 属性值为 text 的 input 元素的值 ”,对应的元素结构如下图

//获取查询的域名
String searchContent = page.getHtml().xpath("//div[@class='search-wrap']/form/input[@type='text']/@value").toString();

spring boot爬虫框架 springboot写爬虫_java_06


对于下面代码,主要是为了排除查询 url 不存在的情况,如搜索 xxx.com,元素结构如下图:

if(tdList.size()==2 && "未找到信息!".equals(tdList.get(1).xpath("/td/text()").toString())){
      //拉取的url对应的信息不存在不存在,直接结束程序
      return;
}

spring boot爬虫框架 springboot写爬虫_java_07


下面代码的主要功能是将下一页的 url 放入 page 对象,下一次抓取才能进行。如下结构图,先遍历获取 class 属性值不为空的 a 元素,找到当前页数和下一页,然后才能找到下一页的 a 元素的 href 属性值,也就是下一页的 url。

//下面代码的功能:将下一页的数据放入查询链接列表
    List<Selectable> aNodes = page.getHtml().xpath("//div[@class='baidurank-pager']/ul/a").nodes();
    int nextPage = -1;
    for (int i = 0; i < aNodes.size(); i++) {
        Selectable classVal = aNodes.get(i).$("a", "class");
        if(StringUtils.isNotEmpty(classVal.toString())){
            nextPage = i +1;
            break;
        }
    }
    //不是第一页也没有超过最后一页(第一页重新进来的时候已经查找)
    if(nextPage > 0 && nextPage < aNodes.size()){
        page.addTargetRequest(aNodes.get(nextPage).$("a" , "href").toString());
    }else{
        System.out.println("finish");
    }

spring boot爬虫框架 springboot写爬虫_java_08



再在 service 再创建一个 pipeline 包,在该包下创建 AizhanPipeline.java ,这个类的代码用于爬取数据,代码如下,这个类主要用于将爬取的数据插入数据库里面。

package com.crawler.aizhan.service.pipeline;

import com.crawler.aizhan.dto.SearchResult;
import com.crawler.aizhan.mapper.AizhanMapper;
import org.apache.commons.collections.CollectionUtils;
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;

import java.util.ArrayList;

/**
 * @Author ThinkingOverflow
 * @Description
 */
@Component
public class AizhanPipeline implements Pipeline {
    @Autowired
    private AizhanMapper aizhanMapper;

    @Override
    public void process(ResultItems resultItems, Task task) {
        ArrayList<SearchResult> aizhanInfoList = resultItems.get("aizhanInfoList");
        if(CollectionUtils.isNotEmpty(aizhanInfoList)){
            aizhanInfoList.stream().forEach(SearchResult -> {
                aizhanMapper.addSearchData(SearchResult);
            });
        }
    }
}

随后在该 service 包下创建一个类 AizhanService.java,代码如下。需要说明的是,我们查询的粒度是“天”,因此这里先查询当天该查询域名是否已经爬取过数据,爬取过则无需重复爬取数据到数据库。

package com.crawler.aizhan.service;

import com.crawler.aizhan.mapper.AizhanMapper;
import com.crawler.aizhan.service.pipeline.AizhanPipeline;
import com.crawler.aizhan.service.process.AizhanProcessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import us.codecraft.webmagic.Spider;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
import java.util.regex.Pattern;


/**
 * @Author ThinkingOverflow
 * @Description
 */
@Service
@Slf4j
public class AizhanService {
    @Autowired
    private AizhanPipeline aizhanPipeline;

    @Autowired
    private AizhanProcessor aizhanProcessor;

    @Autowired
    private AizhanMapper aizhanMapper;

    private final String DOMAIN_NAME_PATTERN = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$";

    /**
     * 抓取爱站网数据
     */
    public void getSearchData(){
        while (true){
            System.out.println();
            System.out.println("------------------------");
            System.out.print("请输入要查询的域名:");
            Scanner in = new Scanner(System.in);
            String searchContent = in.nextLine();

            if(!isURL(searchContent)){
                log.info("输入的域名无效");
                continue;
            }

            //判断当前日期是否已经查询过该域名,如果查询过,则无须继续查询(我们查询的时间粒度是“天”)
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String searchDate = simpleDateFormat.format(new Date());
            //排除域名前面有 www 的重复情况
            if(searchContent.contains("www")){
                searchContent = searchContent.substring(4 , searchContent.length());
            }
            int count = aizhanMapper.selectByDateAndContent(searchContent, searchDate);
            if(count!=0){
                log.info("当天已经查询过" + searchContent + ",请去数据库查询数据,无须重复抓取爱站网数据");
                continue;
            }

            //先查找第一页数据
            String url = "https://baidurank.aizhan.com/baidu/" + searchContent;
            Spider.create(aizhanProcessor)
                    .addUrl(url)
                    .addPipeline(aizhanPipeline)
                    .thread(1)
                    .run();
        }

    }

    /**
     * 验证输入的字符串是否是有效域名
     * @param str
     * @return
     */
    public boolean isURL(String str){
        Pattern pattern = Pattern.compile(DOMAIN_NAME_PATTERN);
        return pattern.matcher(str).find();
    }

}

最后,在 com/crawler/aizhan 包下创建一个包 controller,创建一个 AizhanController 作为程序的入口

package com.crawler.aizhan.controller;

import com.crawler.aizhan.service.AizhanService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author ThinkingOverflow
 * @Description
 */
@RestController
public class AizhanController {

    @Autowired
    private AizhanService service;

    /**
     * 抓取爱站网数据
     */
    @RequestMapping("/aizhan")
    public void getSearchData(){
        service.getSearchData();
    }

}

项目额整个目录结构如下图:

spring boot爬虫框架 springboot写爬虫_spring boot爬虫框架_09

3 项目测试以及相关问题的解决

3.1 项目测试

先启动项目,随后我们进行测试,在浏览器输入 “http://localhost:8080/aizhan” 并回车,然后我们在控制台输入要查询的域名:
(1)baidu.com:我们先查询 “baidu.com”,等待一下发现数据已经加载到数据库,等加载完毕后,在数据库查询SELECT count(*) FROM search_resultwhere search_content = 'baidu.com'; 发现数据量为 1250 条,则说明程序运行正确。

(2)www.baidu.com:我们查询 “www.baidu.com”,逐步运行发现程序会自动去重,如下图:

spring boot爬虫框架 springboot写爬虫_爬虫_10


(3)baidu.com:再次查询 “baidu.com”,发现同样会自动去重

spring boot爬虫框架 springboot写爬虫_spring boot爬虫框架_11


(4)bilibili.com:查询其他域名,如“bilibili.com”,依然可以正常查询。

spring boot爬虫框架 springboot写爬虫_爬虫_12

3.2 可能出现的问题以及解决方法

(1)出现 “java: 错误: 无效的源发行版:15”




特别声明
(1)本文涉及的任何代码,仅用于个人学习研究,禁止用于商业用途,本人对任何代码问题概不负责;
(2)本文涉及的所有资源文件,禁止任何公众号、自媒体在未经本人允许的情况下进行任何形式的转载、发布;
(3)如果任何单位或个人认为该项目可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关代码。