目录
本文对应的代码都会放在 GitHub 仓库 WebMagic-aizhan-java-spider
下,如果你觉得本文以及这个项目对你有用,麻烦在 GitHub 上给我 start 一下!感激不尽!
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='爱站网搜索结果(粒度为天)';
表创建成功,结果如下:
2.2 MVC架构的搭建以及配置
首先,创建一个 SpringBoot 整合 Mybatis 项目 AizhanBaiduCrawler ,如下图:
其次,在 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 的配置文件:
<?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 即可。
//获取封装信息对应的 tr
List<Selectable> trList = page.getHtml().xpath("//div[@class='baidurank-list']/table/tbody/tr").nodes();
这行代码获取的是“ class 属性值为 baidurank-list 的 div 元素下的 table 下的 tbody 下的 tr 元素的集合 ”,即先获取包含每一个要爬取记录的行对象,其元素结构如下图:
如下代码,获取的是“ class 属性值为 search-wrap 的 div 元素下的 table 下的 from 下的 type 属性值为 text 的 input 元素的值 ”,对应的元素结构如下图
//获取查询的域名
String searchContent = page.getHtml().xpath("//div[@class='search-wrap']/form/input[@type='text']/@value").toString();
对于下面代码,主要是为了排除查询 url 不存在的情况,如搜索 xxx.com,元素结构如下图:
if(tdList.size()==2 && "未找到信息!".equals(tdList.get(1).xpath("/td/text()").toString())){
//拉取的url对应的信息不存在不存在,直接结束程序
return;
}
下面代码的主要功能是将下一页的 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");
}
更加详细的 webmagic 爬虫页面元素的抽取方法可以参考我的另一篇文章:《待写》
再在 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();
}
}
项目额整个目录结构如下图:
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”,逐步运行发现程序会自动去重,如下图:
(3)baidu.com:再次查询 “baidu.com”,发现同样会自动去重
(4)bilibili.com:查询其他域名,如“bilibili.com”,依然可以正常查询。
3.2 可能出现的问题以及解决方法
(1)出现 “java: 错误: 无效的源发行版:15”
参考文章: 解决:java无效的目标发行版: 15(百分百有效)
特别声明:
(1)本文涉及的任何代码,仅用于个人学习研究,禁止用于商业用途,本人对任何代码问题概不负责;
(2)本文涉及的所有资源文件,禁止任何公众号、自媒体在未经本人允许的情况下进行任何形式的转载、发布;
(3)如果任何单位或个人认为该项目可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关代码。