You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Elasticsearch中实现多来源搜索结果的多样化排序?

问题描述

我从多个来源收集了文档,部分查询会返回大量同一来源的结果,这一情况并不理想。我不想过滤掉这些结果,而是希望对结果进行排序,让前几页的结果包含来自不同来源的内容。

文档结构示例:

{
  "doc": 1,
  "name": "Programming languages. A deep dive.",
  "source": "blog1",
  "url": "https://example.com/"
}
{
  "doc": 2,
  "name": "Learn coding in 30 seconds. For real!",
  "source": "blog2",
  "url": "..."
}

我的文档来自数千个网站,但部分网站(如blog1)拥有大量教程,其他网站则仅有少量。当我搜索热门词汇(如"chat bots")并获取前20条结果时,可能有5-10条来自blog1;进入第二页后,仍会看到大量blog1的结果,这看起来像是在推广该网站,并非我想要的效果。我希望前几页的结果更具多样性,但如果用户加载足够多的页面,仍能看到所有结果,因此过滤并非可行方案。

我考虑过统计每个来源已返回的文档数量,并以此降低该来源的权重,想知道这种方案能否在Elasticsearch query中实现?是否有其他替代方案?

补充说明:我理解通常这种需求难以实现,因为若提升某文档的分数至已返回文档之上会破坏排序规则,但我实际想要降低分数,这或许可行。Elasticsearch可在某条数据分数高于前一条时返回错误。

可行解决方案

方案1:动态降权已出现的来源(函数评分+脚本字段)

你提到的统计已返回来源数量并降权的思路完全可行,核心是利用Elasticsearch的**函数评分查询(Function Score Query)**结合脚本,根据当前请求中已出现的来源列表动态调整文档评分。

实现逻辑:

  • 在查询时,将当前页之前已经返回过的source列表作为参数传入
  • 通过脚本计算每个文档的来源在已返回列表中的出现次数,以此降低该文档的评分

示例查询:

{
  "query": {
    "function_score": {
      "query": { "match": { "name": "chat bots" } },
      "functions": [
        {
          "script_score": {
            "script": {
              "source": """
                def seen_sources = params.seen_sources;
                def source_count = seen_sources.stream().filter(s -> s == doc['source'].value).count();
                // 每出现一次,分数乘以0.5,可根据需求调整系数
                return Math.pow(0.5, source_count);
              """,
              "params": {
                "seen_sources": ["blog1", "blog3"] // 传入之前页面已返回的来源
              }
            }
          }
        }
      ],
      "boost_mode": "multiply" // 降权系数与原始评分相乘
    }
  },
  "size": 20
}

注意:这种方式需要前端或服务端记录每一页返回的source列表,下一页查询时传入。好处是完全保留所有结果,只是动态调整排序,用户翻到后面仍能看到同一来源的大量内容。

方案2:使用collapse字段折叠(分页需特殊处理)

Elasticsearch的collapse功能可以按字段(这里是source)折叠结果,确保每个来源在当前页只出现一次,同时通过inner_hits获取该来源的其他文档。

示例查询:

{
  "query": { "match": { "name": "chat bots" } },
  "collapse": {
    "field": "source",
    "inner_hits": {
      "name": "other_docs_from_source",
      "size": 5 // 每个来源最多保留5条备用,可调整
    }
  },
  "size": 20 // 当前页返回20个不同来源的顶级文档
}

分页注意:默认的from/size分页对折叠后的结果不友好,需要使用search_after进行分页,避免重复或遗漏。这种方式前几页的来源多样性极强,但需要处理inner_hits的展示逻辑,用户可以通过展开查看同一来源的更多内容。

方案3:自定义分桶排序(结合Terms Aggregation)

先通过聚合获取所有匹配的来源,再按来源的文档数量倒序(或自定义规则),然后逐个来源取文档,组成分页结果。

实现步骤:

  1. 执行聚合查询,获取匹配chat bots的所有source及对应的文档数
  2. 按来源文档数升序排序(优先取文档少的来源,保证多样性)
  3. 逐个来源执行查询,取指定数量的文档,直到凑够当前页的size

这种方式需要服务端做二次处理,灵活性最高,可以完全控制前几页的来源分布,但性能会比直接查询稍差,适合数据量不是特别大的场景。

内容的提问来源于stack exchange,提问作者ivant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 14:53:13