Spec-Zone.ru › Elasticsearch 8
›Elasticsearch Guide [8.17] ›Поиск данных ›API поиска

Выделение

Выделители позволяют получить выделенные фрагменты из одного или нескольких полей в результатах поиска, чтобы показать пользователям, где совпадает запрос. При запросе выделения ответ содержит дополнительный элемент 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 поддерживает три выделителя: unified, plain и fvh (быстрый векторный выделитель). Вы можете указать желаемый выделитель type для каждого поля.

Унифицированный выделитель

Выделитель unified использует унифицированный выделитель Lucene. Этот выделитель разбивает текст на предложения и использует алгоритм BM25 для оценки отдельных предложений как документов в корпусе. Он также поддерживает точное выделение фраз и выделение по нескольким терминам (нечеткое, префикс, регулярное выражение). Выделитель unified может объединять совпадения из нескольких полей в один результат (см. matched_fields). Это стандартный выделитель.

Простой выделитель

Выделитель plain использует стандартный выделитель Lucene. Он пытается отразить логику совпадения запроса в терминах понимания важности слов и любых критериев позиционирования слов в запросах на фразы.

Выделитель plain лучше всего подходит для выделения совпадений простых запросов в одном поле. Чтобы точно отразить логику запроса, он создает крошечный индекс в памяти и повторно выполняет исходные критерии запроса через планировщик выполнения запросов Lucene, чтобы получить доступ к информации о совпадениях на низком уровне для текущего документа. Это повторяется для каждого поля и каждого документа, которые необходимо выделить. Если вы хотите выделить много полей во многих документах со сложными запросами, рекомендуется использовать выделитель unified на полях postings или term_vector.

Быстрый векторный выделитель

Выделитель fvh использует быстрый векторный выделитель Lucene. Этот выделитель может использоваться для полей с term_vector, установленным на with_positions_offsets в сопоставлении. Быстрый векторный выделитель:

  • Может быть настроен с помощью boundary_scanner.
  • Требует установки term_vector на with_positions_offsets, что увеличивает размер индекса
  • Может объединять совпадения из нескольких полей в один результат. См. matched_fields
  • Может назначать разные веса совпадениям в разных позициях, что позволяет, например, сортировать совпадения фраз выше совпадений терминов при выделении запроса Boosting, который повышает совпадения фраз по сравнению с совпадениями терминов

Выделитель fvh не поддерживает запросы span. Если вам нужна поддержка запросов span, попробуйте альтернативный выделитель, такой как выделитель unified.

Стратегия смещений

Чтобы создать осмысленные фрагменты поиска из запрошенных терминов, выделителю нужно знать начальное и конечное смещения символов каждого слова в исходном тексте. Эти смещения можно получить из:

  • Списка результатов. Если index_options установлено на offsets в сопоставлении, выделитель unified использует эту информацию для выделения документов без повторного анализа текста. Он повторно выполняет исходный запрос непосредственно на списке результатов и извлекает соответствующие смещения из индекса, ограничивая сбор выделенными документами. Это важно, если у вас большие поля, поскольку не требуется повторный анализ выделяемого текста. Это также требует меньше места на диске, чем использование term_vectors.
  • Векторов терминов. Если информация о векторах терминов предоставляется путем установки term_vector на term_vector в сопоставлении, выделитель unified автоматически использует term_vector для выделения поля. Он быстрый, особенно для больших полей (> 1MB) и для выделения запросов по нескольким терминам, таких как prefix или wildcard, потому что может получить доступ к словарю терминов для каждого документа. Выделитель fvh всегда использует векторы терминов.
  • Простое выделение. Этот режим используется выделителем unified, когда нет других вариантов. Он создает крошечный индекс в памяти и повторно выполняет исходные критерии запроса через планировщик выполнения запросов Lucene, чтобы получить доступ к информации о совпадениях на низком уровне для текущего документа. Это повторяется для каждого поля и каждого документа, которые необходимо выделить. Выделитель plain всегда использует простое выделение.

Простое выделение для больших текстов может потребовать значительного времени и памяти. Для защиты от этого максимальное количество символов текста, которые будут анализироваться, ограничено 1000000. Это значение по умолчанию можно изменить для конкретного индекса с помощью параметра индекса index.highlight.max_analyzed_offset.

Настройки выделения

Настройки выделения могут быть заданы на глобальном уровне и переопределены на уровне поля.

boundary_chars
Строка, содержащая каждый разделительный символ. По умолчанию .,!? \t\n.
boundary_max_scan
Насколько далеко просматривать разделительные символы. По умолчанию 20.
boundary_scanner

Указывает, как разбивать выделенные фрагменты: chars, sentence или word. Действует только для выделений unified и fvh. По умолчанию используется sentence для выделения unified. По умолчанию используется chars для выделения fvh.

chars
Используйте символы, указанные в boundary_chars, как границы выделения. Параметр boundary_max_scan управляет тем, как далеко искать символы границы. Действует только для выделения fvh.
sentence

Разбивать выделенные фрагменты по границам предложений, определяемым с помощью BreakIterator Java’s BreakIterator. Вы можете указать используемый язык с помощью boundary_scanner_locale.

При использовании с выделением unified, сканер sentence разбивает предложения, превышающие fragment_size, по границе первого слова, следующего за fragment_size. Вы можете установить fragment_size в 0, чтобы никогда не разбивать предложение.

word
Разбивать выделенные фрагменты по границам слов, определяемым с помощью BreakIterator Java’s BreakIterator. Вы можете указать используемый язык с помощью boundary_scanner_locale.
boundary_scanner_locale
Управляет языковым регионом, используемым для поиска границ предложений и слов. Этот параметр принимает тег языка, например, "en-US", "fr-FR", "ja-JP". Дополнительную информацию можно найти в документации Locale Language Tag. Значение по умолчанию — Locale.ROOT.
encoder
Указывает, должен ли фрагмент кодироваться в HTML: default (без кодирования) или html (HTML-экранировать текст фрагмента и затем вставить теги выделения).
fields

Указывает поля, для которых нужно получить выделения. Вы можете использовать шаблоны для указания полей. Например, вы можете указать comment_*, чтобы получить выделения для всех полей text, match_only_text и keyword, которые начинаются с comment_.

При использовании шаблонов выделения применяются только к полям типа text, match_only_text и keyword. Если вы используете пользовательский маппер и хотите выделить поле, вы должны явно указать имя этого поля.

fragmenter
Указывает, как текст должен быть разделен на фрагменты выделения: simple или span. Действует только для выделения plain. По умолчанию используется span.
force_source

устаревший; этот параметр не оказывает никакого влияния

simple
Разбивает текст на фрагменты одинаковой длины.
span
Разбивает текст на фрагменты одинаковой длины, но старается не разделять текст между выделенными терминами. Это полезно, когда вы ищете фразы. Значение по умолчанию.
fragment_offset
Управляет отступом, с которого необходимо начать выделение. Действует только при использовании выделения fvh.
fragment_size
Размер фрагмента выделения в символах. По умолчанию 100.
highlight_query

Выделить совпадения для запроса, отличного от запроса поиска. Это особенно полезно, если вы используете запрос rescore, так как они по умолчанию не учитываются выделением.

Elasticsearch не проверяет, что highlight_query содержит запрос поиска, поэтому возможно определить его так, что результаты поиска не будут выделены. Обычно вы должны включать запрос поиска в highlight_query.

matched_fields
Объединять совпадения по нескольким полям для выделения одного поля. Это наиболее удобно для многопольных структур, которые анализируют одну и ту же строку разными способами. Действительно для выделений unified и fvh`, но поведение этого параметра отличается для каждого выделения.

Для выделения 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 выведет наиболее релевантные фрагменты в первую очередь. Каждое выделение применяет свою логику для вычисления оценок релевантности. См. документ Как работают выделения в ES для получения более подробной информации о том, как различные выделения находят лучшие фрагменты.
    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
Используемое выделение: unified, plain или fvh. По умолчанию используется 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 позволяет принудительно установить определенный тип выделения. Допустимые значения: unified, plain и fvh. Следующий пример принудительно использует простой выделения:

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_tags и post_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>"] }
    }
  }
}

Объединить совпадения по нескольким полям

Поддерживается unified и fvh выделениями.

Объединённое и быстрое векторное выделение могут объединять совпадения по нескольким полям, чтобы выделить одно поле. Это наиболее интуитивно понятно для многопольных случаев, которые анализируют одну строку разными способами.

В следующих примерах comment анализируется с помощью анализатора standard, а comment.english — анализатором english.

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

В следующих примерах comment анализируется с помощью анализатора standard, а comment.english — анализатором english.

resp = client.indices.create(
    index="index2",
    mappings={
        "properties": {
            "comment": {
                "type": "text",
                "analyzer": "standard",
                "term_vector": "with_positions_offsets",
                "fields": {
                    "english": {
                        "type": "text",
                        "analyzer": "english",
                        "term_vector": "with_positions_offsets"
                    }
                }
            }
        }
    },
)
print(resp)
const response = await client.indices.create({
  index: "index2",
  mappings: {
    properties: {
      comment: {
        type: "text",
        analyzer: "standard",
        term_vector: "with_positions_offsets",
        fields: {
          english: {
            type: "text",
            analyzer: "english",
            term_vector: "with_positions_offsets",
          },
        },
      },
    },
  },
});
console.log(response);
PUT index2
{
  "mappings": {
    "properties": {
      "comment": {
        "type": "text",
        "analyzer": "standard",
        "term_vector": "with_positions_offsets",
        "fields": {
          "english": {
            "type": "text",
            "analyzer": "english",
            "term_vector": "with_positions_offsets"
          }
        }
      }
    }
  }
}
resp = client.bulk(
    index="index2",
    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: "index2",
  refresh: "true",
  operations: [
    {
      index: {
        _id: "doc1",
      },
    },
    {
      comment: "run with scissors",
    },
    {
      index: {
        _id: "doc2",
      },
    },
    {
      comment: "running with scissors",
    },
  ],
});
console.log(response);
PUT index2/_bulk?refresh=true
{"index": {"_id": "doc1" }}
{"comment": "run with scissors"}
{ "index" : {"_id": "doc2"} }
{"comment": "running with scissors"}
resp = client.search(
    index="index2",
    query={
        "query_string": {
            "query": "running with scissors",
            "fields": [
                "comment",
                "comment.english"
            ]
        }
    },
    highlight={
        "order": "score",
        "fields": {
            "comment": {
                "type": "fvh"
            }
        }
    },
)
print(resp)
const response = await client.search({
  index: "index2",
  query: {
    query_string: {
      query: "running with scissors",
      fields: ["comment", "comment.english"],
    },
  },
  highlight: {
    order: "score",
    fields: {
      comment: {
        type: "fvh",
      },
    },
  },
});
console.log(response);
GET index2/_search
{
  "query": {
    "query_string": {
      "query": "running with scissors",
      "fields": ["comment", "comment.english"]
    }
  },
  "highlight": {
    "order": "score",
    "fields": {
      "comment": {
        "type" : "fvh"
      }
    }
  }
}

Вышеупомянутый запрос соответствует как «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" : "index2",
        "_id" : "doc2",
        "_score" : 1.0577903,
        "_source" : {
          "comment" : "running with scissors"
        },
        "highlight" : {
          "comment" : [
            "<em>running</em> <em>with</em> <em>scissors</em>"
          ]
        }
      },
      {
        "_index" : "index2",
        "_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 и comment.english.

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

Нижеприведенный запрос не выделит «run» или «scissor», но показывает, что вполне допустимо не указывать поле, к которому объединяются совпадения (comment.english), в выводимых полях.

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

Использование matched_fields с ненулевым массивом приводит к небольшому накладным расходам, поэтому всегда предпочтительнее

    "highlight": {
        "fields": {
            "comment": {}
        }
    }

чем

    "highlight": {
        "fields": {
            "comment": {
                "matched_fields": ["comment"],
                "type" : "fvh"
            }
        }
    }

Технически также допустимо добавлять поля в matched_fields, которые не содержат ту же строку, что и поле, к которому объединяются совпадения. Результаты могут оказаться бессмысленными, и если одно из совпадений находится за пределами текста, то весь запрос завершится ошибкой.

Явное упорядочивание выделенных полей

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, вы можете выбрать между фрагментерами simple и span:

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.

Простой выделыватель начинает с анализа текста с использованием заданного анализатора и создания потока токенов. Простой выделыватель использует очень простой алгоритм для разбивки потока токенов на фрагменты. Он проходит по терминам в потоке токенов, и каждый раз, когда конечный смещение текущего термина превышает fragment_size, умноженное на количество созданных фрагментов, создается новый фрагмент. Небольшие вычисления выполняются с использованием фрагментировщика span для предотвращения разбиения текста между выделенными терминами. Но в целом, поскольку разбиение выполняется только с помощью fragment_size, некоторые фрагменты могут быть довольно странными, например, начинаться с знака препинания.

Выделение ключевых слов Unified или FVH выполняет лучшую работу по разбивке текста на фрагменты, используя BreakIterator Java. Это гарантирует, что фрагмент является полным предложением, если это допускает fragment_size.

Как найти лучшие фрагменты?

Релевантные настройки: number_of_fragments.

Чтобы найти лучшие, наиболее релевантные фрагменты, выделение ключевых слов должно оценить каждый фрагмент по отношению к заданному запросу. Цель состоит в оценке только тех терминов, которые участвовали в создании совпадения в документе. Для некоторых сложных запросов эта задача еще находится в стадии разработки.

Простой выделыватель создает индекс в оперативной памяти из текущего потока токенов и повторно запускает исходные критерии запроса через планировщик выполнения запросов Lucene, чтобы получить доступ к информации о совпадении низкого уровня для текущего текста. Для более сложных запросов исходный запрос может быть преобразован в запрос span, так как запросы span могут более точно обрабатывать фразы. Затем полученная информация о совпадении низкого уровня используется для оценки каждого отдельного фрагмента. Метод оценки простого выделывателя довольно прост. Каждый фрагмент оценивается по количеству уникальных терминов запроса, найденных в этом фрагменте. Оценка отдельного термина равна его весу, который по умолчанию равен 1. Таким образом, по умолчанию фрагмент, содержащий один уникальный термин запроса, получит оценку 1; фрагмент, содержащий два уникальных термина запроса, получит оценку 2 и так далее. Затем фрагменты сортируются по их оценкам, поэтому фрагменты с наивысшей оценкой выводятся первыми.

FVH не нуждается в анализе текста и создании индекса в оперативной памяти, так как использует предварительно индексированные векторы терминов документов и находит среди них термины, соответствующие запросу. FVH оценивает каждый фрагмент по количеству терминов запроса, найденных в этом фрагменте. Аналогично простому выделывателю, оценка отдельного термина равна его значению усиления. В отличие от простого выделывателя, учитываются все термины запроса, а не только уникальные.

Unified выделыватель может использовать предварительно индексированные векторы терминов или предварительно индексированные смещения терминов, если они доступны. В противном случае, подобно простому выделывателю, он должен создать индекс в оперативной памяти из текста. Unified выделыватель использует модель оценки BM25 для оценки фрагментов.

Как выделить термины запроса во фрагменте?

Релевантные настройки: pre-tags, post-tags.

Цель состоит в выделении только тех терминов, которые участвовали в формировании совпадения в документе. Для некоторых сложных булевых запросов эта задача все еще находится в стадии разработки, так как выделыватели не отражают булеву логику запроса и извлекают только листовые (термины, фразы, префиксы и т. д.) запросы.

Простой выделыватель, получив поток токенов и исходный текст, восстанавливает исходный текст, чтобы выделить только термины из потока токенов, которые содержатся в структуре информации о совпадении низкого уровня из предыдущего шага.

Выделыватели 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}

Наш сложный запрос фразы будет преобразован в запрос span: spanNear([text:onli, text:fox], 0, true), что означает, что мы ищем термины "onli:" и "fox" в пределах 0 расстояния друг от друга и в заданном порядке. Запрос span будет запущен против созданного ранее индекса в оперативной памяти, чтобы найти следующее совпадение:

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

В нашем примере мы получили одно совпадение, но может быть несколько совпадений. Учитывая совпадения, выделыватель Unified разбивает текст поля на так называемые «фрагменты». Каждый фрагмент должен содержать как минимум одно совпадение. Выделыватель Unified с использованием BreakIterator Java гарантирует, что каждый фрагмент представляет собой полное предложение, пока он не превышает 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."

и отформатирует с тегами <em> и </em> все совпадения в этой строке, используя информацию о matchStarts и matchEnds фрагмента:

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

Этот тип отформатированных строк является конечным результатом выделения ключевых слов, возвращаемым пользователю.

© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/highlighting.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API