ElasticSearch 实现分词全文检索 - 搜素关键字自动补全(Completion Suggest)

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: ElasticSearch 实现分词全文检索 - 搜素关键字自动补全(Completion Suggest)

需求

搜素关键字自动补全(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("article-suggest", completionSuggestionBuilder));
    //article-suggest为返回的字段,所有返回将在article-suggest里面,可写死,sort按照评分排序
    searchRequest.source(searchSourceBuilder);
    //定义查找响应
    SearchResponse suggestResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);
    //定义完成建议对象
    CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("article-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 附源码

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
目录
相关文章
|
2月前
|
自然语言处理 大数据 应用服务中间件
大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务
大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务
77 5
|
2月前
|
自然语言处理 Java 网络架构
elasticsearch学习三:elasticsearch-ik分词器的自定义配置 分词内容
这篇文章是关于如何自定义Elasticsearch的ik分词器配置以满足特定的中文分词需求。
158 0
elasticsearch学习三:elasticsearch-ik分词器的自定义配置 分词内容
|
1月前
|
测试技术 API 开发工具
ElasticSearch的IK分词器
ElasticSearch的IK分词器
59 7
|
1月前
|
JSON Java API
springboot集成ElasticSearch使用completion实现补全功能
springboot集成ElasticSearch使用completion实现补全功能
42 1
|
2月前
|
存储 JSON Java
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
这篇文章是关于Elasticsearch的学习指南,包括了解Elasticsearch、版本对应、安装运行Elasticsearch和Kibana、安装head插件和elasticsearch-ik分词器的步骤。
228 0
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
|
3月前
|
存储 自然语言处理 关系型数据库
ElasticSearch基础3——聚合、补全、集群。黑马旅游检索高亮+自定义分词器+自动补全+前后端消息同步
聚合、补全、RabbitMQ消息同步、集群、脑裂问题、集群分布式存储、黑马旅游实现过滤和搜索补全功能
|
4月前
|
JSON 自然语言处理 数据库
Elasticsearch从入门到项目部署 安装 分词器 索引库操作
这篇文章详细介绍了Elasticsearch的基本概念、倒排索引原理、安装部署、IK分词器的使用,以及如何在Elasticsearch中进行索引库的CRUD操作,旨在帮助读者从入门到项目部署全面掌握Elasticsearch的使用。
|
1月前
|
存储 安全 数据管理
如何在 Rocky Linux 8 上安装和配置 Elasticsearch
本文详细介绍了在 Rocky Linux 8 上安装和配置 Elasticsearch 的步骤,包括添加仓库、安装 Elasticsearch、配置文件修改、设置内存和文件描述符、启动和验证 Elasticsearch,以及常见问题的解决方法。通过这些步骤,你可以快速搭建起这个强大的分布式搜索和分析引擎。
50 5
|
3月前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
4月前
|
数据可视化 Docker 容器
一文教会你如何通过Docker安装elasticsearch和kibana 【详细过程+图解】
这篇文章提供了通过Docker安装Elasticsearch和Kibana的详细过程和图解,包括下载镜像、创建和启动容器、处理可能遇到的启动失败情况(如权限不足和配置文件错误)、测试Elasticsearch和Kibana的连接,以及解决空间不足的问题。文章还特别指出了配置文件中空格的重要性以及环境变量中字母大小写的问题。
一文教会你如何通过Docker安装elasticsearch和kibana 【详细过程+图解】