OpenSearch 查看分词与词频技术指南

三种方法全面掌握 Token 分析、Term Frequency 与文档频率统计 — 含实测案例与踩坑记录

1. 概述与方法对比

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(文档频率) 全索引范围的高频词统计

2. 方法一:_analyze API — 测试分词效果

1 _analyze API

不依赖已有文档,直接测试一段文本会被拆成哪些 token。不返回词频统计,如果同一个词出现多次,需要自己统计 token 数组中的重复项。

用法 1:指定 analyzer 测试任意文本

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),手动统计即可得到词频。

用法 2:指定 field,使用该字段 mapping 中配置的分析器

POST /{index}/_analyze
{
  "field": "content",
  "text": "我们完成了Series A融资,融资金额创新高"
}

比手动指定 analyzer 更贴近真实索引行为——会自动读取该字段 mapping 里配置的 analyzer(如自定义的 ik_english_mixed)。

💡 使用建议 _analyze 适合开发调试阶段验证分词效果(如中文 IK 分词器的 smart/max_word 模式对比),不适合生产环境的词频统计。它不写入索引,也不影响已有数据。

3. 方法二:Term Vectors API — 单文档精确词频

2 Term Vectors API

直接返回 term_freq 字段,是获取单文档精确词频的最佳方法。支持查看词在文档中的出现位置(position)和字符偏移(offset)。

基本用法:查询已索引文档

GET /{index}/_termvectors/{doc_id}
{
  "fields": ["text"],
  "term_statistics": true,
  "positions": true,
  "offsets": true
}
⚠️ 关键限制 如果字段 mapping 中没有开启 term_vector 存储,对已索引的真实文档按 _id 查询会返回空结果("term_vectors": {}),即使文档 found: true。这个行为不报错,容易误以为文档没有内容。

绕过方案:Artificial Document(实时生成,不依赖 mapping 配置)

不传 _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 次。

正确方案(生产环境):在 mapping 中开启 term_vector 存储

要让已索引的真实文档支持直接查询词频(无需 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全部信息
⚠️ 注意事项

Filter 参数:精确控制返回的 Term

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最大词长度

4. 方法三:Terms Aggregation — 全索引词分布统计

3 Terms Aggregation

统计整个索引范围内某个字段的文档频率分布(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 临时调试,用完应关闭(占用大量内存)

基本用法:对 keyword 子字段聚合

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 vs term_freq doc_count 是"有多少文档包含这个词"(文档频率 / Document Frequency),不是"这个词在所有文档里总共出现了多少次"(总词频 / Total Term Frequency)。如果字段本身是整段文本(如 .keyword 未分词),则 doc_count = 该精确值出现的文档数。

高级用法:配合 query 筛选范围

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"}
      }
    }
  }
}

5. 如何解读 Term Vectors 返回结果

以 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, ...}]}
      }
    }
  }
}

field_statistics(全索引范围统计)

字段含义示例值解释
doc_count索引里有多少篇文档该字段有值70.4 万篇文档含 message 字段
sum_ttf该字段所有词加起来出现的总次数约 1878 万个词元(理解为"这个字段一共有多少个词元")
sum_doc_freq所有不同词各自"出现在多少篇文档里"的加总主要用于计算 TF-IDF,日常查词频不用太关注

terms(当前这一条文档的词频)

字段含义
term_freq这个词在这一条文档里出现了几次
tokens[].position分词后的第几个 token(从 0 开始)
tokens[].start_offset / end_offset该词在原始文本里的字符起止位置,可用于高亮定位
doc_freq(需开启 term_statistics全索引有多少文档包含这个词
ttf(需开启 term_statistics这个词在全索引所有文档里的总出现次数

速查:不同问题该看哪个字段

这条文档里某个词出现几次
terms.<词>.term_freq
这个字段全索引一共有多少词元
field_statistics.sum_ttf
全索引有多少文档命中这个字段
field_statistics.doc_count
某词在全索引所有文档里的总出现次数
terms.<词>.ttf(需开启 term_statistics)

6. 常见坑总结

#问题描述原因与解决
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,验证结果:

7. 参考文档

文档链接
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