ElasticSearch拼音分词
有时我们有允许用户按照拼音搜索的需求,一个解决办法是在插入文档的分词过程中就生成对应的拼音,同时插入进倒排索引。
我们可以使用这个拼音分词插件:medcl/elasticsearch-analysis-pinyin
拼音分词器初步使用
POST /_analyze
{
"text": ["碳酸钙咀嚼片"],
"analyzer": "pinyin"
}
结果
拼音分词器把每个字都分成了拼音,并把首字母缩写也弄成了一个拼音,这是它在默认情况下的设置。
如果我们要在mapping中直接这样使用这个分词器的话,那么原始的中文将不会被保留,并且如上面一样,使用每个单子的拼音和整个的全拼建立倒排索引,这肯定不是我们所期望的。
分词的三个阶段
在ElasticSearch中,分词操作实际上有三个阶段,这给了我们自定义不同阶段所使用的分词器的机会
Character Filter
:用于在tokenizer
实际进行分词前对字符流进行处理(比如Html Strip Character Filter
会移除Html标签和特殊字符,只留下文本)Tokenizer
:接收一个字符流,将它们分成独立token,输出一个token流(即实际分词工作)Token Filter
:经常被称为Filter
,它接收tokenizer
的token流,并可以修改(比如转小写),删除(比如移除stopwords)或添加(比如添加同义词)
所以,整个流程就是
Character Filter预处理 -> Tokenizer分词 -> Filter后处理,增删改token
所以,一个看起来更合理的拼音分词策略是在
tokenizer
上使用ik分词器进行正常的中文分词,然后将pinyin分词器用在filter
上,对于ik分词器输出的token流中的每个token,都添加对应的拼音token。
创建自定义分词器
PUT /test
{
"settings": {
// 分析阶段的设置
"analysis": {
// 分析器设置
"analyzer": {
// 自定义分析器,在tokenizer阶段使用ik_max_word,在filter上使用py
"my_analyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
}
},
// 由于不满足pinyin分词器的默认设置,所以我们基于pinyin
// 自定义了一个filter,叫py,其中修改了一些设置
// 这些设置可以在pinyin分词器官网找到
"filter": {
"py": {
"type": "pinyin",
// 不会这样分:刘德华 > [liu, de, hua]
"keep_full_pinyin": false,
// 这样分:刘德华 > [liudehua]
"keep_joined_full_pinyin": true,
// 保留原始token(即中文)
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
// 定义mapping
"mappings": {
"properties": {
"name": {
"type": "text",
// 使用自定义分词器
"analyzer": "my_analyzer",
}
}
}
}
上面通过一长串定义了我们的自定义分词器,达成了我们的目标,还有值得说的一点是,pinyin分词器的keep_first_letter
的默认值是true,也就是会这样分:刘德华 > [ldh]
测试自定义分词器
POST /test/_doc/1
{
"id": 1,
"name": "狮子"
}
POST /test/_doc/2
{
"id": 2,
"name": "柿子"
}
GET /test/_search
{
"query": {
"match": {
"name": "shizi"
}
}
}
GET /test/_search
{
"query": {
"match": {
"name": "狮子"
}
}
}
目前还有一个缺陷,是当你查询狮子
时,柿子
也会在结果中:
因为搜索时也会使用my_analyzer
进行分词,所以狮子
会被分成狮子
、shizi
、sz
...当然能匹配到柿子
分离插入和搜索分词器
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer",
// 搜索时仅使用ik分词器
"search_analyzer": "ik_smart"
}
}
}