三种方法全面掌握 Token 分析、Term Frequency 与文档频率统计 — 含实测案例与踩坑记录
OpenSearch 没有单一的"查词频" API,需要根据使用场景选择合适的方法:测试分词效果、查看单文档词频、还是统计全索引词分布。下表总结了三种方法的核心差异:
| 方法 | 接口 | 能否直接得到词频 | 适用场景 |
|---|---|---|---|
_analyze API |
POST /{index}/_analyze |
否(需自己数 token 重复次数) | 测试分词效果、验证 analyzer 配置 |
| Term Vectors API | GET/POST /{index}/_termvectors |
是(直接返回 term_freq) |
单个文档的精确词频与位置信息 |
| Terms Aggregation | POST /{index}/_search 配合 aggs |
返回 doc_count(文档频率) |
全索引范围的高频词统计 |
不依赖已有文档,直接测试一段文本会被拆成哪些 token。不返回词频统计,如果同一个词出现多次,需要自己统计 token 数组中的重复项。
POST /{index}/_analyze
{
"analyzer": "ik_smart",
"text": "OpenSearch向量搜索性能优化,向量搜索需要合理配置分片"
}
返回结果(节选):
{
"tokens": [
{"token": "opensearch", "position": 0, ...},
{"token": "向量", "position": 1, ...},
{"token": "搜索", "position": 2, ...},
...
{"token": "向量", "position": 5, ...}, // "向量" 第 2 次出现
{"token": "搜索", "position": 6, ...} // "搜索" 第 2 次出现
]
}
"向量"、"搜索" 各出现 2 次(position 1/5、2/6),手动统计即可得到词频。
POST /{index}/_analyze
{
"field": "content",
"text": "我们完成了Series A融资,融资金额创新高"
}
比手动指定 analyzer 更贴近真实索引行为——会自动读取该字段 mapping 里配置的 analyzer(如自定义的 ik_english_mixed)。
_analyze 适合开发调试阶段验证分词效果(如中文 IK 分词器的 smart/max_word 模式对比),不适合生产环境的词频统计。它不写入索引,也不影响已有数据。
直接返回 term_freq 字段,是获取单文档精确词频的最佳方法。支持查看词在文档中的出现位置(position)和字符偏移(offset)。
GET /{index}/_termvectors/{doc_id}
{
"fields": ["text"],
"term_statistics": true,
"positions": true,
"offsets": true
}
term_vector 存储,对已索引的真实文档按 _id 查询会返回空结果("term_vectors": {}),即使文档 found: true。这个行为不报错,容易误以为文档没有内容。
不传 _id,用 doc 字段提供虚构文档内容,OpenSearch 会实时分词并计算词频:
POST /{index}/_termvectors
{
"doc": {
"content": "我们完成了Series A融资,融资金额创新高"
},
"field_statistics": true,
"fields": ["content"],
"offsets": true,
"positions": true,
"term_statistics": true
}
返回结果(节选):
{
"term_vectors": {
"content": {
"field_statistics": {"sum_doc_freq": 0, "doc_count": 0, "sum_ttf": 0},
"terms": {
"融资": {
"term_freq": 2,
"tokens": [
{"position": 4, ...},
{"position": 5, ...}
]
},
"我们": {"term_freq": 1, ...},
...
}
}
}
}
term_freq 直接给出词频,"融资" = 2 次。
要让已索引的真实文档支持直接查询词频(无需 artificial document),需要在字段 mapping 加上 term_vector 参数:
PUT /my-index
{
"mappings": {
"properties": {
"message": {
"type": "text",
"term_vector": "with_positions_offsets",
"fields": {
"keyword": {"type": "keyword", "ignore_above": 256}
}
}
}
}
}
term_vector 支持以下值:
| 值 | 存储内容 |
|---|---|
no(默认) | 不存储 term vector |
yes | 仅存储词频 |
with_positions | 词频 + 位置 |
with_offsets | 词频 + 字符偏移 |
with_positions_offsets | 词频 + 位置 + 偏移(推荐) |
with_positions_offsets_payloads | 全部信息 |
mapping 修改(含 _index_template)只影响未来新建的索引,OpenSearch 不允许修改已有索引的 term_vector 配置term_vector 存储会增加索引体积(通常几个百分点到 30%,视字段大小),非必要不建议全局开启_index_template 后需要等待下一次 rollover(通常次日)新索引才会生效Term Vectors API 支持 filter 对象来过滤返回的 token:
POST /{index}/_termvectors/{doc_id}
{
"fields": ["content"],
"term_statistics": true,
"filter": {
"max_num_terms": 10,
"min_term_freq": 2,
"min_word_length": 2
}
}
| 过滤参数 | 说明 |
|---|---|
max_num_terms | 最多返回多少个 term |
min_term_freq | 最低文档内词频 |
max_term_freq | 最高文档内词频 |
min_doc_freq | 最低文档频率(全索引) |
max_doc_freq | 最高文档频率(全索引) |
min_word_length | 最小词长度 |
max_word_length | 最大词长度 |
统计整个索引范围内某个字段的文档频率分布(doc_count = 包含该词的文档数,不是词在单文档内的出现次数)。
text 类型字段默认不支持聚合,直接使用会报错:
// 错误信息:
Text fields are not optimised for operations that require per-document
field data like aggregations and sorting, so these operations are
disabled by default. Please use a keyword field instead.
两种解决方式:
| 方式 | 做法 | 适用场景 |
|---|---|---|
推荐用 .keyword 子字段 |
直接聚合 field_name.keyword |
mapping 已配置多字段(大部分默认 mapping 都有) |
仅测试临时开 fielddata |
PUT /{index}/_mapping 设置 "fielddata": true |
临时调试,用完应关闭(占用大量内存) |
POST /{index}/_search
{
"size": 0,
"aggs": {
"top_terms": {
"terms": {
"field": "payload.category.keyword",
"size": 15
}
}
}
}
返回结果示例:
{
"aggregations": {
"top_terms": {
"buckets": [
{"key": "short_term", "doc_count": 314},
{"key": "project", "doc_count": 266},
{"key": "experience", "doc_count": 192},
{"key": "environment", "doc_count": 109},
{"key": "decision", "doc_count": 42},
{"key": "preference", "doc_count": 24}
]
}
}
}
doc_count 是"有多少文档包含这个词"(文档频率 / Document Frequency),不是"这个词在所有文档里总共出现了多少次"(总词频 / Total Term Frequency)。如果字段本身是整段文本(如 .keyword 未分词),则 doc_count = 该精确值出现的文档数。
POST /{index}/_search
{
"size": 0,
"query": {
"range": {
"@timestamp": {
"gte": "2026-07-01",
"lt": "2026-07-25"
}
}
},
"aggs": {
"top_terms": {
"terms": {
"field": "action.keyword",
"size": 20,
"order": {"_count": "desc"}
}
}
}
}
以 VPC Flow Logs 的实测返回为例:
{
"_index": "vpc-logs-2026.07.23-000259",
"_id": "E3wMkp8BQCva11W17tSU",
"found": true,
"term_vectors": {
"message": {
"field_statistics": {
"sum_doc_freq": 17521511,
"doc_count": 703983,
"sum_ttf": 18781232
},
"terms": {
"10.192.22.96": {
"term_freq": 2,
"tokens": [
{"position": 5, "start_offset": 50, "end_offset": 62},
{"position": 24, "start_offset": 202, "end_offset": 214}
]
},
"accept": {"term_freq": 1, "tokens": [{"position": 13, ...}]}
}
}
}
}
| 字段 | 含义 | 示例值解释 |
|---|---|---|
doc_count | 索引里有多少篇文档该字段有值 | 70.4 万篇文档含 message 字段 |
sum_ttf | 该字段所有词加起来出现的总次数 | 约 1878 万个词元(理解为"这个字段一共有多少个词元") |
sum_doc_freq | 所有不同词各自"出现在多少篇文档里"的加总 | 主要用于计算 TF-IDF,日常查词频不用太关注 |
| 字段 | 含义 |
|---|---|
term_freq | 这个词在这一条文档里出现了几次 |
tokens[].position | 分词后的第几个 token(从 0 开始) |
tokens[].start_offset / end_offset | 该词在原始文本里的字符起止位置,可用于高亮定位 |
doc_freq(需开启 term_statistics) | 全索引有多少文档包含这个词 |
ttf(需开启 term_statistics) | 这个词在全索引所有文档里的总出现次数 |
| # | 问题描述 | 原因与解决 |
|---|---|---|
| 1 | Term Vectors 返回空 {} 但 found: true |
字段 mapping 未开 term_vector 存储。临时方案:用 artificial document( doc 参数)绕过长期方案:改 mapping 后 reindex |
| 2 | _source 里的业务 id ≠ OpenSearch 的 _id |
查询 term vectors 要用 OpenSearch 返回的真实 _id(搜索结果的 _id 字段),不是文档内容里自定义的 id 字段 |
| 3 | Terms Aggregation 报错 "Text fields are not optimised..." | text 字段不能直接聚合,用 .keyword 子字段或临时开 fielddata(用完记得关闭) |
| 4 | doc_count(聚合)≠ term_freq(term vectors) |
前者是文档频率(多少文档包含该词),后者是单文档内词频(出现几次),语义不同 |
| 5 | _index_template 改动不是立即对所有索引生效 |
只影响改动后新建(rollover)的索引。当天已存在的索引不受影响,需要等下一次 rollover(通常次日) |
vpc_logs_template(VPC Flow Logs 索引模板)的 message 字段加 term_vector: with_positions_offsets,验证结果:
vpc-logs-2026.07.22-000258(改动前创建)→ 直接查询返回空 {}vpc-logs-2026.07.23-000259(改动后 rollover)→ ✅ 正常返回 term_freq| 文档 | 链接 |
|---|---|
| OpenSearch Term Vectors API | docs.opensearch.org/latest/api-reference/document-apis/termvector/ |
| OpenSearch Multi Term Vectors API | docs.opensearch.org/latest/api-reference/document-apis/mtermvectors/ |
| OpenSearch Analyze API | docs.opensearch.org/latest/api-reference/analyze-apis/ |
| term_vector Mapping Parameter | docs.opensearch.org/latest/mappings/mapping-parameters/term-vector/ |
| Elasticsearch Term Vectors Examples | elastic.co/docs/reference/elasticsearch/rest-apis/term-vectors-examples |