Elasticsearch 自定义分成器 拼音搜索 搜索自动补全 Java对接
介绍
通常用于将文档中的文本数据拆分成易于索引的词项(tokens)。有时,默认的分词器无法满足特定应用需求,这时就可以创建 自定义分词器 来实现定制化的文本分析。
自定义分词器组成
-
Char Filters(字符过滤器):
字符过滤器在文本被传给分词器之前,先对字符进行预处理。常见的处理包括去除特殊字符、替换字符、转换字符等。
例如:html_strip 字符过滤器可以去除 HTML 标签,mapping 字符过滤器可以将某些字符映射为其他字符。 -
Tokenizer(分词器):
将输入的文本拆分成一个个词项(tokens)。
常见的分词器有 standard、keyword、pattern、whitespace 等,也可以自定义一个分词器来根据特定规则进行分割。 -
Token Filters(词项过滤器):
词项过滤器用于对分词后的词项进行进一步的处理,如小写化、去除停用词、词干提取等。
例如,lowercase 过滤器将所有词项转为小写,stop 过滤器会去除常见的无意义词(如 “a”, “the” 等)。
注意事项
- 倒排索引:在构建倒排索引时,拼音分词器可以将每个词语转化为拼音,并为每个拼音索引相关的文档。这种方法依赖于拼音本身,因此在创建索引时,拼音是一个便于检索和存储的统一标准。然而,这种方法并不考虑具体的字或词的实际含义。
- 搜索时的查询:在搜索过程中,用户通常会直接输入汉字(而不是拼音),而且搜索时往往依赖的是汉字的实际语义。如果使用拼音进行搜索,可能会出现同音字或多音字的歧义,导致用户查询无法准确匹配目标内容。例如,拼音 “mā” 可以代表“妈”、“马”或“麻”,但用户搜索的汉字可能是“马”而不是“妈”,此时拼音搜索就会产生误差。
这时候搜索和创建应该使用不同的分词器。所以就需要使用自定义分词器。自定义分词器在创建索引库的时候就应该创建。
创建自定义分词器的索引库
PUT http://172.23.4.130:9200/goods
{
"settings": {
"analysis": {
"analyzer": {
"mx_analyzer": { // 定义自定义分析器名称为 mx_analyzer
"tokenizer": "ik_max_word", // 使用 "ik_max_word" 分词器,进行中文最大化分词
"filter": "py" // 使用拼音过滤器 "py" 进行拼音转换
}
},
"filter": {
"py": { // 配置拼音过滤器
"type": "pinyin", // 设置为拼音类型的过滤器
"keep_full_pinyin": false, // 不保留完整拼音(仅保留简拼)
"keep_joined_full_pinyin": true, // 保留拼音连在一起(例如“北京”变为“bj”)
"keep_original": true, // 保留原始中文词汇
"limit_first_letter_length": 16, // 限制拼音首字母的长度为16
"remove_duplicated_term": true, // 删除重复的拼音词项
"none_chinese_pinyin_tokenize": false // 不进行非中文拼音的分词处理
}
}
}
},
"mappings": {
"properties": {
"title": { // title字段配置
"type": "text", // 使用 "text" 类型,适合进行分词的文本字段
"analyzer": "mx_analyzer", // 使用自定义的 mx_analyzer 分析器进行分词
"search_analyzer": "ik_smart" // 搜索时使用 "ik_smart" 分析器进行分析(简化分词)
},
"transport": { // transport字段配置
"type": "double" // 使用 "double" 类型,用于数值数据(浮动小数)
}
}
}
}
拼音分词库文档:https://github.com/medcl/elasticsearch-analysis-pinyin
添加测试数据
POST http://172.23.4.130:9200/goods/_doc/n
{
"title": "广东梅州盐焗鸡中翅客家特产盐局鸡翅中网红零食小吃熟食"
}
测试搜索
GET http://172.23.4.130:9200/goods/_search
不管使用中文还是英文 全拼还是简写 都可以正常搜索出该商品
{
"query":{
"match":{
"title":"yjj"
}
}
}
搜索的自动补全
Elasticsearch 提供了Completion suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束。
- 参与补全查询的字段必须是completion类型
- 字段的内容一般是用来补全的多个词条形成的数组,也就是提示词语
{
"settings": {
"analysis": {
"analyzer":{
"mx_analyzer":{
"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":{
"title": {
"type": "text",
"analyzer": "mx_analyzer",
"search_analyzer":"ik_smart"
},
"suggestion":{ //搜索的自动补全
"type":"completion",
"analyzer":"completion_analyzer"
}
}
}
}
创建搜索关键字
PUT http://172.23.4.130:9200/goods/_doc/1
{
"title": "客家散养土猪原味腊肠香肠广东梅州特产咸香",
"suggestion":["土猪","腊肠","香肠","散养"]
}
自动补全查询
GET http://172.23.4.130:9200/goods/_search
{
"suggest": {
"title_suggest": { // title_suggest 自定义名称
"text": "l", // 搜索的文本
"completion": { // 使用"completion" 进行自动补全
"field": "suggestion", // 指定用于自动补全的字段名
"skip_duplicates": true, // 跳过重复的建议
"size": 15 // 返回的最大数量
}
}
}
}
对应Java代码
SearchRequest request =new SearchRequest(GOODS_INDEX);
request.source().suggest(new SuggestBuilder().addSuggestion("title_suggest",
SuggestBuilders.completionSuggestion("suggestion").prefix(text)
.skipDuplicates(true).size(15)));
依赖
版本7.12.1
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
配置文件
application.yaml
es:
ip: 172.23.4.130
port: 9200
user: elastic
password: qwertyuiop
配置类
@Component
@ConfigurationProperties(prefix = "es")
@Data
public class ElasticsearchProperties {
private String ip;
private int port;
private String user;
private String password;
}
配置连接
@Configuration
@RequiredArgsConstructor
public class ElasticsearchConfig {
private final ElasticsearchProperties elasticsearchProperties;
@Bean
public RestHighLevelClient restHighLevelClient() {
// 配置基本认证
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope(elasticsearchProperties.getIp(), elasticsearchProperties.getPort()),
new UsernamePasswordCredentials(elasticsearchProperties.getUser(), elasticsearchProperties.getPassword())
);
RestClientBuilder builder = RestClient.builder(new HttpHost(elasticsearchProperties.getIp(), elasticsearchProperties.getPort(), "http"));
builder.setHttpClientConfigCallback(httpClientBuilder -> {
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
});
// 创建并返回 RestHighLevelClient 实例
return new RestHighLevelClient(builder);
}
}
JSON格式化
将JSON转成实体类
public class EsUtils {
public static <T>List<T> toList(SearchHit[] hits, Class<T> clazz) {
List<T> retList = new ArrayList<>();
for (SearchHit item : hits) {
String json = item.getSourceAsString(); // 获取 JSON 字符串
T obj = JSONUtil.toBean(json, clazz); // 使用 Hutool 将 JSON 转换为对象
retList.add(obj);
}
return retList; // 返回结果列表
}
}
搜索自动补全API
/**
* 商品搜索框的自动补全
* @param text
* @return
*/
@Override
@SneakyThrows
public List<String> suggestion(String text) {
String Custom_Name ="title_suggest";
List<String> list =new ArrayList<>();
SearchRequest request =new SearchRequest(GOODS_INDEX);
request.source().suggest(new SuggestBuilder().addSuggestion(Custom_Name,
SuggestBuilders.completionSuggestion("suggestion").prefix(text)
.skipDuplicates(true).size(15)));
SearchResponse response= client.search(request,RequestOptions.DEFAULT);
Suggest suggest =response.getSuggest();
CompletionSuggestion suggestion =suggest.getSuggestion(Custom_Name);
List<CompletionSuggestion.Entry.Option> options =suggestion.getOptions();
for (CompletionSuggestion.Entry.Option option :options){
list.add(option.getText().toString());
}
return list;
}