一、项目需求

简单说一下我们这个项目是干啥的,不了到最后做完也不知道干了点啥,那不完蛋?

我这里是想通过爬虫采集一些博客的数据,采集好数据之后,想着后期把这些采集到的数据都扔在 es 里(es:elasticsearch,一种分布式全文搜索引擎,可以自行了解),然后通过页面搜索关键字,找到一些自己想要的数据。

当然,光采集博客数据还不能满足自己,为了能更好的摸鱼,我还打算爬一些轻小说、短文章的数据,将这些数据采集到本地,然后有时间可以慢慢看。

但是!!!我们这里先不涉及到 es,这个东西我最近在研究,等研究到差不多的时候我会出专门的博客去讲述。

等到项目做完的时候,大家也就学的差不多了,到时候项目可以自己去进行配置,自己选择一些可以爬取的网站进行爬取一些简单的数据。

二、项目框架选择

我们爬虫项目主要基于 SpringBoot 框架,ORM 选择的是 MyBatis-Plus,爬虫我们使用的是 htmlunit。

这里主要说一下为啥用 htmlunit。我之前看过很多的爬虫框架,像 selenium、WebMagic、httpclient+jsoup 等等,很多很多,但是为啥选择了 htmlunit,这里要说一下。

比如 selenium,这个是由 Google 的很多大佬参与开发的一个产品,我们可以用它直接通过程序打开 Chrome 浏览器,然后页面会根据我们的程序自己去操作,当然,在操作的途中我们也可以获取到浏览器中的数据。但是这个玩意也是有坑的,效率贼慢,因为它是靠着浏览器驱动运行的,所以说这玩意不适合我们的项目,我们是定时爬取一大堆数据,就凭借它的效率,可以说直接凉凉。但是它也不是没有优点,selenium 几乎可以说是能爬到任何数据,只要你能用浏览器打开的页面,它都可以爬到该页面的数据。

再有就是 WebMagic 和 httpclient + jsoup 了。

先说 WebMagic,WebMagic 是一个简单灵活的Java爬虫框架,我们可以基于 WebMagic,快速地开发出一个高效、易维护的爬虫。但是为啥我们不用它呢?原因很简单,这玩意爬取不到由 js 加载的数据。我们很多页面,都是一个静态页面,然后里面的一些数据呢,是通过 js 请求后,拿到数据渲染到页面上的,类似这样的页面,WebMagic 可以说直接跪倒在地。

httpclient + jsoup 他俩虽说学起来很容易,运行速度也很快,API 也很简单,但是他俩和 WebMagic 一样,都有一个通病,就是拿不到由 js 加载的数据。

而我们选择的这个 htmlunit 可以说是比较完美了,它是一个没有界面的浏览器,通过模拟浏览器运行,拿到由 js 加载的数据后,返回给我们,然后我们再通过对页面的分析拿到我们所需要的数据,再进行后续操作,而且 API 接口啥的也比较简单易学,这也是我们选择它的原因。

三、项目搭建 & 测试运行

项目名称:

lemon1234_scraper

版本介绍:

  • MySQL 版本:8.0.27(这个版本不强求,你自己会用啥数据库就用啥数据库,那怕存到 derby、h2 都行)
  • SpringBoot 版本:2.7.0(这个同样不强求,我这里一般都是用最新的 SpringBoot~)
  • htmlunit 版本:2.61.0(这个建议是和我的一样,因为版本高了低了可能会存在差异)

pom.xml:

这里我们除去必须要引入的,还引入了 thymeleaf,这里主要是为了后面二开的同学,省的自己再去找模板引擎

<properties>
		<java.version>1.8</java.version>
		<druid.version>1.1.12</druid.version>
		<mybatis.version>2.1.4</mybatis.version>
        <mybatis_plus.version>3.4.1</mybatis_plus.version>
        <fastjson.version>1.2.78</fastjson.version>
        <hutool.version>5.3.0</hutool.version>
        <htmlunit.version>2.61.0</htmlunit.version>
        <jsoup.version>1.15.1</jsoup.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</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>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis_plus.version}</version>
        </dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		
		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        
        <dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>fastjson</artifactId>
		    <version>${fastjson.version}</version>
		</dependency>
		
		<dependency>
		    <groupId>cn.hutool</groupId>
		    <artifactId>hutool-all</artifactId>
		    <version>${hutool.version}</version>
		</dependency>
		
		<dependency>
		    <groupId>net.sourceforge.htmlunit</groupId>
		    <artifactId>htmlunit</artifactId>
		</dependency>
		
		<dependency>
		    <groupId>org.jsoup</groupId>
		    <artifactId>jsoup</artifactId>
		    <version>${jsoup.version}</version>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
	</dependencies>

application.yml:

这里根据自己的环境去配置就好了,如果不会 MyBatis-plus,建议去它官网简单看看学一学,不是很难。

server:
  port: 80
  servlet:
    context-path: /
  tomcat:
    uri-encoding: UTF-8

spring:
  application:
    name: lemon1234_scraper
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/lemon1234_scraper?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    dbcp2:
      min-idle: 5
      initial-size: 5
      max-total: 100
      max-wait-millis: 1000

# Mybatis-plus相关配置
mybatis-plus:
  global-config:
    db-config:
      id-type: UUID
      table-prefix: t_lemon1234_scraper_
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: false

logback-spring.xml

我这里日志用的是 logback,当然,你要用别的也行,无所谓的,有个能记录日志的就行。日志等级是 info。

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="../logs" />
    <!-- 日志输出格式 -->
    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_debug.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

	<root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>
</configuration>

测试:

测试前我们将数据库创建好,别因为连接不上数据库最后报错。

java 爬虫案例 java爬虫项目_网络协议

我这里启动有两个 WARN,简单解释一下。

第一个 WARN:因为我们现在项目里面除去这些配置,屁的代码都没有写,所以说像 MyBatis 的 mapper 啥的根本都没有,不报错才怪,等后面加上就没事了。

第二个 WARN:这里是因为我们引入了 thymeleaf 所导致的,如果不想看这个错误,可以去 resources 目录下创建一个 templates 的目录就行。

自己动动手,把项目创建好,等下一讲我们来设计一下表。