在ElasticSearch中实现跨分段的带时间戳转录本短语搜索
跨片段转录本短语搜索问题及解决方案
当前问题场景
现有转录本文档结构如下:
{ "title": "Hello, World!", "segments": [ { "text": "waive such protections", "start": 0, "end": 7040 }, { "text": "in all contexts", "start": 7040, "end": 8500 } ] }
对应完整语句为waive such protections in all contexts。当前仅能搜索单个segments条目内的短语,无法直接搜索跨相邻segments的完整短语。现有拆分查询方案(将短语拆分为多个连续子集,用bool查询设置minimum_should_match=1)会生成大量子查询,运行效率极低。
解决方案
一、不调整结构的查询优化
如果不想修改文档结构,可采用以下两种查询方式替代拆分查询:
- 短语前缀查询:针对跨片段的短语,仅组合相邻片段的衔接部分词汇生成查询,比如针对示例中的完整短语,只需生成
"protections in"这类衔接短语查询,减少查询数量。 - 跨度查询(Span Query):利用SpanNearQuery指定词汇的位置关系,结合segments的时间顺序(确保是相邻片段),匹配分布在多个segments中的连续短语。
不过这类方案仍有局限性,性能和维护成本不如调整文档结构。
二、更优的文档结构方案
推荐两种从根源解决问题的结构:
1. 全文索引+片段位置映射结构
在原有结构基础上新增full_text字段存储完整转录文本,同时给每个segments条目增加字符位置标记,用于后续时间戳映射:
{ "title": "Hello, World!", "full_text": "waive such protections in all contexts", "segments": [ { "text": "waive such protections", "start": 0, "end": 7040, "char_start": 0, "char_end": 23 }, { "text": "in all contexts", "start": 7040, "end": 8500, "char_start": 24, "char_end": 40 } ] }
- 先对
full_text执行高效的全文短语搜索,得到匹配文本在full_text中的字符区间; - 通过字符区间匹配对应的segments条目,取第一个匹配seg的
start和最后一个匹配seg的end作为短语的时间戳。
这种结构实现简单,搜索性能高,是首选方案。
2. 细粒度词汇拆分结构
将每个词汇作为独立条目,记录其时间信息和所属片段:
{ "title": "Hello, World!", "words": [ { "word": "waive", "start": 0, "end": 1500, "segment_id": 0 }, { "word": "such", "start": 1500, "end": 3000, "segment_id": 0 }, { "word": "protections", "start": 3000, "end": 7040, "segment_id": 0 }, { "word": "in", "start": 7040, "end": 7500, "segment_id": 1 }, { "word": "all", "start": 7500, "end": 8000, "segment_id": 1 }, { "word": "contexts", "start": 8000, "end": 8500, "segment_id": 1 } ] }
- 短语搜索时直接匹配连续的
word条目,只要条目属于相邻segment_id或时间连续,即可聚合出短语的起始(首词start)和结束(末词end)时间戳。 - 此方案精度最高,但数据量会增大,适合对时间戳精度要求极高的场景。
三、方案性能对比
- 原有拆分查询:生成大量子查询,性能最差,不推荐;
- 优化查询方式:无需改结构,但需处理片段衔接逻辑,性能中等;
- 全文索引+片段映射:实现简单,搜索效率高,推荐作为首选;
- 细粒度词汇结构:精度最高,但数据量较大,适合高精度场景。
内容的提问来源于stack exchange,提问作者knpwrs
相关产品推荐
相关产品推荐

