Elasticsearch 中的 Analyzers 和 Tokenizers

Tokenizer 与 Analyzer 详解

在 Elasticsearch 中,全文检索的核心并不是简单地对字符串做包含匹配,而是先将文本转换成一系列可检索的词项(Token),然后基于倒排索引进行查询。这个“将文本转换为词项(Token)”的过程,就是文本分析。

在文本分析体系中,两个非常重要的概念是 TokenizerAnalyzer

很多初学者容易把它们混为一谈,但实际上二者职责不同:

  • Tokenizer 负责切词。
  • Analyzer 负责完整的文本分析流程。

可以简单理解为:

Analyzer = Character Filter + Tokenizer + Token Filter

其中,Tokenizer 只是 Analyzer 中的一环。

1. 为什么 Elasticsearch 需要文本分析?

假设有一篇文档:

{
  "title": "The Quick Brown Fox"
}

如果 title 字段是 text 类型,Elasticsearch 不会直接把整句话作为一个整体保存到倒排索引中,而是会先对文本进行分析,例如转换成:

the
quick
brown
fox

查询时,用户搜索:

quick fox

Elasticsearch 也会对查询文本进行分析,得到:

quick
fox

然后再拿这些 token 去倒排索引中查找匹配文档。

因此,文本分析的核心作用是:

把文档文本和查询文本转换成可匹配的 token 流

如果索引阶段和查询阶段的分析逻辑不一致,就可能出现“明明应该搜到却搜不到”的问题。

2. Tokenizer 是什么?

Tokenizer 是分词器,负责把一段文本切分成一个个 token。

例如输入文本:

Hello, Elasticsearch world!

使用不同 tokenizer,结果可能完全不同。

如果使用 standard tokenizer,可能得到:

Hello
Elasticsearch
world

如果使用 whitespace tokenizer,可能得到:

Hello,
Elasticsearch
world!

可以看到,Tokenizer 的核心职责就是:

输入字符流,输出 token 流

它只负责切词,不负责小写转换、停用词过滤、同义词扩展等工作。这些通常由 Token Filter 完成。

3. Analyzer 是什么?

Analyzer 是分析器,负责完整的文本分析流程。

一个 Analyzer 通常由三部分组成:

Character Filter -> Tokenizer -> Token Filter

3.1 Character Filter

Character Filter 在 Tokenizer 之前执行,用于处理原始字符。

常见作用包括:

  • 去除 HTML 标签
  • 替换特殊字符
  • 正则替换
  • 字符映射

例如原始文本:

<p>Hello&nbsp;World</p>

经过 html_strip character filter 后,可能变成:

Hello World

3.2 Tokenizer

Tokenizer 负责切词。

例如:

Hello World

经过 tokenizer 后得到:

Hello
World

一个 Analyzer 中必须有且只能有一个 Tokenizer。

3.3 Token Filter

Token Filter 在 Tokenizer 之后执行,用于处理 token 流。

常见作用包括:

  • 转小写
  • 删除停用词
  • 词干化
  • 同义词扩展
  • 去重
  • 拼音转换
  • ASCII 折叠

例如:

Quick
Brown
Foxes

经过 lowercase filter 后变成:

quick
brown
foxes

再经过 stemmer 后,可能变成:

quick
brown
fox

4. Tokenizer 与 Analyzer 的关系

Tokenizer 和 Analyzer 的关系可以用下面的流程表示:

原始文本
Character Filter
Tokenizer
Token Filter
最终 token

其中:

  • Character Filter:切词前处理原始字符。
  • Tokenizer:将文本切成 token。
  • Token Filter:切词后处理 token。
  • Analyzer:将以上步骤组合成完整的分析流程。

因此,Tokenizer 是 Analyzer 的组成部分,而不是 Analyzer 本身。

5. 常见 Tokenizer

5.1 standard tokenizer

standard tokenizer 是 Elasticsearch 默认使用的 tokenizer,适合大多数通用文本场景。

示例:

GET /_analyze
{
  "tokenizer": "standard",
  "text": "The QUICK Brown-Foxes jumped!"
}

可能得到:

The
QUICK
Brown
Foxes
jumped

它会根据 Unicode 文本分割规则进行切分,通常会处理掉大部分标点。

5.2 whitespace tokenizer

whitespace tokenizer 只按照空白字符切分文本。

示例:

GET /_analyze
{
  "tokenizer": "whitespace",
  "text": "Hello, world! good-day"
}

可能得到:

Hello,
world!
good-day

它不会主动去除标点,因此 Hello,Hello 会被视为不同 token。

适合已经预处理过、只需要按空格切分的文本。

5.3 keyword tokenizer

keyword tokenizer 不切词,而是把整段输入作为一个 token。

示例:

GET /_analyze
{
  "tokenizer": "keyword",
  "text": "New York City"
}

输出:

New York City

常见用途包括:

  • 邮箱
  • URL
  • ID
  • 状态码
  • 标签
  • 枚举值

不过在实际建模中,这类字段通常更适合直接使用 keyword 字段类型。

5.4 letter tokenizer

letter tokenizer 会按照非字母字符切分文本。

例如:

abc-123-def

可能得到:

abc
def

数字会被分隔掉。

5.5 lowercase tokenizer

lowercase tokenizer 类似 letter tokenizer,但会自动把 token 转为小写。

例如:

Quick-BROWN

得到:

quick
brown

不过在实际项目中,更常见的做法是:

standard tokenizer + lowercase token filter

5.6 ngram tokenizer

ngram tokenizer 会把文本切成连续的 n-gram 片段。

例如文本:

search

如果配置:

min_gram = 2
max_gram = 3

可能得到:

se
sea
ea
ear
ar
arc
rc
rch
ch

它适合做部分匹配,但会显著增加索引体积。

常见使用场景:

  • 商品名模糊搜索
  • 包含匹配
  • 拼写容错
  • 搜索建议

5.7 edge_ngram tokenizer

edge_ngram tokenizer 只从词的开头生成 n-gram。

例如:

search

可能得到:

s
se
sea
sear
searc
search

它非常适合 autocomplete 场景。

例如用户输入:

sea

可以匹配:

search
season
seattle

5.8 pattern tokenizer

pattern tokenizer 使用正则表达式切分文本。

例如按逗号切分:

GET /_analyze
{
  "tokenizer": {
    "type": "pattern",
    "pattern": ","
  },
  "text": "java,python,golang"
}

输出:

java
python
golang

适合处理特殊格式文本,但复杂正则可能影响性能,需要谨慎使用。

6. 常见 Analyzer

Elasticsearch 内置了多种 Analyzer,用于覆盖常见搜索场景。

6.1 standard analyzer

standard analyzer 是默认 analyzer。

它大致等价于:

standard tokenizer + lowercase token filter

示例:

GET /_analyze
{
  "analyzer": "standard",
  "text": "The QUICK Brown-Foxes!"
}

可能得到:

the
quick
brown
foxes

适合大多数普通文本搜索。

6.2 simple analyzer

simple analyzer 会按照非字母字符切分,并转为小写。

例如:

The 2 QUICK Brown-Foxes

可能得到:

the
quick
brown
foxes

数字通常会被丢弃。

6.3 whitespace analyzer

whitespace analyzer 只按照空白字符切分,不做小写转换,也不主动去除标点。

例如:

Hello, WORLD!

得到:

Hello,
WORLD!

6.4 stop analyzer

stop analyzer 类似 simple analyzer,但会删除英文停用词。

例如:

The quick brown fox

可能得到:

quick
brown
fox

6.5 keyword analyzer

keyword analyzer 会把整段文本作为一个 token。

例如:

New York City

得到:

New York City

适合需要整体保留的文本。

6.6 language analyzer

Elasticsearch 还提供多种语言 analyzer,例如:

  • english
  • french
  • german
  • spanish

这些 analyzer 通常会包含:

  • 小写化
  • 停用词过滤
  • 词干化

例如英文中:

running

可能被还原为:

run

7. 自定义 Analyzer

在真实业务中,内置 Analyzer 往往不能完全满足需求,因此经常需要自定义 Analyzer。

一个自定义 Analyzer 通常包括:

{
  "type": "custom",
  "char_filter": [],
  "tokenizer": "standard",
  "filter": []
}

示例:定义一个英文内容搜索 Analyzer。

PUT /articles
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_english_analyzer": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "asciifolding",
            "stop",
            "porter_stem"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "my_english_analyzer"
      }
    }
  }
}

假设输入:

<p>The QUICK cafés are running!</p>

分析过程如下:

html_strip:
The QUICK cafés are running!

standard tokenizer:
The, QUICK, cafés, are, running

lowercase:
the, quick, cafés, are, running

asciifolding:
the, quick, cafes, are, running

stop:
quick, cafes, running

porter_stem:
quick, cafe, run

最终得到:

quick
cafe
run

8. analyzer 与 search_analyzer

字段可以同时配置 analyzersearch_analyzer

"title": {
  "type": "text",
  "analyzer": "index_analyzer",
  "search_analyzer": "search_analyzer"
}

含义是:

analyzer        -> 索引阶段使用
search_analyzer -> 查询阶段使用

如果没有显式指定 search_analyzer,查询阶段通常会使用字段的 analyzer

9. 自动补全场景示例

自动补全通常会在索引阶段生成前缀 token,在查询阶段使用普通 analyzer。

示例:

PUT /products
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "autocomplete_tokenizer": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 15,
          "token_chars": ["letter", "digit"]
        }
      },
      "analyzer": {
        "autocomplete_index_analyzer": {
          "type": "custom",
          "tokenizer": "autocomplete_tokenizer",
          "filter": ["lowercase"]
        },
        "autocomplete_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "autocomplete_index_analyzer",
        "search_analyzer": "autocomplete_search_analyzer"
      }
    }
  }
}

假设商品名为:

MacBook Pro

索引阶段可能生成:

ma
mac
macb
macbo
macboo
macbook
pr
pro

查询:

macb

就能匹配到:

MacBook Pro

需要注意的是,edge_ngram 通常用于索引阶段,不建议直接用于查询阶段,否则查询词也会被拆得很碎,导致召回过多无关结果。

10. text 字段与 keyword 字段

理解 Analyzer 时,还必须区分 textkeyword 两种字段类型。

10.1 text 类型

text 类型会经过 analyzer 分析,适合全文检索。

例如:

"title": {
  "type": "text",
  "analyzer": "standard"
}

文本:

Quick Brown Fox

会被分析成:

quick
brown
fox

适合使用:

{
  "match": {
    "title": "quick fox"
  }
}

10.2 keyword 类型

keyword 类型通常不做全文分析,而是整体作为一个 term。

例如:

"status": {
  "type": "keyword"
}

值:

Payment Failed

会作为整体保存。

适合用于:

  • 精确匹配
  • 排序
  • 聚合
  • 过滤
  • 枚举字段

例如:

{
  "term": {
    "status": "Payment Failed"
  }
}

10.3 multi-field

实际项目中,经常给一个字段同时建立 textkeyword 子字段。

"title": {
  "type": "text",
  "analyzer": "standard",
  "fields": {
    "keyword": {
      "type": "keyword"
    }
  }
}

这样:

title         -> 用于全文搜索
title.keyword -> 用于精确匹配、排序、聚合

全文搜索:

{
  "match": {
    "title": "quick fox"
  }
}

精确聚合:

{
  "aggs": {
    "titles": {
      "terms": {
        "field": "title.keyword"
      }
    }
  }
}

11. match query 与 term query 的区别

Analyzer 还会直接影响查询行为。

11.1 match query 会分析查询文本

{
  "match": {
    "title": "Quick Fox"
  }
}

如果 titletext 类型,查询文本会经过 analyzer,可能变成:

quick
fox

然后再去倒排索引中匹配。

因此,match 适合全文检索。

11.2 term query 不分析查询文本

{
  "term": {
    "title": "Quick Fox"
  }
}

term query 不会对查询文本进行分析,而是拿字面值直接去倒排索引中查。

如果 title 在索引时已经被分析成:

quick
fox

那么用 term 查询 "Quick Fox" 通常查不到结果。

因此:

text 字段通常使用 match / match_phrase / multi_match
keyword 字段通常使用 term / terms / prefix / wildcard

12. 中文搜索中的 Analyzer

中文和英文不同。

英文天然有空格:

I love Elasticsearch

中文通常没有空格:

我喜欢使用Elasticsearch

如果使用默认 analyzer,中文分词效果可能不符合业务预期。因此中文搜索通常需要使用专门的中文分词器,例如:

  • IK Analyzer
  • jieba analyzer
  • smartcn
  • 自研分词器

例如:

我喜欢自然语言处理

理想分词可能是:

喜欢
自然语言
处理

也可能是:

喜欢
自然
语言
处理

不同分词粒度会直接影响搜索效果。

中文搜索中常见问题包括:

  • 粗粒度分词与细粒度分词
  • 品牌词识别
  • 人名、地名、机构名识别
  • 新词热词维护
  • 同义词扩展
  • 拼音搜索
  • 繁简转换

以 IK Analyzer 为例,常见两种模式是:

ik_smart    -> 粗粒度分词
ik_max_word -> 细粒度分词

一种常见实践是:

索引阶段使用 ik_max_word,提高召回率
查询阶段使用 ik_smart,减少噪声

但这不是绝对规则,最终仍需要结合业务数据进行测试。

13. 使用 _analyze API 调试

排查 Analyzer 问题时,最重要的工具是 _analyze API。

示例:

GET /_analyze
{
  "analyzer": "standard",
  "text": "The QUICK Brown-Foxes!"
}

返回结果中重点关注:

{
  "tokens": [
    {
      "token": "the",
      "start_offset": 0,
      "end_offset": 3,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "quick",
      "start_offset": 4,
      "end_offset": 9,
      "type": "<ALPHANUM>",
      "position": 1
    }
  ]
}

其中:

字段 含义
token 最终生成的词项
start_offset token 在原始文本中的起始位置
end_offset token 在原始文本中的结束位置
position token 在 token 流中的位置
type token 类型

14. offset 与 position 的意义

14.1 offset

offset 表示 token 在原始文本中的字符位置。

它主要用于高亮。

例如文本:

The quick brown fox

quick 的 offset 可能是:

start_offset: 4
end_offset: 9

这样 Elasticsearch 在做 highlight 时,就知道应该高亮原始文本中的哪一段。

14.2 position

position 表示 token 在 token 流中的位置。

它会影响:

  • match_phrase
  • slop
  • 同义词短语
  • 停用词删除后的短语匹配

例如:

quick brown fox

positions 可能是:

quick -> 0
brown -> 1
fox   -> 2

如果查询短语:

quick fox

默认要求位置连续,可能无法匹配。

如果设置 slop,则可能匹配成功。

15. 常见实践建议

15.1 普通英文全文搜索

可以使用:

standard analyzer

或自定义:

standard tokenizer + lowercase + stop + stemmer

适合:

  • 文章
  • 博客
  • 描述
  • 知识库内容

15.2 精确匹配字段

使用 keyword 类型。

适合:

  • ID
  • 邮箱
  • 用户名
  • 订单号
  • 状态码
  • 标签
  • 枚举值

15.3 自动补全

可以考虑:

  • edge_ngram
  • search_as_you_type
  • completion suggester

其中 edge_ngram 常用于索引阶段,查询阶段通常使用普通 analyzer。

15.4 中文全文搜索

通常需要使用中文分词器,并结合业务词典进行优化。

重点关注:

  • 专有名词
  • 品牌词
  • 行业词
  • 同义词
  • 停用词
  • 拼音需求

15.5 同义词搜索

可以通过 synonym token filter 实现。

例如:

iphone, apple phone

这样用户搜索 apple phone 时,也可能匹配到 iphone

同义词需要谨慎维护,因为它会影响召回和相关性。

16. 常见误区

16.1 把 Tokenizer 等同于 Analyzer

Tokenizer 只是负责切词,Analyzer 是完整分析链路。

Analyzer = Character Filter + Tokenizer + Token Filter

16.2 对 text 字段使用 term query

text 字段通常会被 analyzer 拆成多个 token。

如果使用 term query 查询原始字符串,可能查不到结果。

对于 text 字段,应优先使用:

match
match_phrase
multi_match

16.3 认为 keyword 字段也会自动分词

keyword 字段通常整体索引,不做全文分词。

它适合精确匹配、排序和聚合,不适合普通全文检索。

16.4 盲目使用 ngram

ngramedge_ngram 会生成大量 token,可能带来:

  • 索引体积膨胀
  • 写入变慢
  • 查询噪声增加
  • 相关性调优复杂

因此只应该在明确需要部分匹配或自动补全时使用。

16.5 索引 analyzer 和查询 analyzer 不匹配

如果索引阶段和查询阶段生成的 token 形态差异过大,可能导致:

  • 召回不足
  • 误召回
  • 短语查询异常
  • 高亮异常

因此需要通过 _analyze API 验证索引文本和查询文本最终生成的 token 是否符合预期。

17. 一个完整示例

假设我们要为博客标题定义一个 analyzer:

PUT /blog
{
  "settings": {
    "analysis": {
      "analyzer": {
        "blog_analyzer": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "standard",
          "filter": ["lowercase", "stop"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "blog_analyzer"
      }
    }
  }
}

写入文档:

POST /blog/_doc/1
{
  "title": "<h1>The QUICK Guide to Elasticsearch</h1>"
}

分析过程:

原始文本:
<h1>The QUICK Guide to Elasticsearch</h1>

html_strip:
The QUICK Guide to Elasticsearch

standard tokenizer:
The, QUICK, Guide, to, Elasticsearch

lowercase:
the, quick, guide, to, elasticsearch

stop:
quick, guide, elasticsearch

最终写入倒排索引的 token 类似:

quick
guide
elasticsearch

当用户查询:

GET /blog/_search
{
  "query": {
    "match": {
      "title": "quick elasticsearch"
    }
  }
}

查询文本会被分析为:

quick
elasticsearch

因此可以匹配到该文档。

18. 总结

Tokenizer 和 Analyzer 是 Elasticsearch 全文检索中非常基础但关键的概念。

Tokenizer 负责把文本切成 token:

"Quick Brown Fox" -> quick, brown, fox

Analyzer 则负责完整的文本分析流程:

Character Filter -> Tokenizer -> Token Filter

在实际使用中,需要重点理解以下几点:

  • text 字段会经过 analyzer,适合全文检索。
  • keyword 字段通常整体索引,适合精确匹配、排序和聚合。
  • match query 会分析查询文本。
  • term query 不会分析查询文本。
  • Tokenizer 只是 Analyzer 的一部分。
  • 索引阶段和查询阶段的 analyzer 需要协同设计。
  • 排查搜索问题时,应优先使用 _analyze API 查看最终 token。

设计 Analyzer 的本质,是让“用户输入的查询文本”和“索引中的文档文本”在 token 层面能够正确对齐。只有 token 对齐,全文检索才能获得稳定、可控的召回和相关性效果。

使用 Hugo 构建
主题 StackJimmy 设计