當用戶在搜索框輸入字符時,我們應該提示出與該字符有關的搜索項,如圖:
這種根據用戶輸入的字母,提示完整詞條的功能,就是自動補全了。
因為需要根據拼音字母來推斷,因此要用到拼音分詞功能。
2.1.拼音分詞器
要實現根據字母做補全,就必須對文檔按照拼音分詞。在GitHub上恰好有elasticsearch的拼音分詞插件。地址:https://github.com/medcl/elasticsearch-analysis-pinyin
資料中也提供了拼音分詞器的安裝包:
安裝方式與IK分詞器一樣,分三步:
①解壓
②上傳到虛擬機中,elasticsearch的plugin目錄
③重啟elasticsearch
④測試
詳細安裝步驟可以參考IK分詞器的安裝過程。
測試用法如下:
POST /_analyze
{
"text": "如家酒店還不錯",
"analyzer": "pinyin"
}
結果:
2.2.自定義分詞器
默認的拼音分詞器會將每個漢字單獨分為拼音,而我們希望的是每個詞條形成一組拼音,需要對拼音分詞器做個性化定制,形成自定義分詞器。
elasticsearch中分詞器(analyzer)的組成包含三部分:
- character filters:在tokenizer之前對文本進行處理。例如刪除字符、替換字符
- tokenizer:將文本按照一定的規則切割成詞條(term)。例如keyword,就是不分詞;還有ik_smart
- tokenizer filter:將tokenizer輸出的詞條做進一步處理。例如大小寫轉換、同義詞處理、拼音處理等
文檔分詞時會依次由這三部分來處理文檔:
聲明自定義分詞器的語法如下:
PUT /test // 創建索引庫
{
"settings": {
"analysis": { // 針對當前的索引庫
"analyzer": { // 自定義分詞器
"my_analyzer": { // 分詞器名稱
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": { // 自定義tokenizer filter
"py": { // 過濾器名稱
"type": "pinyin", // 過濾器類型,這里是pinyin
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer", //
"search_analyzer": "ik_smart" // 搜索時使用,避免同音字
}
}
}
}
測試:
總結:
如何使用拼音分詞器?
- ①下載pinyin分詞器
- ②解壓並放到elasticsearch的plugin目錄
- ③重啟即可
如何自定義分詞器?
- ①創建索引庫時,在settings中配置,可以包含三部分
- ②character filter
- ③tokenizer
- ④filter
拼音分詞器注意事項?
- 為了避免搜索到同音字,搜索時不要使用拼音分詞器
2.3.自動補全查詢
elasticsearch提供了Completion Suggester查詢來實現自動補全功能。這個查詢會匹配以用戶輸入內容開頭的詞條並返回。為了提高補全查詢的效率,對於文檔中字段的類型有一些約束:
- 參與補全查詢的字段必須是completion類型。
- 字段的內容一般是用來補全的多個詞條形成的數組。
比如,一個這樣的索引庫:
// 創建索引庫
PUT test
{
"mappings": {
"properties": {
"title":{
"type": "completion"
}
}
}
}
然后插入下面的數據:
// 示例數據
POST test/_doc
{
"title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
"title": ["SK-II", "PITERA"]
}
POST test/_doc
{
"title": ["Nintendo", "switch"]
}
查詢的DSL語句如下:
// 自動補全查詢
GET /test/_search
{
"suggest": {
"title_suggest": { // 查詢名稱
"text": "s", // 關鍵字
"completion": {
"field": "title", // 補全查詢的字段
"skip_duplicates": true, // 跳過重復的
"size": 10 // 獲取前10條結果
}
}
}
}
2.4.實現酒店搜索框自動補全
現在,我們的hotel索引庫還沒有設置拼音分詞器,需要修改索引庫中的配置。但是我們知道索引庫是無法修改的,只能刪除然后重新創建。
另外,我們需要添加一個字段,用來做自動補全,將brand、suggestion、city等都放進去,作為自動補全的提示。
因此,總結一下,我們需要做的事情包括:
- 修改hotel索引庫結構,設置自定義拼音分詞器
- 修改索引庫的name、all字段,使用自定義分詞器
- 索引庫添加一個新字段suggestion,類型為completion類型,使用自定義的分詞器
- 給HotelDoc類添加suggestion字段,內容包含brand、business
- 重新導入數據到hotel庫
2.4.1.修改酒店映射結構
代碼如下:
// 酒店數據索引庫
PUT /hotel
{
"settings": {
"analysis": {
"analyzer": {
"text_anlyzer": { // 拼音
"tokenizer": "ik_max_word",
"filter": "py"
},
"completion_analyzer": { // 自動補全
"tokenizer": "keyword",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "text_anlyzer",
"search_analyzer": "ik_smart"
},
"suggestion":{ // 自動不全
"type": "completion",
"analyzer": "completion_analyzer"
}
}
}
}
2.4.2.修改HotelDoc實體
HotelDoc中要添加一個字段,用來做自動補全,內容可以是酒店品牌、城市、商圈等信息。按照自動補全字段的要求,最好是這些字段的數組。
因此我們在HotelDoc中添加一個suggestion字段,類型為List<String>
,然后將brand、city、business等信息放到里面。
代碼如下:
package cn.itcast.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
private Object distance;
private Boolean isAD;
private List<String> suggestion;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
// 組裝suggestion
if(this.business.contains("/")){
// business有多個值,需要切割
String[] arr = this.business.split("/");
// 添加元素
this.suggestion = new ArrayList<>();
this.suggestion.add(this.brand);
Collections.addAll(this.suggestion, arr);
}else {
this.suggestion = Arrays.asList(this.brand, this.business);
}
}
}
2.4.3.重新導入
重新執行之前編寫的導入數據功能,可以看到新的酒店數據中包含了suggestion:
2.4.4.自動補全查詢的JavaAPI
之前我們學習了自動補全查詢的DSL,而沒有學習對應的JavaAPI,這里給出一個示例:
而自動補全的結果也比較特殊,解析的代碼如下:
2.4.5.實現搜索框自動補全
查看前端頁面,可以發現當我們在輸入框鍵入時,前端會發起ajax請求:
返回值是補全詞條的集合,類型為List<String>
1)在cn.itcast.hotel.web
包下的HotelController
中添加新接口,接收新的請求:
@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {
return hotelService.getSuggestions(prefix);
}
2)在cn.itcast.hotel.service
包下的IhotelService
中添加方法:
List<String> getSuggestions(String prefix);
3)在cn.itcast.hotel.service.impl.HotelService
中實現該方法:
@Override
public List<String> getSuggestions(String prefix) {
try {
// 1.准備Request
SearchRequest request = new SearchRequest("hotel");
// 2.准備DSL
request.source().suggest(new SuggestBuilder().addSuggestion(
"suggestions",
SuggestBuilders.completionSuggestion("suggestion")
.prefix(prefix)
.skipDuplicates(true)
.size(10)
));
// 3.發起請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結果
Suggest suggest = response.getSuggest();
// 4.1.根據補全查詢名稱,獲取補全結果
CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
// 4.2.獲取options
List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
// 4.3.遍歷
List<String> list = new ArrayList<>(options.size());
for (CompletionSuggestion.Entry.Option option : options) {
String text = option.getText().toString();
list.add(text);
}
return list;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
資料地址:
鏈接:https://pan.baidu.com/s/1cQVyPGjyk-_t3b04wkcMfg
提取碼:1211