Elasticsearch 分词器 工作原理

如果说倒排索引是ES的"心脏",那分词器就是ES的"消化系统"------它决定了一句话被拆成什么样的"营养颗粒"(词项)存入索引。如果分词分得不好,搜索就搜不准。

分词器的工作过程并非简单的"按空格切分",它在内部严格分为 3 个顺序执行的组件


第一阶段:字符过滤器(Character Filters)

这是预处理环节,在正式分词前对原始文本进行"清洗"。

  • 作用:剔除无用字符或进行统一替换。

  • 内置示例

    • HTML Strip:去除 <p><a> 等HTML标签,提取纯文本。

    • Mapping:将 & 替换为 and,或将全角数字(123)替换为半角(123)。

  • 注意 :这里改变词意,只做"净化"。


第二阶段:分词器(Tokenizer)

这是最核心 的环节。它将清洗后的文本,按照一定规则切分成一个个独立的 Token(词项/单元),并记录每个词在原文中的位置(Position)和偏移量(Offset)。

  • 常见切分规则

    • 按空格/标点切分whitespace / standard):适用于英文。

    • 按单字切分keyword):不分词,整个字段作为一个词,适用于邮编、状态码。

    • 按语义切分ik_smart / jieba):专用于中文,因为中文没有空格,必须依赖词典判断。

  • 关键点 :Tokenizer 决定了最小的搜索粒度。切得太细(如"中华人民共和国"切成"中""华""人"),搜"民国"会误出;切得太粗(整词),搜"中华"又搜不到。


第三阶段:词项过滤器(Token Filters)

分词完成后,对生成的 Token 列表进行二次加工。这里可以串联多个过滤器,按顺序执行。

  • Lowercase(小写化) :将 Apple -> apple,保证搜索时不区分大小写。

  • Stopword(停用词过滤) :剔除无意义的虚词,如中文的"的"、"了",英文的 thea。这些词出现太频繁,会严重拉低搜索评分并占用磁盘。

  • Synonym(同义词扩展):当遇到"番茄"时,自动添加一个同义词 Token "西红柿"。这样用户搜"西红柿"也能命中包含"番茄"的文档。

  • Stemming(词干提取,英文专有) :将 running -> runcats -> cat。将时态和复数统一为词根,提高召回率。


你必须要懂的两个"时机"(巨坑预警)

分词器在 ES 中应用在两个不同 的时间点,使用的可以是不同的分词器

  1. Index Time(索引时) :文档写入时,使用 index analyzer 将文本切碎存入倒排索引。

  2. Search Time(搜索时) :用户输入搜索词时,使用 search analyzer 将查询词切碎。

最佳实践 :为了让搜索更智能,索引时通常细粒度 (尽可能多地切出词根),搜索时粗粒度(保持短语完整性)。如果两者用反了,就会导致"搜得到但不相关"或"相关但搜不到"。


实战举例:中文分词是怎么"猜"的?(以 IK 分词器为例)

英文分词简单,但中文是连续字符 ,机器不知道哪里断词。IK 分词器的工作原理是 "词典匹配 + 歧义消除"

  • 词典(Dictionary) :内置一个庞大的词库(如 南京市, 长江, 大桥)。

  • 切分过程

    • 输入:"南京市长江大桥"

    • 智能模式(ik_smart):从左向右扫描,优先匹配最长词,输出 [南京市, 长江大桥]

    • 细粒度模式(ik_max_word):穷尽所有可能,输出 [南京市, 南京, 市长, 长江大桥, 长江, 大桥]

  • 避坑 :如果词典里没有"江大桥",IK 就会切分成 [南京市, 长江, 大桥]。所以词典热更新是中文搜索维护的重点。


总结对比(帮助记忆)

组件 英文类比 中文特例
Character Filter 去掉 <b> 标签 将中文全角逗号","替换为英文","
Tokenizer(核心) 按空格切 "Hello World" 依赖词典切 "你好世界"
Token Filter Running -> run "西红柿" 添加同义词 "番茄"

如果分词效果不理想,该怎么办?

  1. 不用 ES 自带:中文务必安装 IK 或 HanLP 插件。

  2. 自定义词典 :将公司内部专有名词(如"王者荣耀"、"特斯拉Model3")加入 IK 的 main.dic

  3. 使用 term 查询验证 :可以通过 _analyze API 直接测试分词结果,确保索引和搜索的分词器一致。

bash 复制代码
POST /_analyze
{
  "analyzer": "ik_smart",
  "text": "南京市长江大桥"
}