上一篇博客我们简单的了解了一波爬虫,知道了什么是爬虫,爬虫的作用,下面我们就开始着手自己来创建一个爬虫。(项目源码估计你们得看最后的几个博客,因为我这个是一边写代码一边写博客的,所以说没有办法那么快提供给大家源码,但是可以先提供 Git 地址)
一、项目需求
简单说一下我们这个项目是干啥的,不了到最后做完也不知道干了点啥,那不完蛋?
我这里是想通过爬虫采集一些博客的数据,采集好数据之后,想着后期把这些采集到的数据都扔在 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>
测试:
测试前我们将数据库创建好,别因为连接不上数据库最后报错。
我这里启动有两个 WARN,简单解释一下。
第一个 WARN:因为我们现在项目里面除去这些配置,屁的代码都没有写,所以说像 MyBatis 的 mapper 啥的根本都没有,不报错才怪,等后面加上就没事了。
第二个 WARN:这里是因为我们引入了 thymeleaf 所导致的,如果不想看这个错误,可以去 resources 目录下创建一个 templates 的目录就行。
自己动动手,把项目创建好,等下一讲我们来设计一下表。
这一讲就讲到这里,有问题可以联系我:QQ 2100363119,欢迎大家访问我的个人网站:https://www.lemon1234.com