高亮显示

编辑

高亮器使您可以从搜索结果中的一个或多个字段中获取高亮片段,以便您可以向用户显示查询匹配的位置。当您请求高亮显示时,响应会为每个搜索命中包含一个额外的 highlight 元素,其中包含高亮显示的字段和高亮显示的片段。

高亮器在提取要高亮的术语时,不会反映查询的布尔逻辑。因此,对于某些复杂的布尔查询(例如嵌套的布尔查询、使用 minimum_should_match 等的查询),文档的某些部分可能会被高亮显示,而这些部分与查询匹配不对应。

高亮显示需要字段的实际内容。如果字段未存储(映射未将 store 设置为 true),则会加载实际的 _source,并从 _source 中提取相关字段。

例如,要使用默认高亮器获取每个搜索命中中 content 字段的高亮显示,请在请求正文中包含一个 highlight 对象,该对象指定 content 字段

resp = client.search(
    query={
        "match": {
            "content": "kimchy"
        }
    },
    highlight={
        "fields": {
            "content": {}
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        content: 'kimchy'
      }
    },
    highlight: {
      fields: {
        content: {}
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      content: "kimchy",
    },
  },
  highlight: {
    fields: {
      content: {},
    },
  },
});
console.log(response);
GET /_search
{
  "query": {
    "match": { "content": "kimchy" }
  },
  "highlight": {
    "fields": {
      "content": {}
    }
  }
}

Elasticsearch 支持三种高亮器:unifiedplainfvh(快速向量高亮器)。您可以为每个字段指定要使用的高亮器 type

统一高亮器

编辑

unified 高亮器使用 Lucene Unified 高亮器。此高亮器将文本分解为句子,并使用 BM25 算法对各个句子进行评分,就好像它们是语料库中的文档一样。它还支持准确的短语和多词(模糊、前缀、正则表达式)高亮显示。unified 高亮器可以将多个字段的匹配项组合到一个结果中(请参阅 matched_fields)。这是默认的高亮器。

普通高亮器

编辑

plain 高亮器使用标准的 Lucene 高亮器。它尝试反映查询匹配逻辑,包括理解单词的重要性以及短语查询中的任何单词定位标准。

plain 高亮器最适合高亮显示单个字段中的简单查询匹配。为了准确反映查询逻辑,它会创建一个小型内存索引,并通过 Lucene 的查询执行计划器重新运行原始查询条件,以获取当前文档的低级匹配信息。这对需要高亮显示的每个字段和每个文档重复执行。如果您想在高亮显示具有复杂查询的许多文档中的许多字段,我们建议在 postingsterm_vector 字段上使用 unified 高亮器。

快速向量高亮器

编辑

fvh 高亮器使用 Lucene 快速向量高亮器。此高亮器可用于映射中将 term_vector 设置为 with_positions_offsets 的字段。快速向量高亮器

  • 可以使用 boundary_scanner 进行自定义。
  • 需要将 term_vector 设置为 with_positions_offsets,这会增加索引的大小
  • 可以将多个字段的匹配项组合到一个结果中。请参阅 matched_fields
  • 可以为不同位置的匹配项分配不同的权重,从而允许在突出显示短语匹配时,将短语匹配项排序在术语匹配项之上,从而提高短语匹配项的 Boosting Query

fvh 高亮器不支持跨度查询。如果您需要支持跨度查询,请尝试其他高亮器,例如 unified 高亮器。

偏移量策略

编辑

要从查询的术语创建有意义的搜索片段,高亮器需要知道原始文本中每个单词的起始和结束字符偏移量。这些偏移量可以从以下位置获得

  • 倒排列表。如果在映射中将 index_options 设置为 offsets,则 unified 高亮器会使用此信息来高亮显示文档,而无需重新分析文本。它直接在倒排列表上重新运行原始查询,并从索引中提取匹配的偏移量,从而将集合限制为高亮显示的文档。如果您有大型字段,这很重要,因为它不需要重新分析要高亮显示的文本。它还比使用 term_vectors 需要更少的磁盘空间。
  • 词向量。如果通过在映射中将 term_vector 设置为 with_positions_offsets 来提供 term_vector 信息,则 unified 高亮器会自动使用 term_vector 来高亮显示该字段。它速度很快,尤其是对于大型字段(> 1MB)和高亮显示多词查询(如 prefixwildcard),因为它可以使用每个文档的术语字典。fvh 高亮器始终使用词向量。
  • 普通高亮显示。当没有其他选择时,unified 使用此模式。它会创建一个小型内存索引,并通过 Lucene 的查询执行计划器重新运行原始查询条件,以获取当前文档的低级匹配信息。这对需要高亮显示的每个字段和每个文档重复执行。plain 高亮器始终使用普通高亮显示。

对于大型文本,普通高亮显示可能需要大量时间和内存。为了防止这种情况,将被分析的最大文本字符数限制为 1000000。可以使用索引设置 index.highlight.max_analyzed_offset 为特定索引更改此默认限制。

高亮显示设置

编辑

高亮显示设置可以在全局级别设置,并在字段级别覆盖。

boundary_chars
一个字符串,其中包含每个边界字符。默认为 .,!? \t\n
boundary_max_scan
扫描边界字符的距离。默认为 20
boundary_scanner

指定如何分隔高亮显示的片段:charssentenceword。仅对 unifiedfvh 高亮器有效。对于 unified 高亮器,默认为 sentence。对于 fvh 高亮器,默认为 chars

chars
使用 boundary_chars 指定的字符作为高亮显示边界。boundary_max_scan 设置控制扫描边界字符的距离。仅对 fvh 高亮器有效。
sentence

在下一个句子边界处分隔高亮显示的片段,该边界由 Java 的 BreakIterator 确定。您可以使用 boundary_scanner_locale 指定要使用的区域设置。

当与 unified 高亮器一起使用时,sentence 扫描程序会在 fragment_size 旁边的第一个单词边界处拆分大于 fragment_size 的句子。您可以将 fragment_size 设置为 0,以永远不拆分任何句子。

word
在下一个单词边界处分隔高亮显示的片段,该边界由 Java 的 BreakIterator 确定。您可以使用 boundary_scanner_locale 指定要使用的区域设置。
boundary_scanner_locale
控制用于搜索句子和单词边界的区域设置。此参数采用语言标记的形式,例如 "en-US""fr-FR""ja-JP"。有关详细信息,请参阅 区域设置语言标记 文档。默认值为 Locale.ROOT
encoder
指示是否应对片段进行 HTML 编码:default(不编码)或 html(HTML 转义片段文本,然后插入高亮显示标记)
fields

指定要检索高亮显示的字段。您可以使用通配符来指定字段。例如,您可以指定 comment_* 以获取所有以 comment_ 开头的 textmatch_only_textkeyword 字段的高亮显示。

当您使用通配符时,只会高亮显示文本、match_only_text 和关键字字段。如果您使用自定义映射器并仍想在高亮显示某个字段,则必须显式指定该字段名称。

fragmenter
指定如何在突出显示片段中分割文本:simplespan。仅对 plain 高亮器有效。默认为 span
force_source

已弃用;此参数无效

simple
将文本分成大小相同的片段。
span
将文本分成大小相同的片段,但会尽量避免在高亮显示的术语之间分割文本。当您查询短语时,这很有用。默认值。
fragment_offset
控制要开始高亮显示的边距。仅在使用 fvh 高亮器时有效。
fragment_size
高亮显示片段的大小,以字符为单位。默认为 100。
highlight_query

高亮显示与搜索查询不同的查询的匹配项。如果您使用重打分查询,则这尤其有用,因为默认情况下,高亮显示不会考虑这些查询。

Elasticsearch 不会验证 highlight_query 是否以任何方式包含搜索查询,因此可以定义它,使合法的查询结果不会被高亮显示。通常,您应该将搜索查询作为 highlight_query 的一部分包含在内。

matched_fields
合并多个字段的匹配项以高亮显示单个字段。这对于以不同方式分析相同字符串的多字段最为直观。对 unifiedfvh 高亮器有效,但此选项的行为对于每个高亮器都不同。

对于 unified 高亮器

  • matched_fields 数组应包含您想要高亮显示的原始字段。原始字段将自动添加到 matched_fields,并且在突出显示时无法排除其匹配项。
  • matched_fields 和原始字段可以使用不同的策略进行索引(带有或不带有 offsets,带有或不带有 term_vectors)。
  • 仅加载组合匹配项的原始字段,因此只有该字段才能从将 store 设置为 yes 中受益

对于 fvh 高亮器

  • matched_fields 数组可以包含也可以不包含原始字段,具体取决于您的需求。如果要在高亮显示中包含原始字段的匹配项,请将其添加到 matched_fields 数组中。
  • 所有 matched_fields 都必须将 term_vector 设置为 with_positions_offsets
  • 仅加载组合匹配项的原始字段,因此只有该字段才能从将 store 设置为 yes 中受益。

    no_match_size
    如果没有要高亮显示的匹配片段,则要从字段开头返回的文本量。默认为 0(不返回任何内容)。
    number_of_fragments
    要返回的最大片段数。如果将片段数设置为 0,则不会返回任何片段。而是高亮显示并返回整个字段内容。当您需要高亮显示诸如标题或地址之类的短文本,但不需要片段化时,这会很方便。如果 number_of_fragments 为 0,则忽略 fragment_size。默认为 5。
    order
    当设置为 score 时,按分数对高亮显示的片段进行排序。默认情况下,片段将按照它们在字段中出现的顺序输出(order: none)。将此选项设置为 score 将首先输出最相关的片段。每个高亮器都应用其自身的逻辑来计算相关性分数。有关不同高亮器如何查找最佳片段的更多详细信息,请参阅文档 高亮器内部工作原理
    phrase_limit
    控制文档中考虑的匹配短语的数量。防止 fvh 高亮器分析过多短语并消耗过多内存。当使用 matched_fields 时,将考虑每个匹配字段的 phrase_limit 个短语。提高限制会增加查询时间并消耗更多内存。仅 fvh 高亮器支持。默认为 256。
    pre_tags
    post_tags 结合使用,以定义用于高亮显示文本的 HTML 标签。默认情况下,高亮显示的文本包裹在 <em></em> 标签中。指定为字符串数组。
    post_tags
    pre_tags 结合使用,以定义用于高亮显示文本的 HTML 标签。默认情况下,高亮显示的文本包裹在 <em></em> 标签中。指定为字符串数组。
    require_field_match
    默认情况下,只会高亮显示包含查询匹配项的字段。将 require_field_match 设置为 false 可高亮显示所有字段。默认为 true
max_analyzed_offset
默认情况下,高亮显示请求分析的最大字符数受 index.highlight.max_analyzed_offset 设置中定义的值限制,并且当字符数超过此限制时,将返回错误。如果此设置设置为非负值,则高亮显示将在定义的上限处停止,并且不处理其余文本,因此不会高亮显示且不会返回错误。max_analyzed_offset 查询设置不会覆盖 index.highlight.max_analyzed_offset,当其设置为低于查询设置的值时,该设置优先。
tags_schema

设置为 styled 以使用内置的标签模式。styled 模式定义以下 pre_tags,并将 post_tags 定义为 </em>

<em class="hlt1">, <em class="hlt2">, <em class="hlt3">,
<em class="hlt4">, <em class="hlt5">, <em class="hlt6">,
<em class="hlt7">, <em class="hlt8">, <em class="hlt9">,
<em class="hlt10">
type
要使用的高亮器:unifiedplainfvh。默认为 unified

高亮显示示例

编辑

覆盖全局设置

编辑

您可以全局指定高亮器设置,并有选择地覆盖单个字段的设置。

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "number_of_fragments": 3,
        "fragment_size": 150,
        "fields": {
            "body": {
                "pre_tags": [
                    "<em>"
                ],
                "post_tags": [
                    "</em>"
                ]
            },
            "blog.title": {
                "number_of_fragments": 0
            },
            "blog.author": {
                "number_of_fragments": 0
            },
            "blog.comment": {
                "number_of_fragments": 5,
                "order": "score"
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      number_of_fragments: 3,
      fragment_size: 150,
      fields: {
        body: {
          pre_tags: [
            '<em>'
          ],
          post_tags: [
            '</em>'
          ]
        },
        'blog.title' => {
          number_of_fragments: 0
        },
        'blog.author' => {
          number_of_fragments: 0
        },
        'blog.comment' => {
          number_of_fragments: 5,
          order: 'score'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    number_of_fragments: 3,
    fragment_size: 150,
    fields: {
      body: {
        pre_tags: ["<em>"],
        post_tags: ["</em>"],
      },
      "blog.title": {
        number_of_fragments: 0,
      },
      "blog.author": {
        number_of_fragments: 0,
      },
      "blog.comment": {
        number_of_fragments: 5,
        order: "score",
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "number_of_fragments" : 3,
    "fragment_size" : 150,
    "fields" : {
      "body" : { "pre_tags" : ["<em>"], "post_tags" : ["</em>"] },
      "blog.title" : { "number_of_fragments" : 0 },
      "blog.author" : { "number_of_fragments" : 0 },
      "blog.comment" : { "number_of_fragments" : 5, "order" : "score" }
    }
  }
}

指定高亮显示查询

编辑

您可以指定一个 highlight_query,以便在突出显示时考虑其他信息。例如,以下查询在 highlight_query 中同时包含搜索查询和重新评分查询。如果没有 highlight_query,则高亮显示只会考虑搜索查询。

resp = client.search(
    query={
        "match": {
            "comment": {
                "query": "foo bar"
            }
        }
    },
    rescore={
        "window_size": 50,
        "query": {
            "rescore_query": {
                "match_phrase": {
                    "comment": {
                        "query": "foo bar",
                        "slop": 1
                    }
                }
            },
            "rescore_query_weight": 10
        }
    },
    source=False,
    highlight={
        "order": "score",
        "fields": {
            "comment": {
                "fragment_size": 150,
                "number_of_fragments": 3,
                "highlight_query": {
                    "bool": {
                        "must": {
                            "match": {
                                "comment": {
                                    "query": "foo bar"
                                }
                            }
                        },
                        "should": {
                            "match_phrase": {
                                "comment": {
                                    "query": "foo bar",
                                    "slop": 1,
                                    "boost": 10
                                }
                            }
                        },
                        "minimum_should_match": 0
                    }
                }
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        comment: {
          query: 'foo bar'
        }
      }
    },
    rescore: {
      window_size: 50,
      query: {
        rescore_query: {
          match_phrase: {
            comment: {
              query: 'foo bar',
              slop: 1
            }
          }
        },
        rescore_query_weight: 10
      }
    },
    _source: false,
    highlight: {
      order: 'score',
      fields: {
        comment: {
          fragment_size: 150,
          number_of_fragments: 3,
          highlight_query: {
            bool: {
              must: {
                match: {
                  comment: {
                    query: 'foo bar'
                  }
                }
              },
              should: {
                match_phrase: {
                  comment: {
                    query: 'foo bar',
                    slop: 1,
                    boost: 10
                  }
                }
              },
              minimum_should_match: 0
            }
          }
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      comment: {
        query: "foo bar",
      },
    },
  },
  rescore: {
    window_size: 50,
    query: {
      rescore_query: {
        match_phrase: {
          comment: {
            query: "foo bar",
            slop: 1,
          },
        },
      },
      rescore_query_weight: 10,
    },
  },
  _source: false,
  highlight: {
    order: "score",
    fields: {
      comment: {
        fragment_size: 150,
        number_of_fragments: 3,
        highlight_query: {
          bool: {
            must: {
              match: {
                comment: {
                  query: "foo bar",
                },
              },
            },
            should: {
              match_phrase: {
                comment: {
                  query: "foo bar",
                  slop: 1,
                  boost: 10,
                },
              },
            },
            minimum_should_match: 0,
          },
        },
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query": {
    "match": {
      "comment": {
        "query": "foo bar"
      }
    }
  },
  "rescore": {
    "window_size": 50,
    "query": {
      "rescore_query": {
        "match_phrase": {
          "comment": {
            "query": "foo bar",
            "slop": 1
          }
        }
      },
      "rescore_query_weight": 10
    }
  },
  "_source": false,
  "highlight": {
    "order": "score",
    "fields": {
      "comment": {
        "fragment_size": 150,
        "number_of_fragments": 3,
        "highlight_query": {
          "bool": {
            "must": {
              "match": {
                "comment": {
                  "query": "foo bar"
                }
              }
            },
            "should": {
              "match_phrase": {
                "comment": {
                  "query": "foo bar",
                  "slop": 1,
                  "boost": 10.0
                }
              }
            },
            "minimum_should_match": 0
          }
        }
      }
    }
  }
}

设置高亮器类型

编辑

type 字段允许强制使用特定的高亮器类型。允许的值为:unifiedplainfvh。以下是一个强制使用纯文本高亮器的示例

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "fields": {
            "comment": {
                "type": "plain"
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      fields: {
        comment: {
          type: 'plain'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    fields: {
      comment: {
        type: "plain",
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query": {
    "match": { "user.id": "kimchy" }
  },
  "highlight": {
    "fields": {
      "comment": { "type": "plain" }
    }
  }
}

配置高亮显示标签

编辑

默认情况下,高亮显示会将高亮显示的文本包裹在 <em></em> 中。可以通过设置 pre_tagspost_tags 来控制,例如

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "pre_tags": [
            "<tag1>"
        ],
        "post_tags": [
            "</tag1>"
        ],
        "fields": {
            "body": {}
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      pre_tags: [
        '<tag1>'
      ],
      post_tags: [
        '</tag1>'
      ],
      fields: {
        body: {}
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    pre_tags: ["<tag1>"],
    post_tags: ["</tag1>"],
    fields: {
      body: {},
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "pre_tags" : ["<tag1>"],
    "post_tags" : ["</tag1>"],
    "fields" : {
      "body" : {}
    }
  }
}

当使用快速向量高亮器时,您可以指定其他标签,并且“重要性”是有序的。

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "pre_tags": [
            "<tag1>",
            "<tag2>"
        ],
        "post_tags": [
            "</tag1>",
            "</tag2>"
        ],
        "fields": {
            "body": {}
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      pre_tags: [
        '<tag1>',
        '<tag2>'
      ],
      post_tags: [
        '</tag1>',
        '</tag2>'
      ],
      fields: {
        body: {}
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    pre_tags: ["<tag1>", "<tag2>"],
    post_tags: ["</tag1>", "</tag2>"],
    fields: {
      body: {},
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "pre_tags" : ["<tag1>", "<tag2>"],
    "post_tags" : ["</tag1>", "</tag2>"],
    "fields" : {
      "body" : {}
    }
  }
}

您还可以使用内置的 styled 标签模式

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "tags_schema": "styled",
        "fields": {
            "comment": {}
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      tags_schema: 'styled',
      fields: {
        comment: {}
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    tags_schema: "styled",
    fields: {
      comment: {},
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "tags_schema" : "styled",
    "fields" : {
      "comment" : {}
    }
  }
}

高亮显示所有字段

编辑

默认情况下,只会高亮显示包含查询匹配项的字段。将 require_field_match 设置为 false 可高亮显示所有字段。

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "require_field_match": False,
        "fields": {
            "body": {
                "pre_tags": [
                    "<em>"
                ],
                "post_tags": [
                    "</em>"
                ]
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      require_field_match: false,
      fields: {
        body: {
          pre_tags: [
            '<em>'
          ],
          post_tags: [
            '</em>'
          ]
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    require_field_match: false,
    fields: {
      body: {
        pre_tags: ["<em>"],
        post_tags: ["</em>"],
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "require_field_match": false,
    "fields": {
      "body" : { "pre_tags" : ["<em>"], "post_tags" : ["</em>"] }
    }
  }
}

合并多个字段的匹配项

编辑

unifiedfvh 高亮器支持此功能。

统一高亮器和快速向量高亮器可以合并多个字段的匹配项以高亮显示单个字段。这对于以不同方式分析相同字符串的多字段最为直观。

在以下示例中,commentstandard 分析器分析,而 comment.englishenglish 分析器分析。

resp = client.indices.create(
    index="index1",
    mappings={
        "properties": {
            "comment": {
                "type": "text",
                "analyzer": "standard",
                "fields": {
                    "english": {
                        "type": "text",
                        "analyzer": "english"
                    }
                }
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "index1",
  mappings: {
    properties: {
      comment: {
        type: "text",
        analyzer: "standard",
        fields: {
          english: {
            type: "text",
            analyzer: "english",
          },
        },
      },
    },
  },
});
console.log(response);
PUT index1
{
  "mappings": {
    "properties": {
      "comment": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "english": {
            "type": "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}
resp = client.bulk(
    index="index1",
    refresh=True,
    operations=[
        {
            "index": {
                "_id": "doc1"
            }
        },
        {
            "comment": "run with scissors"
        },
        {
            "index": {
                "_id": "doc2"
            }
        },
        {
            "comment": "running with scissors"
        }
    ],
)
print(resp)
const response = await client.bulk({
  index: "index1",
  refresh: "true",
  operations: [
    {
      index: {
        _id: "doc1",
      },
    },
    {
      comment: "run with scissors",
    },
    {
      index: {
        _id: "doc2",
      },
    },
    {
      comment: "running with scissors",
    },
  ],
});
console.log(response);
PUT index1/_bulk?refresh=true
{"index": {"_id": "doc1" }}
{"comment": "run with scissors"}
{ "index" : {"_id": "doc2"} }
{"comment": "running with scissors"}
resp = client.search(
    index="index1",
    query={
        "query_string": {
            "query": "running with scissors",
            "fields": [
                "comment",
                "comment.english"
            ]
        }
    },
    highlight={
        "order": "score",
        "fields": {
            "comment": {}
        }
    },
)
print(resp)
const response = await client.search({
  index: "index1",
  query: {
    query_string: {
      query: "running with scissors",
      fields: ["comment", "comment.english"],
    },
  },
  highlight: {
    order: "score",
    fields: {
      comment: {},
    },
  },
});
console.log(response);
GET index1/_search
{
  "query": {
    "query_string": {
      "query": "running with scissors",
      "fields": ["comment", "comment.english"]
    }
  },
  "highlight": {
    "order": "score",
    "fields": {
      "comment": {}
    }
  }
}

上面的请求同时匹配“run with scissors”和“running with scissors”,并且会高亮显示“running”和“scissors”,但不会高亮显示“run”。如果两个短语都出现在大型文档中,那么“running with scissors”在片段列表中会排在“run with scissors”之上,因为该片段中有更多的匹配项。

{
  ...
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score": 1.0577903,
    "hits" : [
      {
        "_index" : "index1",
        "_id" : "doc2",
        "_score" : 1.0577903,
        "_source" : {
          "comment" : "running with scissors"
        },
        "highlight" : {
          "comment" : [
            "<em>running</em> <em>with</em> <em>scissors</em>"
          ]
        }
      },
      {
        "_index" : "index1",
        "_id" : "doc1",
        "_score" : 0.36464313,
        "_source" : {
          "comment" : "run with scissors"
        },
        "highlight" : {
          "comment" : [
            "run <em>with</em> <em>scissors</em>"
          ]
        }
      }
    ]
  }
}

以下请求同时高亮显示“run”以及“running”和“scissors”,因为 matched_fields 参数指示,对于高亮显示,我们需要将来自 comment.english 字段的匹配项与来自原始 comment 字段的匹配项合并。

resp = client.search(
    index="index1",
    query={
        "query_string": {
            "query": "running with scissors",
            "fields": [
                "comment",
                "comment.english"
            ]
        }
    },
    highlight={
        "order": "score",
        "fields": {
            "comment": {
                "matched_fields": [
                    "comment.english"
                ]
            }
        }
    },
)
print(resp)
const response = await client.search({
  index: "index1",
  query: {
    query_string: {
      query: "running with scissors",
      fields: ["comment", "comment.english"],
    },
  },
  highlight: {
    order: "score",
    fields: {
      comment: {
        matched_fields: ["comment.english"],
      },
    },
  },
});
console.log(response);
GET index1/_search
{
  "query": {
    "query_string": {
      "query": "running with scissors",
      "fields": ["comment", "comment.english"]
    }
  },
  "highlight": {
    "order": "score",
    "fields": {
      "comment": {
        "matched_fields": ["comment.english"]
      }
    }
  }
}
{
  ...
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score": 1.0577903,
    "hits" : [
      {
        "_index" : "index1",
        "_id" : "doc2",
        "_score" : 1.0577903,
        "_source" : {
          "comment" : "running with scissors"
        },
        "highlight" : {
          "comment" : [
            "<em>running</em> <em>with</em> <em>scissors</em>"
          ]
        }
      },
      {
        "_index" : "index1",
        "_id" : "doc1",
        "_score" : 0.36464313,
        "_source" : {
          "comment" : "run with scissors"
        },
        "highlight" : {
          "comment" : [
            "<em>run</em> <em>with</em> <em>scissors</em>"
          ]
        }
      }
    ]
  }
}

显式排序高亮显示的字段

编辑

Elasticsearch 按照发送的顺序高亮显示字段,但根据 JSON 规范,对象是无序的。如果您需要显式指定高亮显示字段的顺序,请将 fields 指定为数组

resp = client.search(
    highlight={
        "fields": [
            {
                "title": {}
            },
            {
                "text": {}
            }
        ]
    },
)
print(resp)
response = client.search(
  body: {
    highlight: {
      fields: [
        {
          title: {}
        },
        {
          text: {}
        }
      ]
    }
  }
)
puts response
const response = await client.search({
  highlight: {
    fields: [
      {
        title: {},
      },
      {
        text: {},
      },
    ],
  },
});
console.log(response);
GET /_search
{
  "highlight": {
    "fields": [
      { "title": {} },
      { "text": {} }
    ]
  }
}

Elasticsearch 中内置的任何高亮器都不关心高亮显示字段的顺序,但插件可能会关心。

控制高亮显示的片段

编辑

每个高亮显示的字段都可以控制高亮显示片段的大小(以字符为单位,默认为 100),以及要返回的最大片段数(默认为 5)。例如

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "fields": {
            "comment": {
                "fragment_size": 150,
                "number_of_fragments": 3
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      fields: {
        comment: {
          fragment_size: 150,
          number_of_fragments: 3
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    fields: {
      comment: {
        fragment_size: 150,
        number_of_fragments: 3,
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "fields" : {
      "comment" : {"fragment_size" : 150, "number_of_fragments" : 3}
    }
  }
}

除此之外,还可以指定需要按分数对高亮显示的片段进行排序

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "order": "score",
        "fields": {
            "comment": {
                "fragment_size": 150,
                "number_of_fragments": 3
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      order: 'score',
      fields: {
        comment: {
          fragment_size: 150,
          number_of_fragments: 3
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    order: "score",
    fields: {
      comment: {
        fragment_size: 150,
        number_of_fragments: 3,
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "order" : "score",
    "fields" : {
      "comment" : {"fragment_size" : 150, "number_of_fragments" : 3}
    }
  }
}

如果将 number_of_fragments 值设置为 0,则不会生成任何片段,而是返回整个字段内容,当然它是高亮显示的。如果需要高亮显示短文本(如文档标题或地址),但不需要分段,则此功能非常方便。请注意,在这种情况下,fragment_size 将被忽略。

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "fields": {
            "body": {},
            "blog.title": {
                "number_of_fragments": 0
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      fields: {
        body: {},
        'blog.title' => {
          number_of_fragments: 0
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    fields: {
      body: {},
      "blog.title": {
        number_of_fragments: 0,
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "fields" : {
      "body" : {},
      "blog.title" : {"number_of_fragments" : 0}
    }
  }
}

当使用 fvh 时,可以使用 fragment_offset 参数来控制高亮显示的起始边距。

如果不存在匹配的片段进行高亮显示,默认情况下不会返回任何内容。相反,我们可以通过设置 no_match_size(默认为 0)为想要返回的文本长度,从字段开头返回一段文本。实际长度可能比指定的长度短或长,因为它会尝试在单词边界处断开。

resp = client.search(
    query={
        "match": {
            "user.id": "kimchy"
        }
    },
    highlight={
        "fields": {
            "comment": {
                "fragment_size": 150,
                "number_of_fragments": 3,
                "no_match_size": 150
            }
        }
    },
)
print(resp)
response = client.search(
  body: {
    query: {
      match: {
        'user.id' => 'kimchy'
      }
    },
    highlight: {
      fields: {
        comment: {
          fragment_size: 150,
          number_of_fragments: 3,
          no_match_size: 150
        }
      }
    }
  }
)
puts response
const response = await client.search({
  query: {
    match: {
      "user.id": "kimchy",
    },
  },
  highlight: {
    fields: {
      comment: {
        fragment_size: 150,
        number_of_fragments: 3,
        no_match_size: 150,
      },
    },
  },
});
console.log(response);
GET /_search
{
  "query": {
    "match": { "user.id": "kimchy" }
  },
  "highlight": {
    "fields": {
      "comment": {
        "fragment_size": 150,
        "number_of_fragments": 3,
        "no_match_size": 150
      }
    }
  }
}

使用倒排列表进行高亮显示

编辑

以下示例展示了如何在索引映射中设置 comment 字段,以便使用倒排列表进行高亮显示

resp = client.indices.create(
    index="example",
    mappings={
        "properties": {
            "comment": {
                "type": "text",
                "index_options": "offsets"
            }
        }
    },
)
print(resp)
response = client.indices.create(
  index: 'example',
  body: {
    mappings: {
      properties: {
        comment: {
          type: 'text',
          index_options: 'offsets'
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "example",
  mappings: {
    properties: {
      comment: {
        type: "text",
        index_options: "offsets",
      },
    },
  },
});
console.log(response);
PUT /example
{
  "mappings": {
    "properties": {
      "comment" : {
        "type": "text",
        "index_options" : "offsets"
      }
    }
  }
}

以下示例展示了如何设置 comment 字段,以便使用 term_vectors 进行高亮显示(这将导致索引更大)

resp = client.indices.create(
    index="example",
    mappings={
        "properties": {
            "comment": {
                "type": "text",
                "term_vector": "with_positions_offsets"
            }
        }
    },
)
print(resp)
response = client.indices.create(
  index: 'example',
  body: {
    mappings: {
      properties: {
        comment: {
          type: 'text',
          term_vector: 'with_positions_offsets'
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "example",
  mappings: {
    properties: {
      comment: {
        type: "text",
        term_vector: "with_positions_offsets",
      },
    },
  },
});
console.log(response);
PUT /example
{
  "mappings": {
    "properties": {
      "comment" : {
        "type": "text",
        "term_vector" : "with_positions_offsets"
      }
    }
  }
}

为 plain 高亮器指定分段器

编辑

当使用 plain 高亮器时,您可以在 simplespan 分段器之间选择

resp = client.search(
    index="my-index-000001",
    query={
        "match_phrase": {
            "message": "number 1"
        }
    },
    highlight={
        "fields": {
            "message": {
                "type": "plain",
                "fragment_size": 15,
                "number_of_fragments": 3,
                "fragmenter": "simple"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'my-index-000001',
  body: {
    query: {
      match_phrase: {
        message: 'number 1'
      }
    },
    highlight: {
      fields: {
        message: {
          type: 'plain',
          fragment_size: 15,
          number_of_fragments: 3,
          fragmenter: 'simple'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "my-index-000001",
  query: {
    match_phrase: {
      message: "number 1",
    },
  },
  highlight: {
    fields: {
      message: {
        type: "plain",
        fragment_size: 15,
        number_of_fragments: 3,
        fragmenter: "simple",
      },
    },
  },
});
console.log(response);
GET my-index-000001/_search
{
  "query": {
    "match_phrase": { "message": "number 1" }
  },
  "highlight": {
    "fields": {
      "message": {
        "type": "plain",
        "fragment_size": 15,
        "number_of_fragments": 3,
        "fragmenter": "simple"
      }
    }
  }
}

响应

{
  ...
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.6011951,
    "hits": [
      {
        "_index": "my-index-000001",
        "_id": "1",
        "_score": 1.6011951,
        "_source": {
          "message": "some message with the number 1",
          "context": "bar"
        },
        "highlight": {
          "message": [
            " with the <em>number</em>",
            " <em>1</em>"
          ]
        }
      }
    ]
  }
}
resp = client.search(
    index="my-index-000001",
    query={
        "match_phrase": {
            "message": "number 1"
        }
    },
    highlight={
        "fields": {
            "message": {
                "type": "plain",
                "fragment_size": 15,
                "number_of_fragments": 3,
                "fragmenter": "span"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'my-index-000001',
  body: {
    query: {
      match_phrase: {
        message: 'number 1'
      }
    },
    highlight: {
      fields: {
        message: {
          type: 'plain',
          fragment_size: 15,
          number_of_fragments: 3,
          fragmenter: 'span'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "my-index-000001",
  query: {
    match_phrase: {
      message: "number 1",
    },
  },
  highlight: {
    fields: {
      message: {
        type: "plain",
        fragment_size: 15,
        number_of_fragments: 3,
        fragmenter: "span",
      },
    },
  },
});
console.log(response);
GET my-index-000001/_search
{
  "query": {
    "match_phrase": { "message": "number 1" }
  },
  "highlight": {
    "fields": {
      "message": {
        "type": "plain",
        "fragment_size": 15,
        "number_of_fragments": 3,
        "fragmenter": "span"
      }
    }
  }
}

响应

{
  ...
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.6011951,
    "hits": [
      {
        "_index": "my-index-000001",
        "_id": "1",
        "_score": 1.6011951,
        "_source": {
          "message": "some message with the number 1",
          "context": "bar"
        },
        "highlight": {
          "message": [
            " with the <em>number</em> <em>1</em>"
          ]
        }
      }
    ]
  }
}

如果将 number_of_fragments 选项设置为 0,则会使用 NullFragmenter,它不会对文本进行分段。这对于高亮显示文档或字段的全部内容非常有用。

高亮器内部工作原理

编辑

给定一个查询和一个文本(文档字段的内容),高亮器的目标是找到查询的最佳文本片段,并在找到的片段中高亮显示查询词。为此,高亮器需要解决几个问题

  • 如何将文本分成片段?
  • 如何在所有片段中找到最佳片段?
  • 如何在片段中高亮显示查询词?

如何将文本分成片段?

编辑

相关设置:fragment_size, fragmenter, 高亮器的 type, boundary_chars, boundary_max_scan, boundary_scanner, boundary_scanner_locale

Plain 高亮器首先使用给定的分析器分析文本,并从中创建一个令牌流。Plain 高亮器使用非常简单的算法将令牌流分成片段。它循环遍历令牌流中的术语,并且每次当前术语的 end_offset 超过 fragment_size 乘以已创建的片段数时,都会创建一个新的片段。使用 span 分段器会进行更多计算,以避免在高亮显示的术语之间断开文本。但是总的来说,由于分割仅由 fragment_size 完成,因此某些片段可能会很奇怪,例如,以标点符号开头。

Unified 或 FVH 高亮器通过利用 Java 的 BreakIterator 来更好地将文本分成片段。这确保了只要 fragment_size 允许,片段就是一个有效的句子。

如何找到最佳片段?

编辑

相关设置:number_of_fragments

为了找到最佳、最相关的片段,高亮器需要根据给定的查询对每个片段进行评分。目标是仅对那些参与生成文档命中的术语进行评分。对于某些复杂的查询,这仍在进行中。

Plain 高亮器从当前令牌流创建一个内存索引,并通过 Lucene 的查询执行计划器重新运行原始查询条件,以访问当前文本的底层匹配信息。对于更复杂的查询,原始查询可以转换为跨度查询,因为跨度查询可以更准确地处理短语。然后,使用获得的底层匹配信息来对每个单独的片段进行评分。plain 高亮器的评分方法非常简单。每个片段都通过在该片段中找到的唯一查询词的数量进行评分。单个术语的分数等于其提升值,默认为 1。因此,默认情况下,包含一个唯一查询词的片段将获得 1 分;包含两个唯一查询词的片段将获得 2 分,依此类推。然后按分数对片段进行排序,因此得分最高的片段将首先输出。

FVH 不需要分析文本并构建内存索引,因为它使用预索引的文档词向量,并在其中找到与查询对应的术语。FVH 通过在该片段中找到的查询词的数量来对每个片段进行评分。与 plain 高亮器类似,单个术语的分数等于其提升值。与 plain 高亮器不同,所有查询词都会被计数,而不仅仅是唯一术语。

Unified 高亮器可以使用预索引的词向量或预索引的词偏移量(如果可用)。否则,类似于 Plain 高亮器,它必须从文本创建内存索引。Unified 高亮器使用 BM25 评分模型对片段进行评分。

如何在片段中高亮显示查询词?

编辑

相关设置:pre-tags, post-tags

目标是仅高亮显示那些参与生成文档命中的术语。对于某些复杂的布尔查询,这仍在进行中,因为高亮器不反映查询的布尔逻辑,而仅提取叶子(术语、短语、前缀等)查询。

Plain 高亮器给定令牌流和原始文本,重新构造原始文本,仅高亮显示来自令牌流中包含在先前步骤中的底层匹配信息结构中的术语。

FVH 和 unified 高亮器使用中间数据结构以某种原始形式表示片段,然后用实际文本填充它们。

高亮器使用 pre-tags, post-tags 来编码高亮显示的术语。

Unified 高亮器工作示例

编辑

让我们更详细地了解 unified 高亮器的工作原理。

首先,我们创建一个包含文本字段 content 的索引,该字段将使用 english 分析器进行索引,并且将在不带偏移量或词向量的情况下进行索引。

PUT test_index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "english"
      }
    }
  }
}

我们将以下文档放入索引中

PUT test_index/_doc/doc1
{
  "content" : "For you I'm only a fox like a hundred thousand other foxes. But if you tame me, we'll need each other. You'll be the only boy in the world for me. I'll be the only fox in the world for you."
}

我们运行以下带有高亮显示请求的查询

GET test_index/_search
{
  "query": {
    "match_phrase" : {"content" : "only fox"}
  },
  "highlight": {
    "type" : "unified",
    "number_of_fragments" : 3,
    "fields": {
      "content": {}
    }
  }
}

在找到 doc1 作为此查询的命中项后,此命中项将被传递给 unified 高亮器,以高亮显示文档的 content 字段。由于 content 字段未通过偏移量或词向量进行索引,因此将分析其原始字段值,并从与查询匹配的术语中构建内存索引

{"token":"onli","start_offset":12,"end_offset":16,"position":3},
{"token":"fox","start_offset":19,"end_offset":22,"position":5},
{"token":"fox","start_offset":53,"end_offset":58,"position":11},
{"token":"onli","start_offset":117,"end_offset":121,"position":24},
{"token":"onli","start_offset":159,"end_offset":163,"position":34},
{"token":"fox","start_offset":164,"end_offset":167,"position":35}

我们复杂的短语查询将转换为跨度查询:spanNear([text:onli, text:fox], 0, true),这意味着我们正在查找彼此之间距离为 0 且按给定顺序排列的术语 "onli" 和 "fox"。跨度查询将针对之前创建的内存索引运行,以找到以下匹配项

{"term":"onli", "start_offset":159, "end_offset":163},
{"term":"fox", "start_offset":164, "end_offset":167}

在我们的示例中,我们获得了一个匹配项,但可能存在多个匹配项。给定匹配项,unified 高亮器会将字段的文本分成所谓的“段落”。每个段落必须包含至少一个匹配项。unified 高亮器使用 Java 的 BreakIterator 确保每个段落都表示一个完整的句子,只要它不超过 fragment_size。对于我们的示例,我们得到一个段落,其中包含以下属性(此处仅显示属性的子集)

Passage:
    startOffset: 147
    endOffset: 189
    score: 3.7158387
    matchStarts: [159, 164]
    matchEnds: [163, 167]
    numMatches: 2

请注意段落如何具有分数,该分数是使用为段落改编的 BM25 评分公式计算的。如果有更多可用段落而不是用户请求的 number_of_fragments,则分数使我们可以选择得分最高的段落。如果用户请求,分数还允许我们按 order: "score" 对段落进行排序。

作为最后一步,unified 高亮器将从字段的文本中提取与每个段落对应的字符串

"I'll be the only fox in the world for you."

并将使用段落的 matchStartsmatchEnds 信息,使用标签 <em> 和 </em> 格式化此字符串中的所有匹配项

I'll be the <em>only</em> <em>fox</em> in the world for you.

这种格式化的字符串是返回给用户的最终高亮器结果。