Tokenizer 与 Analyzer 详解
在 Elasticsearch 中,全文检索的核心并不是简单地对字符串做包含匹配,而是先将文本转换成一系列可检索的词项(Token),然后基于倒排索引进行查询。这个“将文本转换为词项(Token)”的过程,就是文本分析。
在文本分析体系中,两个非常重要的概念是 Tokenizer 和 Analyzer。
很多初学者容易把它们混为一谈,但实际上二者职责不同:
- 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 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,例如:
englishfrenchgermanspanish
这些 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
字段可以同时配置 analyzer 和 search_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 时,还必须区分 text 和 keyword 两种字段类型。
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
实际项目中,经常给一个字段同时建立 text 和 keyword 子字段。
"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"
}
}
如果 title 是 text 类型,查询文本会经过 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_phraseslop- 同义词短语
- 停用词删除后的短语匹配
例如:
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_ngramsearch_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
ngram 和 edge_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字段通常整体索引,适合精确匹配、排序和聚合。matchquery 会分析查询文本。termquery 不会分析查询文本。- Tokenizer 只是 Analyzer 的一部分。
- 索引阶段和查询阶段的 analyzer 需要协同设计。
- 排查搜索问题时,应优先使用
_analyzeAPI 查看最终 token。
设计 Analyzer 的本质,是让“用户输入的查询文本”和“索引中的文档文本”在 token 层面能够正确对齐。只有 token 对齐,全文检索才能获得稳定、可控的召回和相关性效果。