目录
哈喽!大家好,我是【一心同学】,一位上进心十足的【Java领域博主】!
?【一心同学】的写作风格:喜欢用【通俗易懂】的文笔去讲解每一个知识点,而不喜欢用【高大上】的官方陈述。
?【一心同学】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。
?如果有对【后端技术】感兴趣的【小可爱】,欢迎关注【一心同学】
??????感谢各位大可爱小可爱!??????
前言
环境准备
一、创建项目
1.1 依赖导入
1.2 确保版本统一
1.3 创建ES索引
二、编写配置文件
三、注册客户端配置
四、获取数据
4.1 依赖
4.2 获取网页数据
五、编写实体类
六、编写service
6.1 存放数据
6.2 分页查询
6.3高亮搜索
七、编写controller层
7.1 编写前端前端控制类
7.2 编写数据控制类
八、前后端分离
8.1 获取vue和axios依赖
8.2 编写前端页面
小结
前言
我们学了Elasticsearch的各种基本操作,可能还觉得对ES的操作并不是特别熟悉,这时候我们就应该来完成一个小项目的开发,从而巩固我们的知识!
我们先来看最终的项目效果:
文章图片
环境准备
JDK:1.8
Elasticsearch:7.6.1
SpringBoot:2.6.4
在开始讲解之前,【一心同学】先扔出我自己的目录,大家可以参考一下:
文章图片
一、创建项目
1.1 依赖导入
创建一个SpringBoot项目并在创建时自动导入以下依赖:
(1)导入开发基本依赖
文章图片
(2)导入Web依赖
文章图片
(3)导入数据交互Thymeleaf
文章图片
(4)导入Elasticsearch依赖
文章图片
(5)手动导入以下两个依赖
org.jsoup
jsoup
1.10.2
com.alibaba
fastjson
1.2.62
总的依赖如下:
org.jsoup
jsoup
1.10.2
com.alibaba
fastjson
1.2.62
org.springframework.boot
spring-boot-starter-data-elasticsearch
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-configuration-processor
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
1.2 确保版本统一
我们要确保导入的ES依赖与我们自己的ES版本是一致的,故继续在pom.xml中进行以下配置:
1.8 7.6.1
1.3 创建ES索引
创建ES索引es_jd:
文章图片
二、编写配置文件
application.properties:
# 配置端口号
server.port=9090
# 关闭thymeleaf缓存
spring.thymeleaf.cache=false
三、注册客户端配置
对ES客户端进行注册,以便可以访问到我们自己的Elasticsearch服务。
package com.yixin.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticSearchClientConfig {
// 注册 rest高级客户端
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1",9200,"http")
)
);
return client;
}
}
四、获取数据
【Elasticsearch|Elasticsearch实战之搜索项目】注意:我们对商城的数据并不打算自己手动输入,而是直接获取其他网页的数据。
4.1 依赖
对爬取所依赖的网页解析架包我们已经导进来了,不需要再导入了,就是这个:
org.jsoup
jsoup
1.10.2
4.2 获取网页数据
我们先去该页面打开其开发者模式,来查看其前端代码:
文章图片
通过以上页面的分析,我们得到以下信息:
J_goodsList:存放所有商品的容器
li标签:存放每一个商品的小容器
p-img:存放商品图片
p-name:存放商品名称
p-price:存放商品价格
注意:
对于商品的图片我们并没办法直接获取,因为某东对图片的加载是属于懒加载机制的,
文章图片
编写网页解析工具类:
package com.yixin.utils;
import com.yixin.pojo.Content;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@Component
public class HtmlParseUtil {public static List parseJD(String keyword) throws IOException {
/// 使用前需要联网
// 请求url
String url = "http://search.jd.com/search?keyword=" + keyword;
// 1.解析网页(jsoup 解析返回的对象是浏览器Document对象)
Document document = Jsoup.parse(new URL(url), 30000);
// 使用document可以使用在js对document的所有操作
// 2.获取元素(通过id)
Element j_goodsList = document.getElementById("J_goodsList");
// 3.获取J_goodsList ul 每一个 li
Elements lis = j_goodsList.getElementsByTag("li");
//System.out.println(lis);
// 4.获取li下的 img、price、name
// list存储所有li下的内容
List contents = new ArrayList();
for (Element li : lis) {
// 由于网站图片使用懒加载,将src属性替换为data-lazy-img
String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");
// 获取li下 第一张图片
String name = li.getElementsByClass("p-name").eq(0).text();
String price = li.getElementsByClass("p-price").eq(0).text();
// 封装为对象
Content content = new Content(name,img,price);
// 添加到list中
contents.add(content);
}
//System.out.println(contents);
// 5.返回 list
return contents;
}
}
五、编写实体类
我们需要从某东网页拿到图片地址,名称,价格这三个属性,我们将其包装为一个实体类,拿到数据后直接封装为对象即可。
package com.kuang.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content implements Serializable {
private static final long serialVersionUID = -8049497962627482693L;
private String name;
private String img;
private String price;
}
六、编写service
6.1 存放数据
获取商品数据并存放到ES索引es_jd中
@Autowired
private RestHighLevelClient restHighLevelClient;
//获取数据存放到ES的索引es_jd中
public Boolean parseContent(String keywords) throws Exception{
List contents=new HtmlParseUtil().parseJD(keywords);
BulkRequest bulkRequest=new BulkRequest();
bulkRequest.timeout("2m");
for(int i=0;
i
6.2 分页查询
根据关键字进行分页查询
// 2、根据keyword分页查询结果
public List
6.3高亮搜索
对输入的关键字进行高亮搜索返回
public List highlightSearch(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
SearchRequest searchRequest = new SearchRequest("es_jd");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 精确查询,添加查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchSourceBuilder.query(termQueryBuilder);
// 分页
searchSourceBuilder.from(pageIndex);
searchSourceBuilder.size(pageSize);
// 高亮 =========
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name");
highlightBuilder.preTags("");
highlightBuilder.postTags("");
searchSourceBuilder.highlighter(highlightBuilder);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果 ==========
SearchHits hits = searchResponse.getHits();
List results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits()) {// 使用新的字段值(高亮),覆盖旧的字段值
Map sourceAsMap = documentFields.getSourceAsMap();
// 高亮字段
Map highlightFields = documentFields.getHighlightFields();
HighlightField name = highlightFields.get("name");
// 替换
if (name != null){
Text[] fragments = name.fragments();
StringBuilder new_name = new StringBuilder();
for (Text text : fragments) {
new_name.append(text);
}
sourceAsMap.put("name",new_name.toString());
}
results.add(sourceAsMap);
}
return results;
}
该类的全部代码如下:
package com.yixin.service;
import com.alibaba.fastjson.JSON;
import com.yixin.pojo.Content;
import com.yixin.utils.HtmlParseUtil;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class ContentService {public static void main(String[] args) throwsException{
System.out.println(new ContentService().parseContent("java"));
}//获取数据存放到ES的索引es_jd中
public Boolean parseContent(String keywords) throws Exception{
List contents=new HtmlParseUtil().parseJD(keywords);
BulkRequest bulkRequest=new BulkRequest();
bulkRequest.timeout("2m");
for(int i=0;
i search(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
if (pageIndex < 0){
pageIndex = 0;
}SearchRequest jd_goods = new SearchRequest("es_jd");
// 创建搜索源建造者对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 条件采用:精确查询 通过keyword查字段name
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 60s// 分页
searchSourceBuilder.from(pageIndex);
searchSourceBuilder.size(pageSize);
// 高亮
// ....
// 搜索源放入搜索请求中
jd_goods.source(searchSourceBuilder);
// 执行查询,返回结果
SearchResponse searchResponse = restHighLevelClient.search(jd_goods, RequestOptions.DEFAULT);
restHighLevelClient.close();
// 解析结果
SearchHits hits = searchResponse.getHits();
List results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits()) {
Map sourceAsMap = documentFields.getSourceAsMap();
results.add(sourceAsMap);
}// 返回查询的结果
return results;
}
// 3、 在2的基础上进行高亮查询
public List highlightSearch(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
SearchRequest searchRequest = new SearchRequest("es_jd");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 精确查询,添加查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchSourceBuilder.query(termQueryBuilder);
// 分页
searchSourceBuilder.from(pageIndex);
searchSourceBuilder.size(pageSize);
// 高亮 =========
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name");
highlightBuilder.preTags("");
highlightBuilder.postTags("");
searchSourceBuilder.highlighter(highlightBuilder);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果 ==========
SearchHits hits = searchResponse.getHits();
List results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits()) {
// 使用新的字段值(高亮),覆盖旧的字段值
Map sourceAsMap = documentFields.getSourceAsMap();
// 高亮字段
Map highlightFields = documentFields.getHighlightFields();
HighlightField name = highlightFields.get("name");
// 替换
if (name != null){
Text[] fragments = name.fragments();
StringBuilder new_name = new StringBuilder();
for (Text text : fragments) {
new_name.append(text);
}
sourceAsMap.put("name",new_name.toString());
}
results.add(sourceAsMap);
}
return results;
}
}
七、编写controller层
7.1 编写前端前端控制类
IndexController类:
package com.yixin.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping({"/","index"})
public String index(){
return "index";
}
}
7.2 编写数据控制类
ContentController类:
package com.yixin.controller;
import com.yixin.service.ContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@RestController
public class ContentController {@Autowired
private ContentService contentService;
@GetMapping("/parse/{keywords}")
public Boolean parse(@PathVariable("keywords") String keywords) throwsException{
returncontentService.parseContent(keywords);
}@ResponseBody
@GetMapping("/search/{keyword}/{pageIndex}/{pageSize}")
public List parse(@PathVariable("keyword") String keyword,
@PathVariable("pageIndex") Integer pageIndex,
@PathVariable("pageSize") Integer pageSize) throws IOException{
return contentService.search(keyword,pageIndex,pageSize);
}@ResponseBody
@GetMapping("/h_search/{keyword}/{pageIndex}/{pageSize}")
public List highlightParse(@PathVariable("keyword") String keyword,
@PathVariable("pageIndex") Integer pageIndex,
@PathVariable("pageSize") Integer pageSize) throws IOException {
System.out.println("*********************************");
return contentService.highlightSearch(keyword,pageIndex,pageSize);
}
}
八、前后端分离
8.1 获取vue和axios依赖
前提:已经安装了nodejs环境在任意目录下打开控制台分别输入以下命令:
npm install vue
npm install axios
我们分别拿到axios.min.js和vue.min.js这两个文件,并存放在js目录中。
文章图片
文章图片
8.2 编写前端页面
由于这个页面的css和js样式【一心同学】没办法展示出来,大家可以自己去找一个前端模板,然后按我的思路来写就可以了。
编写templates目录下的index.html:
Java-ES仿某东实战 - 锐客网
文章图片
- Java
- Vue
- Linux
- Elasticsearch
综合
人气
新品
销量
价格
文章图片
店铺: 一心同学 月成交999笔
评价 3
现在整个项目就搭建成功了!
但注意现在我们的ES索引中还没有数据,还记得我们刚刚写了一个获取数据的方法吗,我们在浏览器中输入:http://localhost:9090/parse/java
这样我们ES中就存放了关于Java的数据,这些数据都是从某东爬取过来的:
文章图片
现在我们启动我们自己的项目:
文章图片
由于我们没有输入任何关键词所以没有数据,现在我们往里面输入java,就可以看到有关java类型的书籍了:
文章图片
至此,就大功告成了!
小结
以上就是【一心同学】通过浏览网上资料而整理的【用Elasticsearch仿某东的搜索】项目,大家在学完一个知识过后,可以自己动手做个【小项目】,从而来对知识进行【巩固】。
如果这篇【文章】有帮助到你,希望可以给【一心同学】点个赞,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【后端技术】感兴趣的小可爱,也欢迎关注?????? 【一心同学】??????,我将会给你带来巨大的【收获与惊喜】!
推荐阅读
- java|测试开发岗面试系列——滴滴面试题
- kubernetes|2021超全整理,128道kubenetes高频面试题汇总(带答案)
- 数据结构|算法系列--动态规划
- java|java 动态实现数组增删(韩顺平)
- java|Aspose.Words 19.X 文档转换 反编译破解 Crack
- JavaWeb|JavaWeb(一) Servlet
- Linux操作系统|服务器如何部署并启动前后端分离(springboot+vue)的web项目
- 面试|测试开发岗面试系列——滴滴二面
- 数据机构与算法|找规律之动态规划系列