ElasticSearch - 基于 拼音分词器 和 IK分词器 模拟实现“百度”搜索框自动补全功能

news2024/11/16 16:29:57

目录

一、自动补全

1.1、效果说明

1.2、安装拼音分词器

1.3、自定义分词器

1.3.1、为什么要自定义分词器

1.3.2、分词器的构成

1.3.3、自定义分词器

1.3.4、面临的问题和解决办法

问题

解决方案

1.4、completion suggester 查询

1.4.1、基本概念和语法

1.4.2、示例

1.4.3、示例(黑马旅游)

a)修改 hotel 索引库结构,设置自定义拼音分词器.

b)给 HotelDoc 类添加 suggestion 字段

c)将数据重新导入到 hotel 索引库中

d)基于 JavaRestClient 编写 DSL

1.5、黑马旅游案例

1.5.1、需求

1.5.2、前端对接

1.5.3、实现 controller

1.5.4、创建接口并实现.

1.5.5、效果展示


一、自动补全


1.1、效果说明

当用户在搜索框中输入字符时,我们应该提示出与该字符有关的搜索项.

例如百度中,输入关键词 "byby",他的效果如下:

1.2、安装拼音分词器

 要实现根据字母补全,就需要对文档按照拼英分词.  在GitHub 上有一个 es 的拼英分词插件.

地址:GitHub - medcl/elasticsearch-analysis-pinyin: This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.

这里的安装方式和 IK 分词器一样,分四步:

1. 安装解压.

2. 上传到云服务器中,es 的 plugin 目录.

3. 重启 es.

4. 测试.

这里可以看到,拼音分词器不光对每个字用拼音进行分词,还对每个字的首字母进行分词.

1.3、自定义分词器

1.3.1、为什么要自定义分词器

根据上述测试,可以看出.

1. 拼音分词器是将一句话中的每一个字都分成了拼音,这没什么实际的用处.

2. 这里并没有分出汉字,只有拼英.  实际的使用中,用户更多的是使用汉字去搜,有拼音只是锦上添花,但是也不能只用拼音分词器,把汉字丢了.

因此这里我们需要对拼音分词器进行一些自定义的配置.

1.3.2、分词器的构成

想要自定义分词器,首先要先了解 es 中分词器的构成.

分词器主要由以下三个部分组成:

  1. character filters:在 tokenizer 之前,对文本的特殊字符进行处理. 比如他会把文本中出现的一些特殊字符转化成汉字,例如 :) => 开心.
  2. tokenizer:将文本按照一定的规则切割成词条(term). 例如 “我很开心” 会切割成 "我"、"很"、"开心".
  3. tokenizer filter:对 tokenizer 进一步处理.  例如将汉字转化成拼音.

1.3.3、自定义分词器

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { //自定义分词器
        "my_analyzer": { //自定义分词器名称
          "tokenizer": "ik_max_word",
          "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
        }
      }
    }
  }
}

  • “type”: “pinyin”:指定使用拼音过滤器进行拼音转换。
  • “keep_full_pinyin”: false:表示不保留完整的拼音。如果设置为true,则会将完整的拼音保留下来。
  • “keep_joined_full_pinyin”: true:表示保留连接的完整拼音。当设置为true时,如果某个词的拼音有多个音节,那么它们将被连接在一起作为一个完整的拼音。
  • “keep_original”: true:表示保留原始词汇。当设置为true时,原始的中文词汇也会保留在分词结果中。
  • “limit_first_letter_length”: 16:限制拼音首字母的长度。默认为16,即只保留拼音首字母的前16个字符。
  • “remove_duplicated_term”: true:表示移除重复的拼音词汇。如果设置为true,则会移除拼音结果中的重复词汇。
  • “none_chinese_pinyin_tokenize”: false:表示是否对非中文文本进行拼音分词处理。当设置为false时,非中文文本将保留原样,不进行拼音分词处理

例如,创建一个 test 索引库,来测试自定义分词器.

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "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": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

使用此索引库的分词器进行测试

从上图中可以看出:

1.不光有拼音,还有中文分词.

2.还有中文分词后的英文全拼,以及分词首字母.

1.3.4、面临的问题和解决办法

问题

上面实现的拼音分词器还不能应用到实际的生产环境中~

可以想象这样一个场景:

如果词库中有这两个词:“狮子” 和 “虱子”,那么也就意味着,创建倒排索引时,通过上述自定义的 拼音分词器 ,就会把这两个词归为一个文档,因为他们在分词的时候,会分出共同的拼音 "shizi" 和 "sz",这就导致他两的文档编号对应同一个词条,导致将来用户在搜索框里输入 “狮子” ,点击搜索之后,会同时搜索出 "狮子" 和 “虱子” ,这并不是我们想看到的.

解决方案

因此字段在创建倒排索引时因该使用 my_analyzer 分词器,但是字段在搜索时应该使用 ik_smart 分词器. 

也就是说,用户输入中文的时候,就按中文去搜,用户输入拼音的时候,才按拼音去搜,即使出现上述情况,同时搜出这两个词,那你是按拼音搜,两个都是符合的,不存在歧义.

如下:

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "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": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer" //创建倒排索引使用 my_analyzer 分词器.
        "search_analyzer": "ik_smart"  //搜索时使用 ik_smart 分词器.
      }
    }
  }
}

1.4、completion suggester 查询

1.4.1、基本概念和语法

es 中提供了 completion suggester 查询来实现自动补全功能.  这个查询会匹配用户输入内容开头的词条并返回.

为了提高补全查询的效率,对于文档中的字段类型有一些约束,如下:

  1. 参与补全查询的字段必须是 completion 类型.
  2. 参与 自动补全字段 的内容一般是多个词条形成的数组.

POST /test2/_search
{
  "suggest": {
    "title_suggest": { //自定义补全名
      "text": "s",  //用户在搜索框中输入的关键字
      "completion": { // completion 是自动补全中的一种类型(最常用的)
        "field": "补全时需要查询的字段名", //这里的字段名指向的是一个数组(字段必须是 completion 类型),就是要根据数组中的字段进行查询,然后自动补全
        "skip_duplicates": true,  //如果查询时有重复的词条,是否自动跳过(true 为跳过)
        "size": 10 // 获取前 10 条结果.
      }
    }
  }
}

1.4.2、示例

这里我用一个示例来演示 completion suggester 的用法.

首先创建索引库(参与自动补全的字段类型必须是 completion).

PUT /test2
{
  "mappings": {
    "properties": {
      "title": {
        "type": "completion"
      }
    }
  }
}

插入示例数据(字段内容一般是用来补全的多个词条形成的数组.)

POST test2/_doc
{
 "title": ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
  "title": ["Nintendo", "switch"]
}

这里我们设置关键字为 "s",来自动补全查询,如下:

POST /test2/_search
{
  "suggest": {
    "title_suggest": {
      "text": "s", 
      "completion": {
        "field": "title", 
        "skip_duplicates": true, 
        "size": 10
      }
    }
  }
}

1.4.3、示例(黑马旅游)

这里我们基于之前实现的黑马旅游案例来做栗子,实现步骤如下:

a)修改 hotel 索引库结构,设置自定义拼音分词器.

1.设置自定义分词器.

2. 修改索引库的 name、all 字段(建立倒排索引使用 拼音分词器,搜索时使用 ik 分词器).

3. 给索引库添加一个新字段 suggestion,类型为 completion 类型,使用自定义分词器.

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"
      }
    }
  }
}

b)给 HotelDoc 类添加 suggestion 字段

suggestion 字段(包含多个字段的数组,这里可以使用 List 表示),内容包含 brand、business.

Ps:name、all 是可以分词的,自动补全的 brand、business 是不可分词的,要使用不同的分词器组合.

@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();
        this.suggestion = new ArrayList<>();
        suggestion.add(brand);
        suggestion.add(business);
    }
}

c)将数据重新导入到 hotel 索引库中

将 hotel 索引库删了,然后重建(a 中的 DSL).  通过单元测试将所有信息从数据库同步到 es 上.

    @Test
    public void testBulkDocument() throws IOException {
        //1.获取酒店所有数据
        List<Hotel> hotelList = hotelService.list();
        //2.构造请求
        BulkRequest request = new BulkRequest();
        //3.准备参数
        for(Hotel hotel : hotelList) {
            //转化为文档(主要是地理位置)
            HotelDoc hotelDoc = new HotelDoc(hotel);
            String json = objectMapper.writeValueAsString(hotelDoc);
            request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));
        }
        //4.发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

d)基于 JavaRestClient 编写 DSL

例如自动补全关键为 "h" 的内容.

    @Test
    public void testSuggestion() throws IOException {
        //1.创建请求
        SearchRequest request = new SearchRequest("hotel");
        //2.准备参数
        request.source().suggest(new SuggestBuilder().addSuggestion(
            "testSuggestion",
                SuggestBuilders
                        .completionSuggestion("suggestion")
                        .prefix("h")
                        .skipDuplicates(true)
                        .size(10)
        ));
        //3.发送请求,接收响应
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
        //4.解析响应
        handlerResponse(search);
    }

这里可以对应着 DSL 语句来写.

对查询结果的处理如下:

        //4.处理自动补全结果
        Suggest suggest = response.getSuggest();
        if(suggest != null) {
            CompletionSuggestion suggestion = suggest.getSuggestion("testSuggestion");
            for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
                String text = option.getText().toString();
                System.out.println(text);
            }
        }

这里可以对应着 DSL 语句来写.

运行结果如下:

1.5、黑马旅游案例

1.5.1、需求

首先搜索框的自动补全功能.

最终实现效果就类似于 百度的搜索框,比如当我们输入 "byby",他就会立马自动补全出有关 byby 关键字的信息,如下图:

1.5.2、前端对接

在搜索框中输入,会触发以下请求. 这里前端就传入一个参数 key.

这里约定,返回的是一个 List,内容就是自动补全的所有信息.

1.5.3、实现 controller

这里使用 @RequestParam 接收前端传入的参数,然后调用 IhotelService 接口处理即可.

    @RequestMapping("/suggestion")
    public List<String> suggestion(@RequestParam("key") String prefix) {
        return hotelService.suggestion(prefix);
    }

1.5.4、创建接口并实现.

在 IhotelService 接口中创建 suggestion 方法.

public interface IHotelService extends IService<Hotel> {

    PageResult search(RequestParams params);

    Map<String, List<String>> filters(RequestParams params);

    List<String> suggestion(String prefix);
}

接着在 IhotelService 的实现类 HotelService 中实现该方法.

具体的实现,就和前面写的测试案例基本一致了~  要注意的点就是补全的关键字不是写死的,而是前端传入的 prefix.

    @Override
    public List<String> suggestion(String prefix) {
        try {
            //1.创建请求
            SearchRequest request = new SearchRequest("hotel");
            //2.准备参数
            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "mySuggestion",
                    SuggestBuilders
                            .completionSuggestion("suggestion")
                            .prefix(prefix)
                            .skipDuplicates(true)
                            .size(10)
            ));
            //3.发送请求,接收响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4.解析响应(处理自动补全结果)
            Suggest suggest = response.getSuggest();
            List<String> suggestionList = new ArrayList<>();
            if(suggest != null) {
                CompletionSuggestion suggestion = suggest.getSuggestion("mySuggestion");
                for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
                    String text = option.getText().toString();
                    suggestionList.add(text);
                }
            }
            return suggestionList;
        } catch (IOException e) {
            System.out.println("[HotelService] 自动补全失败!prefix=" + prefix);
            e.printStackTrace();
            return null;
        }
    }
}

1.5.5、效果展示

输入关键词,即可出现自动补全.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1047708.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【c语言中数组和指针的联系】

C语言中的数组和指针之间存在紧密的联系&#xff0c;因为数组名本质上就是指向数组首元素的指针。这种联系使得在处理数组时可以像处理指针一样操作&#xff0c;同时也使得数组和指针可以互相转换。 下面详细说明数组和指针之间的联系&#xff0c;并提供一些示例来演示这种联系…

多线程入门知识点及代码详解及相关面试八股文

多线程详解及相关面试八股文 多线程 线程概述 * 多线程&#xff1a;进程&#xff1a;当前正在运行的程序&#xff0c;一个应用程序在内存中的执行区域线程&#xff1a;进程中的一个执行控制单元&#xff0c;执行路径* 一个进程可以有一个线程&#xff0c;也可以有多个线程* …

B+tree 与 B-tree区别

原理:分批次的将磁盘块加载进内存中进行检索,若查到数据,则直接返回,若查不到,则释放内存,并重新加载同等数据量的索引进内存,重新遍历 结构: 数据 向下的指针 指向数据的指针 特点: 1&#xff0c;节点排序 2 .一个节点了可以存多个元索&#xff0c;多个元索也排序了 结构: 数…

vscode 打开后 默认terminal power shell 报错 名为“.C”的驱动器不存在。

这是 默认terminal power shell 打开报的错 Test-Path : 找不到驱动器。名为“.C”的驱动器不存在。 所在位置 C:\Users\HUAWEI\Documents\WindowsPowerShell\profile.ps1:4 字符: 5 If (Test-Path "C:\Users\HUAWEI\AppData\Local\Temp\_MEI319962\Scripts\ ... …

Java列表查询Long(id)到前端转换出错

Java列表查询Long到前端转换出错 问题描述排查思路思路一&#xff1a;SQL问题思路二&#xff1a;Long类型转换出错 解决方法 问题描述 做了一个列表查询功能&#xff0c;本来不应该有啥大问题的&#xff0c;但是往往事与愿违。 诶&#xff0c;你越觉得不可能出问题&#xff0c…

深入探讨 Presto 中的缓存

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 Presto是一种流行的开源分布式SQL引擎&#xff0c;使组织能够在多个数据源上大规模运行交互式分析查询。缓存是一种典型的提高 Presto 查询性能的优化技术。它为 Prest…

网络安全——黑客——自学

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01;&#xff01;&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队…

信创办公–基于WPS的PPT最佳实践系列 (将文字转换为SmartArt)

信创办公–基于WPS的PPT最佳实践系列 &#xff08;将文字转换为SmartArt&#xff09; 目录 应用背景操作步骤第一种&#xff1a;将已有文字列表转换生成SmartArt形状。第二种&#xff1a;直接插入SmartArt形状。 应用背景 如何清晰有效的呈现文字信息&#xff1f;通常使用视觉效…

BM4 合并两个排序的链表

思路&#xff1a;先选择最小的作为Head&#xff0c;每次从两个队列中取最小的挂到Head后面&#xff0c;如果一个合并空&#xff0c;后面直接挂。此外判断几个为空链表的情况 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullp…

排序算法一 直接插入排序,希尔排序,直接选择排序,堆排序和冒泡排序

什么是排序 排序&#xff1a;排序就是使一串记录,按照其中的某个或者某些关键字的大小,递增或递减的排列起来的操作 稳定性: 假定在排序序列中,存在多个具有相同的关键字记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,存在r[i] r[j],且r[i]在 r[j] 之前&#xf…

Oracle-通过BBED强制打开数据库

前言: 在通过备份进行数据恢复时&#xff0c;经常会遇到由于备份的不完整&#xff0c;特别是归档日志的缺失&#xff0c;导致虽然数据文件已经顺利恢复&#xff0c;但是数据文件的恢复时间点没有完全一致或者数据文件的fuzzy的状态不一致&#xff0c;从而数据库无法正常的打开&…

三维模型3DTile格式轻量化压缩处理重难点分析

三维模型3DTile格式轻量化压缩处理重难点分析 在对三维模型3DTile格式进行轻量化压缩处理的过程中&#xff0c;存在一些重要而又困难的问题需要解决。以下是几个主要的重难点&#xff1a; 1、压缩率和模型质量之间的平衡&#xff1a;压缩技术的目标是尽可能地减少数据大小&…

用CRM系统提高客户转化率

影响客户转化的因素有很多&#xff0c;例如潜在客户是否真实有效、销售跟进策略、销售跟进流程和及时性等。不少企业正在使用CRM销售管理系统&#xff0c;下面说说CRM系统如何提高提高客户转化率&#xff1f; 1、甄别高质量获客渠道 CRM系统可以对获取的客户线索进行分析&…

premire 两个视频叠加显示

1 背景视频放到 v1 视频轨道 前视 视频 放到 v2 视频轨道 2 调整 前视 视频颜色参数 2.1 曲线 亮度 暗度 调整 黑白鲜明对比 2.2 基本校正 对比度 白色 黑色 饱和度 曝光 3 效果控件 -> 混合模式 -> 滤色 4 视频人物大小调整 位置调整

基于微信小程序的宠物交易商城系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌…

电设备工厂的BOM管理系统

一、什么是BOM管理系统&#xff1f; BOM管理系统是一种软件工具&#xff0c;用于管理和跟踪产品的物料清单。它包括产品的组成结构、零部件的规格、数量和关联信息。BOM管理系统提供了一个集中存储和更新BOM数据的平台&#xff0c;并确保所有相关部门和团队都能够访问最新的BO…

2023工博会 | 上海添力网络营销公司 | 助力工业品线上推广

2023年9月23日&#xff0c;为期五天的工博会正式落下帷幕。本届工博会不仅有数量&#xff0c;更加有质量&#xff0c;国内外企业纷纷拿出看家本领&#xff0c;围绕着“绿色低碳”、“数字化转型”、“数字经济”、“科技创新”、“智能制造”等主题进行推陈出新。 本次工博会也…

未来预判:跨境电商全球化的下一个步伐

在当今全球化的时代&#xff0c;跨境电商已经成为了国际贸易和全球商业的主要驱动力之一。跨境电商不仅仅改变了消费者的购物方式&#xff0c;也对传统零售业产生了深远的影响。 然而&#xff0c;随着技术和市场的不断发展&#xff0c;跨境电商也在不断演化&#xff0c;其全球…

2023 现阶段H5的机型适配

个人愚见 现在的主流体验&#xff0c;是大屏手机展示更多的内容&#xff0c;并不着重于放大展示&#xff0c; 所以&#xff0c;外层布局使用vw,百分比&#xff0c;flex&#xff0c;内层直接px就行 参考&#xff1a; https://juejin.cn/post/7128051145431318535

LoadLibraryEx调用dll时有未经处理的异常,发生访问冲突

0x000000000006A220 处的第一机会异常(在 testHFHZDll.exe 中): 0xC0000005: 执行位置 0x000000000006A220 时发生访问冲突。 0x000000000006A220 处有未经处理的异常(在 testHFHZDll.exe 中): 0xC0000005: 执行位置 0x000000000006A220 时发生访问冲突。 最近做一个测试&#…