ElasticSearch 實現分詞全文檢索

2023-03-22 12:04:03

目錄

ElasticSearch 實現分詞全文檢索 - 概述
ElasticSearch 實現分詞全文檢索 - ES、Kibana、IK安裝
ElasticSearch 實現分詞全文檢索 - Restful基本操作
ElasticSearch 實現分詞全文檢索 - Java SpringBoot ES 索引操作
ElasticSearch 實現分詞全文檢索 - Java SpringBoot ES 檔案操作
ElasticSearch 實現分詞全文檢索 - 測試資料準備
ElasticSearch 實現分詞全文檢索 - term、terms查詢
ElasticSearch 實現分詞全文檢索 - match、match_all、multimatch查詢
ElasticSearch 實現分詞全文檢索 - id、ids、prefix、fuzzy、wildcard、range、regexp 查詢
ElasticSearch 實現分詞全文檢索 - Scroll 深分頁
ElasticSearch 實現分詞全文檢索 - delete-by-query
ElasticSearch 實現分詞全文檢索 - 複合查詢
ElasticSearch 實現分詞全文檢索 - filter查詢
ElasticSearch 實現分詞全文檢索 - 高亮查詢
ElasticSearch 實現分詞全文檢索 - 聚合查詢 cardinality
ElasticSearch 實現分詞全文檢索 - 經緯度查詢
ElasticSearch 實現分詞全文檢索 - 搜素關鍵字自動補全(suggest)
ElasticSearch 實現分詞全文檢索 - SpringBoot 完整實現 Demo 附原始碼

需求

搜素關鍵字自動補全(suggest)
輸入「人工」 自動帶出人工開頭的關鍵字

Kibana 介面操作 實現 搜素關鍵字自動補全(suggest)

ES使用Completion Suggest 做關鍵字自動補全時,實際應用中搜尋效能更加高效,建議多開一個子欄位,如下範例,假設要根據title欄位做關鍵字自動補全,不要改原欄位的型別,多開一個子欄位title.suggest,型別設定為completion,然後之後的suggest針對title.suggest欄位做操作

  • Term Suggester:詞條建議器。對給輸入的文字進進行分詞,為每個分詞提供詞項建議, 基於編輯距離,對analyze過的單個term去提供建議,並不會考慮多個term/片語之間的關係。quert -> query
  • Phrase Suggester:短語建議器,在term的基礎上,會考量多個term之間的關係在Term Suggester的基礎上,通過ngram以片語為單位返回建議。noble prize -> nobel prize
  • Completion Suggester:它主要針對的應用場景就是"Auto Completion",FST資料結構,類似Trie樹,不用開啟倒排,快速返回,字首匹配
  • Context Suggester:上下文建議器,在Completion Suggester的基礎上,用於filter和boost

建立索引

## 建立索引並指定結構
PUT /article-index
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties":{
      "id":{
        "type":"keyword"
      },
      "title":{
        "type":"text",
        "analyzer":"ik_max_word",
        "fields": {   # 擴充套件一個欄位,用於關鍵字自動補全查詢
            "suggest" : {
              "type" : "completion",
              "analyzer": "ik_max_word"
            }
          }
      },
      "summary":{
        "type":"text",
        "analyzer":"ik_max_word"
      },
      "createDate":{
        "type":"date",
        "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
      }
    }
  }
}

新增資料

JSON { 括號裡面的內容,不能換行 }

# _bulk 批次新增檔案
POST /article-index/_doc/_bulk
{"index":{"_id":1}}
{"id":1,"title":"人工智慧技術","summary":"ElasticSearch 實現分詞全文檢索 - ES、Kibana、IK安裝","createDate":"2023-02-23"}
{"index":{"_id":2}}
{"id":2,"title":"人工智慧軟體 Chart GTP","summary":"太極生兩儀,兩儀生四象,四象生八卦","createDate":"2023-02-23"} 
{"index":{"_id":3}}
{"id":3,"title":"Restful基本操作","summary":"ElasticSearch 實現分詞全文檢索 - Java SpringBoot ES 索引操作","createDate":"2023-02-23"} 
{"index":{"_id":4}}
{"id":4,"title":"人工呼吸","summary":"ElasticSearch 實現分詞全文檢索 - 經緯度查詢","createDate":"2023-02-23"} 
{"index":{"_id":5}}
{"id":5,"title":"SpringBoot 全文檢索實戰","summary":"ElasticSearch 實現分詞全文檢索 - SpringBoot 全文檢索實戰","createDate":"2023-02-23"}

查詢資料

## 查詢
GET /article-index/_doc/_search
{
  "suggest": {
    "my-suggest" : {
      "prefix" : "人",
      "completion" : {
        "field" : "title.suggest"
      }
    }
  }
}

返回值--自動帶出人開頭的關鍵字

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "my-suggest" : [
      {
        "text" : "人",
        "offset" : 0,
        "length" : 1,
        "options" : [
          {
            "text" : "人工呼吸",
            "_index" : "article-index",
            "_type" : "_doc",
            "_id" : "4",
            "_score" : 1.0,
            "_source" : {
              "id" : 4,
              "title" : "人工呼吸",
              "summary" : "ElasticSearch 實現分詞全文檢索 - 經緯度查詢",
              "createDate" : "2023-02-23"
            }
          },
          {
            "text" : "人工智慧技術",
            "_index" : "article-index",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 1.0,
            "_source" : {
              "id" : 1,
              "title" : "人工智慧技術",
              "summary" : "ElasticSearch 實現分詞全文檢索 - ES、Kibana、IK安裝",
              "createDate" : "2023-02-23"
            }
          },
          {
            "text" : "人工智慧軟體 Chart GTP",
            "_index" : "article-index",
            "_type" : "_doc",
            "_id" : "2",
            "_score" : 1.0,
            "_source" : {
              "id" : 2,
              "title" : "人工智慧軟體 Chart GTP",
              "summary" : "太極生兩儀,兩儀生四象,四象生八卦",
              "createDate" : "2023-02-23"
            }
          }
        ]
      }
    ]
  }
}

JAVA SpringBoot 實現 搜素關鍵字自動補全(suggest)

建立索引

/**
 * 第一步:系統初始化,建立索引
 * 如果索引不存在,建立,輸出
 */
@Test
void createIndexTest() throws Exception {
    boolean indexExists = elasticSearchUtil.indexExists(INDEX_NAME);
    if (!indexExists) {
        try {
            createIndex(INDEX_NAME);
            logger.info("索引【{}】,建立成功", INDEX_NAME);

            //測試效果 可再次查詢驗證。
            indexExists = elasticSearchUtil.indexExists(INDEX_NAME);
            logger.info("索引【{}】, {}", INDEX_NAME, indexExists ? "驗證存在" : "驗證不存在");
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    } else {
        logger.info("索引【{}】已存在,無需建立", INDEX_NAME);
    }
}

/**
 * 建立索引
 *
 * @param indexName
 * @throws Exception
 */
void createIndex(String indexName) throws Exception {
    //準備索引的 settings
    Settings.Builder settings = Settings.builder()
            .put("number_of_shards", INDEX_NUMBER_OF_SHARDS)   //分片數,可以使用常數
            .put("number_of_replicas", esProperties.getReplicasNum()); //是否叢集,需要多少副本,在組態檔中設定

    //準備索引的結構 Mappings
    XContentBuilder mappings = JsonXContent.contentBuilder()
            .startObject()
            .startObject("properties")
            .startObject("id").field("type", "keyword").endObject()
            .startObject("title").field("type", "text").field("analyzer", "ik_max_word")
                .startObject("fields").startObject("suggest").field("type", "completion").field("analyzer", "ik_max_word").endObject().endObject()
            .endObject()  //對該欄位進行分詞
            .startObject("summary").field("type", "text").field("analyzer", "ik_max_word").endObject()  //對該欄位進行分詞
            .startObject("createDate").field("type", "date").field("format", "yyyy-MM-dd HH:mm:ss").endObject()
            .endObject()
            .endObject();

    CreateIndexResponse resp = elasticSearchUtil.createIndex(indexName, settings, mappings);

    //輸出
    logger.info("CreateIndexResponse => {} ", resp.toString());
}

新增資料

/**
 * 第二步:模擬後臺管理員,在新增文章時,將要檢查的欄位內容,同步到ES中
 */
@Test
void addArticleTest() throws Exception {
    Map<Integer, String> titleMap = new HashMap<>();
    titleMap.put(1, "人工智慧技術");
    titleMap.put(2, "人工智慧軟體 Chart GTP");
    titleMap.put(3, "Restful基本操作");
    titleMap.put(4, "Java SpringBoot ES 索引操作");
    titleMap.put(5, "Java SpringBoot ES 檔案操作");
    titleMap.put(6, "人工呼吸");
    titleMap.put(7, "SpringBoot 全文檢索實戰");

    Map<Integer, String> introMap = new HashMap<>();
    introMap.put(1, "ElasticSearch 實現分詞全文檢索 - 概述");
    introMap.put(2, "ElasticSearch 實現分詞全文檢索 - ES、Kibana、IK安裝");
    introMap.put(3, "ElasticSearch 實現分詞全文檢索 - Restful基本操作");
    introMap.put(4, "ElasticSearch 實現分詞全文檢索 - Java SpringBoot ES 索引操作");
    introMap.put(5, "ElasticSearch 實現分詞全文檢索 - Java SpringBoot ES 檔案操作");
    introMap.put(6, "ElasticSearch 實現分詞全文檢索 - 經緯度查詢");
    introMap.put(7, "ElasticSearch 實現分詞全文檢索 - SpringBoot 全文檢索實戰");

    //內容
    Map<Integer, String> contentMap = new HashMap<>();
    contentMap.put(1, "【阿里雲】尊敬的vipsoft:您有2臺雲伺服器ECS設定升級成功。如有CPU、記憶體變更或0Mbps頻寬升級,您需要在ECS控制檯手動重啟雲伺服器後才能生效。");
    contentMap.put(2, "為更好地為您提供服務,溫馨提醒:您本月有1次抽獎機會,贏取大額通用流量,月月抽月月領,點選掌廳連結 原URL:http://wap.js.10086.cn/Mq 快來試試你的運氣吧,如本月已參與請忽略【江蘇移動心級服務,讓愛連線】");
    contentMap.put(3, "國家反詐中心提醒:公檢法機關會當面向涉案人員出示證件或法律文書,絕對不會通過網路給當事人傳送通緝令、拘留證、逮捕證等法律文書,並要求轉賬匯款。\n" +
            "切記:公檢法機關不存在所謂「安全賬戶」,更不會讓你遠端轉賬匯款!");
    contentMap.put(4, "【江蘇省公安廳、江蘇省通訊管理局】溫馨提示:近期利用蘋果手機iMessage訊息冒充熟人、冒充領導換號、新增新微訊號等詐騙形式多發。如有收到類似簡訊,請您謹慎判斷,蘋果手機使用者如無需要可關閉iMessage功能,以免上當受騙。");
    contentMap.put(5, "多一點快樂,少一點懊惱,不管鈔票有多少,只有天天開心就好,累了就睡覺,生活的甜苦,自己來調味。收到資訊就要開心的笑");
    contentMap.put(6, "黃金週好運每天交,我把祝福來送到:願您生活步步高,彩票期期中,股票每天漲,生意年年旺,祝您新年新景象!");
    contentMap.put(7, "【阿里雲】當你手機響,那是我的問候;當你收到簡訊,那有我的心聲;當你翻閱簡訊,那有我的牽掛;當你籌備關機時,記得我今天說過週末快樂!");
    contentMap.put(8, "我剛去了一趟銀行,取了無數的幸福黃金好運珠寶平安翡翠成功股票健康基金。噓!別作聲,統統的送給你,因為我想提「錢」祝你國慶節快樂!");
    contentMap.put(9, "一個人的精彩,一個人的打拼,一個人的承載,一個人的舞蹈。光棍節送你祝福,不因你是光棍,只因你生活色彩。祝你:快樂打拼,生活出彩!");
    contentMap.put(10, "爆竹響激情燃放,雪花舞祥風歡暢,煙火騰期待閃亮,感動湧心中激盪,心情美春節衝浪,願景好心中珍藏,祝與福簡訊奉上:祝您身體健康,兔年吉祥!");

    //模似7次 新增文章
    for (int i = 1; i <= 7; i++) {
        ArticleInfo article = new ArticleInfo();
        article.setId(String.valueOf(i));
        article.setTitle(titleMap.get(i));
        article.setAuthor("VipSoft");
        article.setSummary(introMap.get(i));
        article.setContent(contentMap.get(i));
        article.setCreateTime(new Date());
        //將article 儲存到 MySQL --- 省略
        boolean flag = true; //儲存資料到 MySQL 資料庫成功
        if (flag) {
            //將需要查詢的資料,賦給DTO,更新到 ES中
            ArticleDTO articleDTO = new ArticleDTO();
            BeanUtils.copyProperties(article, articleDTO);
            String json = JSON.toJSONStringWithDateFormat(articleDTO, "yyyy-MM-dd HH:mm:ss"); //FastJson 將日期格式化
            IndexResponse resp = elasticSearchUtil.createDoc(INDEX_NAME, articleDTO.getId(), json);
            logger.info(" {}", resp.getResult().toString());
        }
    }
}

查詢資料

/**
 * 第三步:模擬使用者搜尋,輸入關鍵詞「人」,帶出和人有關的關鍵詞
 */
@Test
void earchTest() throws Exception {
    List<String>  resp = elasticSearchUtil.suggest(INDEX_NAME, "title.suggest", "人", 2);
    //4. 獲取到 _source 中的資料,並展示
    for (String hit : resp) {
        System.out.println(hit);
    }
}

/**
 * 自動補全 根據使用者的輸入聯想到可能的詞或者短語
 *
 * @param indexName 索引名稱
 * @param field     搜尋條件欄位
 * @param keywords  搜尋鍵碼
 * @param size      匹配數量
 * @return
 * @throws Exception
 */
public List<String> suggest(String indexName, String field, String keywords, int size) throws Exception {
    //定義返回
    List<String> suggestList = new ArrayList<>();
    //構建查詢請求
    SearchRequest searchRequest = new SearchRequest(indexName);
    //通過查詢構建器定義評分排序
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
    //構造搜尋建議語句,搜尋條件欄位
    CompletionSuggestionBuilder completionSuggestionBuilder = new CompletionSuggestionBuilder(field);
    //搜尋鍵碼
    completionSuggestionBuilder.prefix(keywords);
    //去除重複
    completionSuggestionBuilder.skipDuplicates(true);
    //匹配數量
    completionSuggestionBuilder.size(size);
    searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion("my-suggest", completionSuggestionBuilder));
    //czbk-suggest為返回的欄位,所有返回將在czbk-suggest裡面,可寫死,sort按照評分排序
    searchRequest.source(searchSourceBuilder);
    //定義查詢響應
    SearchResponse suggestResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);
    //定義完成建議物件
    CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("my-suggest");
    List<CompletionSuggestion.Entry.Option> optionsList = completionSuggestion.getEntries().get(0).getOptions();
    //從optionsList取出結果
    if (!CollectionUtils.isEmpty(optionsList)) {
        optionsList.forEach(item -> suggestList.add(item.getText().toString()));
    }
    return suggestList;
}

ElasticSearchUtil 程式碼見 - SpringBoot 完整實現 Demo 附原始碼