此博客是學習了b站up主狂神說java的相關課程整理而來
適合沒有接觸過ElasticSearch的小白食用
狂神b站地址:https://space.bilibili.com/95256449/
如果需要相關的文件和Demo源碼等資源,可以評論博主
編程之路漫長,本人也是今年上岸的小白一個,如果有志同道合的朋友可以加群一起學習討論,互相幫助:817080571
概述
ElasticSearch ,簡稱為es,是一個開源的、高拓展的分布式全文檢索引擎,它可以近乎實時的存儲、檢索數據。本身拓展性很好,可以拓展到上百台服務器,處理PB級別的數據。ElasticSearch使用java開發並且使用Lucene作為其核心來實現所有索引和搜索的功能,但是他的目的是通過簡單的RestFul API來隱藏Lucene的復雜性,從而讓全文檢索變得簡單。
ElasticSearch和Solr的比較
- es基本上是開箱即用,非常簡單,Solr安裝略麻煩;
- Solr利用Zookeeper進行分布式管理,而elasticsearch自身就帶有分布式協調管理功能;
- Solr支持更多格式的數據,例如:json,xml,csv,而ElasticSearch僅僅支持json文件格式;
- Solr官方提供的功能更多,而ElasticSearch本身更注重核心功能,高級功能多由第三方插件提供,例如圖形化界面需要kibana友好支持;
- Solr查詢快,但更新索引時慢(即插入刪除慢),用於電商的查詢多的應用;
- ElasticSearch建立索引快(即查詢慢),即實時性查詢快,用於新浪的搜索;
- Solr是傳統搜索應用的解決方案,但ElasticSearch更適用於新興的實時搜索應用。
- Solr比較成熟,有一個更大、更成熟的用戶、開發和貢獻者社區,而ElasticSearch相對開發維護者較少,更新太快,學習使用成本較高。
ElasticSearch安裝及啟動
聲明:ElasticSearch要求安裝環境必須是要jdk1.8以上才行。
1、下載安裝包
2、解壓安裝包完成解壓
解壓完成即安裝完成。
3、ElasticSearch目錄環境說明
- bin:可執行文件
- config:配置文件
- elasticsearch.yml:項目配置文件
- jvm.options:jvm相關的配置文件
- log4j2.properties:日志相關配置文件
- jdk:相關的jdk環境
- lib:項目所使用的相關jar包
- logs:日志信息
- modules:模塊信息
- plugins:插件信息
4、啟動ElasticSearch
雙擊elasticsearch.bat運行。
5、訪問
訪問成功!
可視化界面的安裝
聲明:可視化界面的安裝必須基於node.js的環境
1、下載可視化界面的項目壓縮包
2、解壓
3、啟動可視化頁面
因為是基於node.js的前端項目,所以我們先要進入到項目文件,在cmd窗口中使用npm install
命令安裝相關環境,然后使用npm run start
運行項目:
啟動完成,訪問端口是9100。
解決可視化界面訪問ElasticSearch產生的跨域問題
可視化界面的端口是9100,通過可視化界面去訪問9200的ElasticSearch會產生跨域問題。
修改ElasticSearch.yml文件讓其支持跨域請求
在yml文件的最后加上允許跨域的配置,並重啟ElasticSearch。
重啟之后再使用可視化界面進行訪問:
連接成功!
Kibana
概述
Kibana是一個針對ElasticSearch的開源分析及可視化平台,用於搜索、查看交互存儲在ElasticSearch索引中的數據。使用Kibana,可以通過各種圖表進行高級數據分析及展示。Kibana讓海量數據更容易理解,它操作簡單,基於瀏覽器的用戶界面可以快速創建儀表板實時顯示ElasticSearch查詢動態。設置Kibana非常簡單,無需編碼或者額外的基礎架構,幾分鍾就可以完成安裝並啟動索引監測。
注意事項:Kibana的版本必須要與安裝的es版本一致。
我們可以將es理解為一個處理海量數據的數據庫,Kibana可以監測並分析數據庫中的數據信息。
Kibana的安裝和啟動
1、下載安裝包
2、解壓安裝包
3、啟動Kibana
4、訪問
訪問成功!
Kibana的漢化
Kibana是支持漢化的,我們只需要在他的項目yml文件中進行相關配置即可:
配置保存之后重啟Kibana:
漢化完成!
ES核心概念
ElasticSearch是一個面向文檔的數據庫,其中的所有數據都是json,以下是各種專用名詞和關系型數據庫的對比:
ElasticSearch中可以包含多個索引(數據庫),每個索引中可以包含多個類型(表),每個類型下又包含多個文檔,每個文檔中又包含多個字段。
物理設計:
ElasticSearch在后台把每個索引划分為多個分片,每份分片可以在集群的不同服務器間遷移。ElasticSearch一啟動就是一個集群,哪怕只有一個,默認的集群名稱為:elasticsearch。
邏輯設計
在ES中,一個索引中包含多個文檔,當我們索引一篇文檔時,可以通過這樣的順序找到它:索引>類型>文檔ID。通過這個組合我們就能索引到某個具體的文檔。
文檔
文檔的概念換算到關系型數據庫中就類似於一條數據。
ES是面向文檔的,也就意味着索引和搜索數據的最小單位是文檔,在ElasticSearch中,文檔有幾個重要屬性:
- 自我包含:一片文檔同時包含字段和對應的值,也就是同時包含key-value;
- 可以是層次型的:一個文檔包含另一個文檔;
- 靈活的結構:文檔不依賴預先定義的模式,我們知道關系型數據庫中,要提前定義字段才能使用。在ElasticSearch中,對於字段是非常靈活的,有時候我們可以忽略該字段,或者動態的添加一個新的字段。
類型
類型是文檔的邏輯容器,就像關系型數據庫中表格是行的容器一樣,類型對於字段的定義稱之為映射,比如說name
映射為字符串類型。
我們說文檔是無模式的,我們不需要對我們新增的每一個字段的類型進行映射,在沒有進行映射的時候Elasticsearch會對數據的類型進行猜測,但是也有可能會猜不對,所以最安全的方式提前定義好所需要的映射,這一部分就跟關系型數據庫差不多了。
索引
索引就類似於關系型數據庫中的數據庫。
索引是映射類型的容器,ElasticSearch中的索引是一個非常大的文檔集合。索引存儲了映射類型的字段和其他設置,然后他們被保存到了各個分片上。
物理設計:節點和分片如何工作
一個集群至少有一個節點,就是最基本的elasticsearch進程,每個節點可以有多個索引,如果你創建索引,則,默認會創建5個分片(又稱主分片),每一個分片都會有一個副本(又稱復制分片)。
例如上圖是一個有三個節點的集群,可以看到主分片對應的復制分片都不會在同一個節點內,這樣就有利於如果某個節點掛掉了,數據也不至於丟失,
實際上,一個分片是一個lucene索引,一個包含倒排索引的文件目錄,倒排索引的結構使得ElasticSearch在不掃描全部文檔的情況下,就能告訴你哪些文檔包含特定的關鍵字。
等等,倒排索引是什么????
倒排索引
Lucene采用倒排索引作為底層,這種設計適用於快速的全文搜索。
在ElasticSearch中,倒排索引的做法是對索引中的每個單詞都進行重構為一個索引列表, 這樣就可以清楚的反應每個單詞在文檔中的位置,當我們想要查找某個數據的時候,根據倒排索引生成的索引列表就可以最大限度的避免不符合數據的重復查詢,只會在包含該數據的文檔中進行查詢。
例如下圖數據:
在以上數據中可以得知:python這個詞條,在1,2,3號文檔中都有出現,linux這個詞條在3,4號文檔中出現,當我們想要查詢linux這個詞條的時候,根據倒排索引生成的索引列表,就不會再去查詢1,2這兩個文檔,最大限度的避免了無用數據的查詢。
ElasticSearch索引和Lucene索引的關系
在ElasticSearch中,每創建一個索引就會生成多個分片,其實每個分片就是一個Lucene索引,所以一個ElasticSearch索引本質就是用多個Lucene索引構成的。
ik分詞器插件
什么是分詞器?
分詞的意思就是把一段文字分成一個個的關鍵字,我們在搜索的時候會把自己的信息進行分詞,會把數據庫中的數據進行分詞,然后進行一個匹配操作。
例如,我搜索”奧特曼打小怪獸“,在搜索結果中你可能看到只包含”奧特曼“的信息,也有可能看到只包含"小怪獸"的信息,這就是程序對我們的搜索信息進行了分詞。
而默認的中文分詞器是將每一個字分成一個詞,例如:”奧特曼“分解成”奧“”特“”曼“。這種粒度的分詞顯然是不太方便的,所以在此我們使用ik分詞器插件。
ik分詞器提供了兩個分詞算法:ik_smart和ik_max_word,其中ik_smart為最少切分,ik_max_word為最細粒度切分。
安裝ik分詞器
1、下載
2、將壓縮包解壓到ElsticSearch的插件包中
3、重啟ElasticSearch
重啟可以在日志中觀察到ik分詞器插件被加載。
4、使用ElasticSearch的命令查看插件是否安裝成功
在ElasticSearch的bin目錄中打開cmd窗口,輸入命令elasticsearch-plugin list
即可查看安裝的插件列表:
使用Kibana發送請求展示ik分詞器效果
json代碼:
GET _analyze //請求分詞器
{
"analyzer": "ik_smart", //選擇分詞算法
"text": "奧特曼打小怪獸"
}
分詞效果:
由上可以看到,分詞器會根據語句中的詞語進行分割,但是他們怎么認使什么字連起來是一個詞呢?這是因為在分詞器的內部有一個自己的字典,可以識別常用的正常詞語,當我們輸入一個人名或者自己捏造的詞語的時候就無法達到想要的效果,例如:
明顯:梅雪是一個可愛鬼的名字,但是分詞器卻沒法進行分詞,因為內部的字典無法識別梅雪這兩個字的組合。
此時,我們就需要給ik分詞器的詞典進行自定義。
自定義ik分詞器詞典信息
我們打開ik分詞器的配置文件,發現可以在配置文件中自定義詞典配置:
所以,我們先需要創建一個自己的詞典:
如圖,自定義了一個love.dic詞典,並在其中保存了梅雪
這個詞條,然后將這個詞典配置到xml節點中即可。
配置完成之后,重啟es,查看效果:
自定義詞典成功!
注意:自定義詞典的文件編碼格式必須是UTF-8,不然的話會無效。
ElasticSearch基本操作
操作ElasticSearch的命令都是通過RestFul風格的請求命令去完成的,大致如下圖:
使用Kibana演示基礎操作
創建一個索引
語法:
PUT /索引名/類型名/文檔id
{
請求體
}
在head頁面上查看數據:
由以上示例可知,我們通過put命令創建了一個索引並添加了文檔數據,但是我們並沒有給這個索引映射類型,在ElasticSearch中數據有多少的數據類型呢?
ElasticSearch相關的數據類型:
- 字符串類型:text、keyword
- 數值類型:long、integer、short、byte、doule、float、half float、scaled float
- 日期類型:date
- 布爾值類型:boolean
- 二進制類型:binary
- 等等......
創建一個索引不賦值並指定類型
以上json命令只創建了一個test2索引並映射類型
查看索引信息
通過GET
命令就可以查看到索引的信息,后面的目標准確到索引就查看索引信息,准確到文檔就查看文檔信息。
修改文檔信息
修改文檔信息有兩種方式,第一種是通過PUT命令在原來的文檔上添加數據進行覆蓋,第二種是通過POST進行修改。
1)PUT覆蓋修改
使用PUT進行修改有一個弊端,就是他會將所有的數據都進行覆蓋,如果你修改的字段有缺漏,則缺漏的部分會被覆蓋為空,造成數據的丟失。
2)POST方式修改
刪除索引
刪除索引信息通過DELETE命令來完成。
DELETE操作和GET操作一樣,后面的目標精確到文檔就刪除文檔信息,精確到索引就刪除索引信息。
復雜查詢操作
條件查詢
語法:
GET 索引名/類型名/_search?q=字段名:字段值
關於基礎條件查詢語句的解析:
其中_search后面的q的意思是query,在語法中是一個對象,完整的寫法應該如下:
如上圖所示,我們可以在query對象中設置很多的參數來完成各種情況下的查詢方式。查詢的結果中包含一個hits對象,這個對象的參數就包含了所有具體的查詢結果。
條件查詢_只顯示某幾個字段
默認情況下的條件查詢會將文檔的所有字段都查詢出來,但是我們可以通過一個_source屬性去指定想要查詢出來的字段:
由上圖可以看到,當我們指定了只查詢"name"字段的時候,后面查詢出來的信息中就只包含了"name"字段信息。
根據指定字段排序查詢
如上圖所示,按照age字段進行降序排列,因為我們自定義了按照某種規則進行排序嗎,所以之前的排序規則分數就會為Null。
分頁查詢
我們可以通過設置"from"和"size"參數來設置分頁查詢的相關信息。
布爾值查詢
通過布爾值查詢的方式我們可以實現類似於數據庫的多條件查詢:
例如通過這個must指令就可以實現多條件查詢,在上圖中,只有同時滿足name中包含張三,並且年齡為18的數據才會被查詢出。
簡單的來說滿足這兩個條件就會返回true的布爾值然后被查詢出來,所以被叫做布爾值查詢,相當於sql語句中的where and條件語句。
而should命令則表示后方的兩個條件只需要滿足其中之一即可,就類似於sql語句中的where or條件語句。
同理,must_not表示查詢出不滿足條件的數據,例如上圖查詢出年齡不為18的信息,相當於sql中的not條件語句。
過濾查詢操作
在滿足多種條件查詢的同時,es也支持我們對查詢的數據進行進一步的篩選過濾。
通過上圖的配置可以實現按照年齡大小進行進一步過濾的操作,gte是大於等於操作,lte是小於等於操作,gt只表示大於操作,lt只表示小於操作。
同時,也可以同時設置大於和小於來進行值的區間搜索操作,相當於sql中的between and條件。
匹配多個條件查詢
匹配多個條件查詢就有點類似於sql中的in關鍵字。
如圖所示,tags是興趣標簽,在數據中是以數組的形式存在的,也就是說有多個值,通過這種方式就可以進行多個值的隨意匹配。
精確匹配term
term精確匹配和match的不同:
term會將條件依據倒排索引進行精確匹配,而match則會將查詢條件進行分詞然后再匹配。簡單的來說,match會產生類似與模糊查詢的效果,而term不會,條件匹配不上即使數據包含查詢條件也不會被查詢出來。
關於text和keyword類型:
text類型和keyword的不同之處在於,text會被分詞器進行分詞,而keyword不會被分詞器分詞。
高亮查詢
ElasticSearch同時也支持高亮查詢,他會將查詢結果中的查詢條件關鍵字進行自動的高亮顯示。
SpringBoot集成ElasticSearch
此次SpringBoot集成ElasticSearch采用SpringBoot腳手架來進行學習。
1、勾選引入ElasticSearch依賴
勾選之后需要注意的是,我們在此使用的SpringBoot版本為2.2.5,此版本的ES客戶端並不是本地安裝的7.6.1版本:
因此,我們需要在pom.xml進行ES客戶端版本的自定義配置:
完成配置之后刷新項目,查看依賴版本:
版本依賴導入完成!
2、書寫配置類將ES對象注入到Spring容器之中
package com.xsh.es_api.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 {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client=new RestHighLevelClient(
RestClient.builder(
//ES集群的相關信息,如果有多個就配置多個
new HttpHost("localhost",9200,"http")
)
);
return client;
}
}
至此,SpringBoot集成ElasticSearch就完成了。
關於java操作ES索引的API
關於java API的學習都會在測試類中進行完成。
創建索引
@Autowired
RestHighLevelClient restHighLevelClient;
//測試索引的創建
@Test
void createIndex() throws IOException {
//創建索引請求
CreateIndexRequest java_index = new CreateIndexRequest("java_index");
//客戶端執行請求創建索引
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(java_index, RequestOptions.DEFAULT);
}
創建成功!
判斷索引是否存在
@Autowired
RestHighLevelClient restHighLevelClient;
//測試判斷索引是否存在
@Test
void existIndex() throws IOException {
GetIndexRequest java_index = new GetIndexRequest("java_index");
boolean flag = restHighLevelClient.indices().exists(java_index, RequestOptions.DEFAULT);
System.out.println(flag); //返回布爾值表示索引是否存在
}
代碼效果:
索引存在!
刪除索引
@Autowired
RestHighLevelClient restHighLevelClient;
//測試刪除索引
@Test
void deleteIndex() throws IOException {
DeleteIndexRequest java_index = new DeleteIndexRequest("java_index");
AcknowledgedResponse delete = restHighLevelClient.indices().delete(java_index, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged()); //獲取刪除成功與否的提示信息
}
代碼效果:
刪除成功!
關於java操作ES文檔的API
添加文檔
@Autowired
RestHighLevelClient restHighLevelClient;
//測試添加文檔
@Test
void addDocument() throws IOException {
//創建對象
User user=new User();
user.setName("張三");
user.setAge(18);
//創建請求
IndexRequest java_index = new IndexRequest("java_index");
//填充規則
java_index.id("1"); //文檔編號
//將對象放入請求中
java_index.source(JSON.toJSONString(user), XContentType.JSON);
//客戶端發送請求,接收響應結果
IndexResponse index = restHighLevelClient.index(java_index, RequestOptions.DEFAULT);
//打印響應結果
System.out.println(index.toString()); //查看返回的具體json信息
System.out.println(index.status()); //查看操作的狀態
);
}
因為ES只支持json格式的數據流通,所以在將對象放入請求的過程中需要將對象序列化為josn字符串,在此需要阿里巴巴的fastjson支持:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
查看添加文檔結果:
添加成功!
判斷文檔是否存在
@Autowired
RestHighLevelClient restHighLevelClient;
//測試判斷文檔是否存在
@Test
void ExistDocument() throws IOException {
GetRequest java_index = new GetRequest("java_index", "1");
boolean exists = restHighLevelClient.exists(java_index, RequestOptions.DEFAULT);
System.out.println(exists); //返回布爾值表示是否存在
}
代碼結果:
一號文檔存在!
獲取文檔信息
@Autowired
RestHighLevelClient restHighLevelClient;
//測試獲取文檔信息
@Test
void getDocument() throws IOException {
GetRequest java_index = new GetRequest("java_index", "1");
GetResponse getResponse = restHighLevelClient.get(java_index, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString()); //打印文檔的內容
System.out.println(getResponse); //getResponse對象就包含ES的所有查詢信息
}
代碼效果:
修改文檔記錄
@Autowired
RestHighLevelClient restHighLevelClient;
//測試修改文檔信息
@Test
void updateDocument() throws IOException {
UpdateRequest java_index = new UpdateRequest("java_index", "1");
User user=new User();
user.setName("法外狂徒張三");
user.setAge(20);
java_index.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse update = restHighLevelClient.update(java_index, RequestOptions.DEFAULT);
System.out.println(update.status()); //查看更新狀態
}
查看結果:
修改成功!
刪除文檔信息
@Autowired
RestHighLevelClient restHighLevelClient;
//刪除文檔信息
@Test
void deleteDocument() throws IOException {
DeleteRequest java_index = new DeleteRequest("java_index", "1");
DeleteResponse delete = restHighLevelClient.delete(java_index,RequestOptions.DEFAULT);
System.out.println(delete.status()); //查看刪除狀態
}
查看結果:
刪除成功!
批量操作
ES同時也支持批量增刪改的操作,在此只演示批量添加操作:
@Autowired
RestHighLevelClient restHighLevelClient;
//測試批量添加文檔下信息
@Test
void bulkDocument() throws IOException {
//創建批量操作對象
BulkRequest bulkRequest = new BulkRequest();
ArrayList<User> list = new ArrayList<>();
list.add(new User("張三1",12));
list.add(new User("張三2",12));
list.add(new User("張三3",12));
list.add(new User("張三4",12));
list.add(new User("張三5",12));
for (int i=0;i<list.size();i++){
bulkRequest.add(
new IndexRequest("java_index").id(""+(i+1))
.source(JSON.toJSONString(list.get(i)),XContentType.JSON));
}
//發送請求
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulk.hasFailures()); //查看狀態,是否失敗,返回false代表成功
}
查看結果:
查詢文檔信息
@Autowired
RestHighLevelClient restHighLevelClient;
//測試查詢文檔信息
@Test
void search() throws IOException {
//創建請求對象
SearchRequest java_index = new SearchRequest("java_index");
//構造搜索條件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//使用工具類構造搜索信息
MatchQueryBuilder query = QueryBuilders.matchQuery("name", "張三1");
searchSourceBuilder.query(query);
java_index.source(searchSourceBuilder);
//發送請求
SearchResponse search = restHighLevelClient.search(java_index, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(search.getHits())); //Hits對象就包含查詢的各種信息
}
查詢結果:
Hits對象中包含的是所有的查詢結果信息,我們可以通過遍歷想要的參數獲得具體的信息。
對於復雜查詢的各種操作都可以在searchSourceBuilder對象的方法中找到對應的方法:
ElasticSeatch項目實戰:京東搜索
此次項目實戰采用java爬蟲爬取京東的數據放在es數據源中,然后通過頁面來模擬京東搜索。
1、項目搭建
創建項目並引入相關pom依賴
相關pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xsh</groupId>
<artifactId>es_jdsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es_jdsearch</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.6.1</elasticsearch.version>
</properties>
<dependencies>
<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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yml相關配置
server:
port: 8080
#關閉thymeleaf緩存
spring:
thymeleaf:
cache: false
引入靜態資源
編寫控制器訪問index.html頁面
package com.xsh.es_jdsearch.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/")
public String toIndex(){
return "index";
}
}
訪問頁面
2、使用jsoup爬取京東相關的數據
引入jsoup依賴
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
編寫工具類解析網頁爬取數據
package com.xsh.es_jdsearch.utils;
import com.xsh.es_jdsearch.pojo.Content;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class HtmlParseUtils {
public static void main(String[] args) throws IOException {
parseJD("java").forEach(System.out::println);
}
public static List<Content> parseJD(String keyWord) throws IOException {
//獲取請求 https://search.jd.com/Search?keyword=java
String url="https://search.jd.com/Search?keyword="+keyWord;
//根據url解析網頁 Jsoup返回的document對象就是javascript中的頁面對象,所有在javascript中能夠使用的方法在這里都能使用
Document document = Jsoup.parse(new URL(url), 30000); //第二個參數為最大連接時間,超時即報錯
//通過document對象獲取頁面上的一部分元素
Element element = document.getElementById("J_goodsList"); //element是獲取的商品列表的主要信息
//獲取到所有的li元素,商品信息部分是用ul來裝載的,所以要先獲取到所有的li元素
Elements elements = element.getElementsByTag("li");
//通過li標簽我們可以獲取到每一個li標簽中的商品信息,在此我們主要獲取三個部分:圖片地址,標題,價格
ArrayList<Content> contentList = new ArrayList<>();
for (Element el: elements) { //每一個el都是一個li
//獲取圖片地址,在此獲取的並不是img的src屬性,而是source-data-lazy-img屬性
//原因是因為京東為了追求網頁渲染的速度,會在圖片渲染之前先渲染一個默認的頁面,而真實的圖片路徑會放在source-data-lazy-img中進行懶加載
String img = el.getElementsByTag("img").eq(0).attr("src");
String title = el.getElementsByClass("p-name").eq(0).text();
String price = el.getElementsByClass("p-price").eq(0).text();
Content content = new Content(img, title, price);
contentList.add(content);
}
return contentList;
}
}
注意:該類可以通過jsoup爬取頁面上的相關信息,在爬取圖片的時候狂神使用的是source-data-lazy-img屬性,但是我在寫的時候這個屬性是無效的,還是使用的src屬性才獲取到的圖片地址。
3、書寫接口使用工具類將解析到的數據插入到ElasticSearch中
創建jd_index索引
在此通過圖形化界面快速創建索引:
注入ElasticSearch客戶端對象
要實現對ES數據進行操作,首先肯定要通過配置類來注入客戶端對象:
package com.xsh.es_jdsearch.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 {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")
)
);
return client;
}
}
Controller代碼展示
//添加數據到es接口
@GetMapping("/insert/{keyword}")
public Boolean insertToEs(@PathVariable("keyword") String keyword) throws IOException {
return jdService.insertToEs(keyword);
}
ServiceImpl代碼展示
//批量插入使用jsoup查詢到的數據
@Override
public Boolean insertToEs(String keyword) throws IOException {
List<Content> contents = HtmlParseUtils.parseJD(keyword);
//創建批量插入對象
BulkRequest bulkRequest = new BulkRequest();
//封裝數據
for (int i=0;i<contents.size();i++){
bulkRequest.add(
new IndexRequest("jd_index")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
);
}
//發送請求進行數據插入
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures(); //返回結果是是否出現錯誤,插入成功則返回false,所以在此要取反
}
訪問接口就可以調用jsoup工具類將解析到的網頁數據插入到es索引中,結果如下:
4、書寫接口分頁帶條件查詢信息
數據有了之后,就是做數據展示,在此接口接收查詢的關鍵字和分頁的信息進行分頁並帶條件的查詢:
Controller接口代碼展示
//分頁查詢數據接口
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String,Object>> search(@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize) throws IOException {
return jdService.search(keyword,pageNo,pageSize);
}
ServiceImpl代碼展示
//分頁查詢
public List<Map<String,Object>> search(String keyword,int pageNo,int pageSize) throws IOException {
if(pageNo==0){
pageNo=1;
}
//創建搜索對象
SearchRequest jd_index = new SearchRequest("jd_index");
//構造搜索條件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//配置分頁信息
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
//構造搜索條件
TermQueryBuilder query = QueryBuilders.termQuery("title", keyword);
//封裝搜索條件
sourceBuilder.query(query);
//封裝搜索對象
jd_index.source(sourceBuilder);
//發送請求
SearchResponse response = restHighLevelClient.search(jd_index, RequestOptions.DEFAULT);
List<Map<String,Object>> list=new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
list.add(hit.getSourceAsMap());
}
return list;
}
5、采用Vue+axios進行前后端分離數據展示
使用npm下載vue.js和axios.js的相關文件
首先我們隨便創建一個英文名稱的文件夾,在其中使用cmd命令行npm init
來初始化,使用npm install vue
和npm install axios
來下載依賴。
下載結果:
在項目中引入vue.min.js和axios.min.js文件:
修改頁面信息,動態綁定搜索框的數據和搜索按鈕的單擊事件,實現單擊搜索按鈕就發送請求進行ES庫的查詢,並且使用v-for將查詢結果進行遍歷顯示,以下僅展示vue對象的相關代碼:
<script>
new Vue({
el : "#app",
data : {
keyword : "", //搜索條件
results : [] //返回結果集
},
methods :{
searchKey(){
let key=this.keyword;
console.log(key);
axios.get("/JD/search/"+key+"/1/10").then(result=>{
console.log(result);
this.results=result.data;
})
}
}
})
</script>
查看頁面查詢效果
以上,我們就通過代碼完成了es數據庫的查詢操作,可以用來做頁面的搜索功能。
但是如上圖所示,我們不僅實現了搜索功能,還實現了關鍵字結果的高亮,這是因為接口的不同:
6、分頁待條件且關鍵字高亮查詢
ServiceImpl代碼展示
//高亮分頁查詢
public List<Map<String,Object>> searchHighLight(String keyword,int pageNo,int pageSize) throws IOException {
if(pageNo==0){
pageNo=1;
}
//創建搜索對象
SearchRequest jd_index = new SearchRequest("jd_index");
//構造搜索條件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//配置分頁信息
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
//封裝高亮顯示條件
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title"); //對哪個字段進行高亮
highlightBuilder.preTags("<span style='color:red'>"); //設置高亮前綴
highlightBuilder.postTags("</span>"); //高亮后綴
//構造搜索條件
TermQueryBuilder query = QueryBuilders.termQuery("title", keyword);
//封裝搜索條件
sourceBuilder.query(query);
sourceBuilder.highlighter(highlightBuilder); //封裝高亮搜索條件
//封裝搜索對象
jd_index.source(sourceBuilder);
//發送請求
SearchResponse response = restHighLevelClient.search(jd_index, RequestOptions.DEFAULT);
List<Map<String,Object>> list=new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField title = highlightFields.get("title"); //高亮之后的title
Map<String, Object> sourceAsMap = hit.getSourceAsMap(); //未高亮之前的結果集
//接下來就是將高亮的title與結果集中未高亮的title進行替換
if(title!=null){
Text[] fragments = title.fragments();
String newTitle="";
for (Text text : fragments) {
newTitle+=text;
}
sourceAsMap.put("title",newTitle); //替換掉未高亮的title
}
list.add(sourceAsMap);
}
return list;
}
歡迎關注博主的微信公眾號,以后也會在微信公眾號分享個人精品文章筆記: