Elasticsearch实战之搜索项目
Posted 一心同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Elasticsearch实战之搜索项目相关的知识,希望对你有一定的参考价值。
📢📢📢📣📣📣
哈喽!大家好,我是【一心同学】,一位上进心十足的【Java领域博主】!😜😜😜
✨【一心同学】的写作风格:喜欢用【通俗易懂】的文笔去讲解每一个知识点,而不喜欢用【高大上】的官方陈述。
✨【一心同学】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。
✨如果有对【后端技术】感兴趣的【小可爱】,欢迎关注【一心同学】💞💞💞
❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️
目录
前言
我们学了Elasticsearch的各种基本操作,可能还觉得对ES的操作并不是特别熟悉,这时候我们就应该来完成一个小项目的开发,从而巩固我们的知识!
我们先来看最终的项目效果:
环境准备
JDK:1.8
Elasticsearch:7.6.1
SpringBoot:2.6.4
在开始讲解之前,【一心同学】先扔出我自己的目录,大家可以参考一下:
一、创建项目
1.1 依赖导入
创建一个SpringBoot项目并在创建时自动导入以下依赖:
(1)导入开发基本依赖
(2)导入Web依赖
(3)导入数据交互Thymeleaf
(4)导入Elasticsearch依赖
(5)手动导入以下两个依赖
<!-- 解析网页jsoup,获取某东数据所需要的-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
总的依赖如下:
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.2 确保版本统一
我们要确保导入的ES依赖与我们自己的ES版本是一致的,故继续在pom.xml中进行以下配置:
<properties>
<java.version>1.8</java.version>
<!-- 确保版本统一-->
<elasticsearch.version>7.6.1</elasticsearch.version>
</properties>
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;
四、获取数据
注意:我们对商城的数据并不打算自己手动输入,而是直接获取其他网页的数据。
4.1 依赖
对爬取所依赖的网页解析架包我们已经导进来了,不需要再导入了,就是这个:
<!-- 解析网页jsoup,获取某东数据所需要的-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
4.2 获取网页数据
我们先去该页面打开其开发者模式,来查看其前端代码:
通过以上页面的分析,我们得到以下信息:
J_goodsList:存放所有商品的容器
li标签:存放每一个商品的小容器
p-img:存放商品图片
p-name:存放商品名称
p-price:存放商品价格
注意:
对于商品的图片我们并没办法直接获取,因为某东对图片的加载是属于懒加载机制的,<img>的src链接并拿不到我们要的图片,我们应该直接去获取其懒加载里面的图片地址:
编写网页解析工具类:
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<Content> 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<Content> contents = new ArrayList<Content>();
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<Content> contents=new HtmlParseUtil().parseJD(keywords);
BulkRequest bulkRequest=new BulkRequest();
bulkRequest.timeout("2m");
for(int i=0;i<contents.size();i++)
bulkRequest.add(
new IndexRequest("es_jd")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
);
BulkResponse bulk=restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
return !bulk.hasFailures();
6.2 分页查询
根据关键字进行分页查询
// 2、根据keyword分页查询结果
public List<Map<String, Object>> 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<Map<String,Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits())
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
results.add(sourceAsMap);
// 返回查询的结果
return results;
6.3 高亮搜索
对输入的关键字进行高亮搜索返回
public List<Map<String, Object>> 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("<span style='color:red'>");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果 ==========
SearchHits hits = searchResponse.getHits();
List<Map<String, Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits())
// 使用新的字段值(高亮),覆盖旧的字段值
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
// 高亮字段
Map<String, HighlightField> 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) throws Exception
System.out.println(new ContentService().parseContent("java"));
//获取数据存放到ES的索引es_jd中
public Boolean parseContent(String keywords) throws Exception
List<Content> contents=new HtmlParseUtil().parseJD(keywords);
BulkRequest bulkRequest=new BulkRequest();
bulkRequest.timeout("2m");
for(int i=0;i<contents.size();i++)
bulkRequest.add(
new IndexRequest("es_jd")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
);
BulkResponse bulk=restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
return !bulk.hasFailures();
@Autowired
private RestHighLevelClient restHighLevelClient;
// 2、根据keyword分页查询结果
public List<Map<String, Object>> 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<Map<String,Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits())
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
results.add(sourceAsMap);
// 返回查询的结果
return results;
// 3、 在2的基础上进行高亮查询
public List<Map<String, Object>> 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("<span style='color:red'>");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果 ==========
SearchHits hits = searchResponse.getHits();
List<Map<String, Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits())
// 使用新的字段值(高亮),覆盖旧的字段值
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
// 高亮字段
Map<String, HighlightField> 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) throws Exception
return contentService.parseContent(keywords);
@ResponseBody
@GetMapping("/search/keyword/pageIndex/pageSize")
public List<Map<String, Object>> 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<Map<String, Object>> 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:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>Java-ES仿某东实战</title>
<link rel="stylesheet" th:href="@/css/style.css"/>
<script th:src="@/js/jquery.min.js"></script>
</head>
<body class="pg">
<div class="page">
<div id="app" class=" mallist tmall- page-not-market ">
<!-- 头部搜索 -->
<div id="header" class=" header-list-app">
<div class="headerLayout">
<div class="headerCon ">
<!-- Logo-->
<h1 id="mallLogo">
<img th:src="@/images/jdlogo.png" alt="">
</h1>
<div class="header-extra">
<!--搜索-->
<div id="mallSearch" class="mall-search">
<form name="searchTop" class="mallSearch-form clearfix">
<fieldset>
<legend>京东搜索</legend>
<div class="mallSearch-input clearfix">
<div class="s-combobox" id="s-combobox-685">
<div class="s-combobox-input-wrap">
<input v-model="keyword" type="text" autocomplete="off" id="mq"
class="s-combobox-input" aria-haspopup="true">
</div>
</div>
<button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
</div>
</fieldset>
</form>
<ul class="relKeyTop">
<li><a>Java</a></li>
<li><a>Vue</a></li>
<li><a>Linux</a></li>
<li><a>Elasticsearch</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 商品详情页面 -->
<div id="content">
<div class="main">
<!-- 品牌分类 -->
<form class="navAttrsForm">
<div class="attrs j_NavAttrs" style="display:block">
<div class="brandAttr j_nav_brand">
<div class="j_Brand attr">
<div class="attrKey">
品牌
</div>
<div class="attrValues">
<ul class="av-collapse row-2">
<li><a href="#"> 一心同学 </a></li>
<li><a href="#"> Java </a></li>
</ul>
</div>
</div>
</div>
</div>
</form>
<!-- 排序规则 -->
<div class="filter clearfix">
<a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
<a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
<a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
<a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
<a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
</div>
<!-- 商品详情 -->
<div class="view grid-nosku" >
<div class="product" v-for="result in results">
<div class="product-iWrap">
<!--商品封面-->
<div class="productImg-wrap">
<a class="productImg">
<img :src="result.img">
</a>
</div>
<!--价格-->
<p class="productPrice">
<em v-text="result.price"></em>
</p>
<!--标题-->
<p class="productTitle">
<a v-html="result.name"></a>
</p>
<!-- 店铺名 -->
<div class="productShop">
<span>店铺: 一心同学 </span>
</div>
<!-- 成交信息 -->
<p class="productStatus">
<span>月成交<em>999笔</em></span>
<span>评价 <a>3</a></span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@/js/vue.min.js"></script>
<script th:src="@/js/axios.min.js"></script>
<script>
new Vue(
el:"#app",
data:
"keyword": '', // 搜索的关键字
"results":[] // 后端返回的结果
,
methods:
searchKey()
var keyword = this.keyword;
console.log(keyword);
axios.get('h_search/'+keyword+'/0/20').then(response=>
console.log(response.data);
this.results=response.data;
)
);
</script>
</body>
</html>
现在整个项目就搭建成功了!
但注意现在我们的ES索引中还没有数据,还记得我们刚刚写了一个获取数据的方法吗,我们在浏览器中输入:http://localhost:9090/parse/java
这样我们ES中就存放了关于Java的数据,这些数据都是从某东爬取过来的:
现在我们启动我们自己的项目:
由于我们没有输入任何关键词所以没有数据,现在我们往里面输入java,就可以看到有关java类型的书籍了:
至此,就大功告成了!
小结
以上就是【一心同学】通过浏览网上资料而整理的【用Elasticsearch仿某东的搜索】项目,大家在学完一个知识过后,可以自己动手做个【小项目】,从而来对知识进行【巩固】。
如果这篇【文章】有帮助到你,希望可以给【一心同学】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【后端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【一心同学】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!
以上是关于Elasticsearch实战之搜索项目的主要内容,如果未能解决你的问题,请参考以下文章