elasticsearch添加拼音分词搜索

分词器是es当中的一个组件,通俗来讲,就是将搜索的条件按照语义进行拆分,分词为多个词语,es会讲text格式的字段按照分词器的结果进行分词,并编排成倒排索引,正因为如此,es的查询速度才会很快,es当中本身就内置了很多分词器

分词器 作用
Standard ES的默认分词器,按单词分类并进行大小写处理
Simple 按照非字母区分,然后去除非字母并按小写进行处理
Stop 按照停顿词进行过滤并进行小写处理,停用词包括the a is
Whitespace 按照空格进行拆分
Patter 按照正则进行拆分,默认是\W+ ,代表非字母
Keyword 不进行分词,作为一个整体输出

es的默认分词器为Standard,对中文分词时会全部拆分为单个字并过滤掉特殊字符
说到分词器,不得不提及中文分词器ik,这里只描述es中的ik分词器

ik分词器

ikAnalyzrer是一个开源的,基于java语言开发的轻量级的中文分词工具包,新版本的ik分词器发展为面向java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认实现
ik分词器默认有两种分词模式:
ik_max_word(常用模式)将文本做最细粒度拆分
ik_smart 将文本做粗粒度拆分
ik_smart分词

GET book/_analyze
{
  "analyzer": "ik_smart",
  "text": ["武当山上张三丰"]
}
{
  "tokens" : [
    {
      "token" : "武当",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "山上",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "张三丰",
      "start_offset" : 4,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 2
    }
  ]
}

使用ik_max_word分词

GET book/_analyze
{
  "analyzer": "ik_max_word",
  "text": ["武当山上张三丰"]
}
{
  "tokens" : [
    {
      "token" : "武当山",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "武当",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "山上",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "张三丰",
      "start_offset" : 4,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "张三",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "三",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "TYPE_CNUM",
      "position" : 5
    },
    {
      "token" : "丰",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "CN_CHAR",
      "position" : 6
    }
  ]
}

可以明显的看到ik_max_word拆分的更细一些,实际使用时一般写入数据时使用ik_max_word,查询数据时使用ik_smart(如果没有search_analyzer,默认使用analyzer)
一般安装了ik中文分词器可以满足大部分的场景,但是很多使用用户并不一定搜的是中文,输入法的原因可能输入的拼音,为了满足用户也为了搜索更加精准,扩展的分词器有拼音分词器、同义字分词器、繁体字转简体字等等,这里总结一下es使用拼音分词器

安装拼音分词器

项目地址:https://github.com/medcl/elasticsearch-analysis-pinyin
源码大致原理为调用nlp工具包https://github.com/NLPchina
1、下载完项目之后需要根据自己当前的es版本选择代码分支

这里我的版本是7.17.4所以使用7.x这个分支的代码
2、修改pom.xml中的elasticsearch.version为自己的版本

3、mvn install之后在target文件夹elasticsearch-analysis-pinyin-xxx.jar复制到es安装目录plugins目录底下,解压之后后看到elasticsearch-analysis-pinyin-7.17.4.jar、 plugin-descriptor.properties和nlp-lang-1.7.jar

4、重启es,线上环境要注意
安装完成我们可以重构索引,使用pinyin分词器
这里有一些tokenizer参数

参数 默认值 描述 分词结果
keep_first_letter true 保留字符首个拼音 刘德华 -> ldh
keep_separate_first_letter false 会将首字母单独拆分为一个token 刘德华>[l,d,h]
limit_first_letter_length 16 设置首字母最大长度
keep_full_pinyin true 每个字符的全拼 刘德华> [liu,de,hua]
keep_joined_full_pinyin false 每个字符的全拼连接 刘德华> [liudehua]

这里我们新建索引pinyin_test进一步测试

PUT pinyin_test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "pinyin_analyzer": {
          "tokenizer": "my_pinyin"
        }
      },
      "tokenizer": {
        "my_pinyin": {
          "type": "pinyin",
          "keep_first_letter": true,
          "keep_separate_first_letter": false,
          "keep_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "lowercase": true,
          "remove_duplicated_term": true,
          "keep_separate_chinese": true
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword",
        "fields": {
          "pinyin": {
            "type": "text",
            "analyzer": "pinyin_analyzer"
          }
        }
      }
    }
  }
}

创建索引的时候指定name字段的分词器为pinyin_analyzer, 注意这里的 tokenizer里的名称不能是pinyin,如果写成pinyin所有的参数都是默认的,不利于我们定制不同的字段分词器。 这里我们先使用analyzer,不使用search_analyzer
插入数据

POST pinyin_test/_doc
{
  "name": "少年歌行"
}
POST pinyin_test/_doc
{
  "name": "骚年歌行"
}
POST pinyin_test/_doc
{
  "name": "少年歌杭"
}


下面我们查看分词的结果

GET pinyin_test/_analyze
{
  "analyzer": "pinyin_analyzer",
  "text": "少年歌行"
}
{
  "tokens" : [
    {
      "token" : "shao",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "少年歌行",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "sngx",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "nian",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "ge",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "xing",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 3
    }
  ]
}

可以看到的拆分后的token中含有我们需要的拼音,因为keep_first_letter设置为true所以可以看到sngx,

GET pinyin_test/_search
{
  "query": {
    "match_phrase": {
      "name.pinyin": "shao年歌行"
    }
  }
}

搜索结果

但是当搜索 少年gexing 的时候,期望结果是只有少年歌行一条结果

但是可以看到骚年歌行也出来了,这是因为keep_separate_first_letter改为true之后骚年歌行也会包含s

针对这种情况,有位老哥提了issue并且作者合到了master分支,在tokenizer中添加参数keep_separate_chinese:true,可以保证在中文加拼音的时候,优先匹配到中文汉字,然后匹配拼音
https://github.com/medcl/elasticsearch-analysis-pinyin/issues/203

posted @ 2023-03-18 18:07  木马不是马  阅读(419)  评论(0编辑  收藏  举报