Loading

ElasticSearch拼音分词

有时我们有允许用户按照拼音搜索的需求,一个解决办法是在插入文档的分词过程中就生成对应的拼音,同时插入进倒排索引

我们可以使用这个拼音分词插件:medcl/elasticsearch-analysis-pinyin

拼音分词器初步使用

POST /_analyze
{
  "text": ["碳酸钙咀嚼片"],
  "analyzer": "pinyin"
}

结果

img

拼音分词器把每个字都分成了拼音,并把首字母缩写也弄成了一个拼音,这是它在默认情况下的设置。

如果我们要在mapping中直接这样使用这个分词器的话,那么原始的中文将不会被保留,并且如上面一样,使用每个单子的拼音和整个的全拼建立倒排索引,这肯定不是我们所期望的。

分词的三个阶段

在ElasticSearch中,分词操作实际上有三个阶段,这给了我们自定义不同阶段所使用的分词器的机会

  1. Character Filter:用于在tokenizer实际进行分词前对字符流进行处理(比如Html Strip Character Filter会移除Html标签和特殊字符,只留下文本)
  2. Tokenizer:接收一个字符流,将它们分成独立token,输出一个token流(即实际分词工作)
  3. 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": "狮子"
    }
  }
}

目前还有一个缺陷,是当你查询狮子时,柿子也会在结果中:

img

因为搜索时也会使用my_analyzer进行分词,所以狮子会被分成狮子shizisz...当然能匹配到柿子

分离插入和搜索分词器

"mappings": {
    "properties": {
        "name": {
            "type": "text",
            "analyzer": "my_analyzer",
            // 搜索时仅使用ik分词器
            "search_analyzer": "ik_smart"
        }
    }
}
posted @ 2022-08-19 13:50  yudoge  阅读(545)  评论(0编辑  收藏  举报