Анатомия анализатора: char filters, токенизатор, token filters

Когда вы индексируете документ с text-полем, Elasticsearch не кладёт строку в индекс как есть. Сначала текст проходит через анализатор — конвейер из трёх последовательных стадий. На выходе получается список токенов: нормализованных фрагментов, по которым строится инвертированный индекс. Именно анализатор решает, найдёт ли поиск по слову «бегу» документ со словом «бежит» — или нет.

Понять устройство анализатора нужно до перехода к конкретным языковым настройкам — английскому, русскому, синонимам. Всё это варианты одного и того же механизма.

Конвейер анализа: три стадии

Анализатор всегда состоит из трёх частей:

1. Char filters — обрабатывают сырую строку символ за символом, ещё до разбивки на слова.

2. Tokenizer — разрезает строку на токены. Ровно один, обязателен.

3. Token filters — нормализуют и обогащают каждый токен. Их может быть несколько.

flowchart LR A([Сырой текст]) -->|строка| B[Char Filters] B -->|строка| C[Токенизатор] C -->|токены| D[Token Filters] D -->|токены| E([Инвертированный индекс]) style A fill:#eef2ff,stroke:#6366f1 style E fill:#d4edda,stroke:#28a745
flowchart LR
    A([Сырой текст]) -->|строка| B[Char Filters]
    B -->|строка| C[Токенизатор]
    C -->|токены| D[Token Filters]
    D -->|токены| E([Инвертированный индекс])
    style A fill:#eef2ff,stroke:#6366f1
    style E fill:#d4edda,stroke:#28a745
Конвейер анализатора: от сырого текста к токенам

Char filters и token filters необязательны — их может не быть вовсе. Токенизатор же один и только один: без него конвейер не работает.

Стадия 1: char filters

Char filters видят сырую строку до токенизации и могут изменить её любым образом. Три встроенных варианта:

ФильтрЧто делаетПример
html_stripУдаляет HTML-теги<b>Hello</b>Hello
mappingЗаменяет подстроки по словарю&and
pattern_replaceЗаменяет по регулярному выражению(\d+)-(\d+)$1 $2

Практичный сценарий: вы индексируете статьи из CMS, которые приходят с HTML-разметкой. Без html_strip токены вроде </p> или <strong> попадут в индекс и засорят результаты.

Check yourself
HTML-страница содержит строку `<h1>Quick Fox</h1>`. Вы применяете анализатор с char filter `html_strip` и токенизатором `standard` (плюс token filter `lowercase`). Какие токены окажутся в инвертированном индексе?

Стадия 2: tokenizer

Токенизатор получает строку (уже обработанную char filters) и разрезает её на токены. От его выбора зависит фундаментальная гранулярность поиска.

ТокенизаторПринципРезультат для «Hello, world!»
standardПо пробелам и пунктуации (Unicode)["Hello", "world"]
whitespaceТолько по пробелам["Hello,", "world!"]
keywordВся строка — один токен["Hello, world!"]
patternПо regex-разделителюзадаётся явно

standard подходит для большинства задач с европейскими языками — он удаляет пунктуацию и разбивает по словам. keyword применяется там, где строку нужно хранить и искать целиком: именно так работают keyword-поля по умолчанию.

Check yourself
Вы используете токенизатор `whitespace` для строки «Hello, world!». Сколько токенов получится и каких именно?

Стадия 3: token filters

Фильтры токенов работают с потоком токенов после токенизатора. Применяются последовательно — один за другим.

ФильтрЧто делает
lowercaseHellohello
stopУдаляет стоп-слова (the, a, «и», «в»)
stemmerСтемминг — обрезает до основы (runningrun)
synonymДобавляет синонимы (car = automobile)
asciifoldingЗаменяет диакритику (ée)

Порядок фильтров имеет значение. Если поставить stop перед lowercase, фильтр не распознает стоп-слово The (с заглавной буквы) — в словаре оно записано строчными. Правильный порядок: сначала lowercase, потом stop.

Встроенный standard-анализатор под микроскопом

Анализатор standard — тот, что Elasticsearch применяет к text-полям, если маппинг не задан явно. Он устроен так:

  • char filters: нет
  • tokenizer: standard
  • token filters: lowercase

Строка «Quick brown FOX jumps» превращается в: ["quick", "brown", "fox", "jumps"].

Важный момент: стоп-слова в standard-анализаторе по умолчанию не удаляются — словарь стоп-слов пуст. Чтобы они убирались, нужен языковой анализатор (english, russian) или кастомный. Об этом подробнее в статьях Анализ английского текста: стемминг и стоп-слова и Анализ русского текста: морфология, стемминг, Hunspell.

Index-анализ и Search-анализ

Анализ происходит дважды, и это ключевая идея:

1. При индексации — Elasticsearch анализирует текст документа и записывает токены в инвертированный индекс.

2. При поиске — строка из запроса match тоже проходит через анализатор, прежде чем сравниваться с индексом.

Это означает: оба прохода должны давать совместимые токены. Если при индексации running превратилось в run, то поисковая строка running тоже должна превратиться в run — иначе совпадения не будет.

По умолчанию Elasticsearch использует один и тот же анализатор на обоих этапах. Но в маппинге можно задать отдельный search_analyzer. Это нужно, например, для синонимов: их лучше применять только при поиске, чтобы не раздувать индекс. Подробнее — в статье Синонимы и кастомные анализаторы.

Check yourself
Анализатор настроен так, что слово `running` при индексации превращается в токен `run`. Пользователь вводит запрос `match` со словом `running`. Найдётся ли документ?

_analyze API: смотрим под капот

Прежде чем зафиксировать анализатор в маппинге, проверьте его поведение. Эндпоинт _analyze показывает ровно то, что попадёт в индекс.

Проверить встроенный анализатор:

POST /_analyze
{
  "analyzer": "standard",
  "text": "The Quick Brown Fox Jumps"
}

Ответ (упрощённо):

{
  "tokens": [
    { "token": "the",   "start_offset": 0,  "end_offset": 3,  "position": 0 },
    { "token": "quick", "start_offset": 4,  "end_offset": 9,  "position": 1 },
    { "token": "brown", "start_offset": 10, "end_offset": 15, "position": 2 },
    { "token": "fox",   "start_offset": 16, "end_offset": 19, "position": 3 },
    { "token": "jumps", "start_offset": 20, "end_offset": 25, "position": 4 }
  ]
}

Что возвращает API:

  • token — что попало в индекс
  • start_offset / end_offset — позиция в исходной строке (нужна для подсветки совпадений)
  • position — порядковый номер токена (важен для match_phrase)

Проверить анализатор конкретного индекса (если индекс catalog уже создан с кастомным анализатором):

POST /catalog/_analyze
{
  "analyzer": "my_analyzer",
  "text": "Бегущий по волнам"
}

Собрать конвейер на лету и отладить пошагово:

POST /_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase", "stop"],
  "text": "The Quick Brown Fox Jumps"
}

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

_analyze — только инструмент разработки. Он не меняет индекс и не выполняет поиск: просто показывает, как анализатор преобразует конкретный текст.

Итог

Анализатор — конвейер из трёх стадий: char filters обрабатывают сырую строку, токенизатор режет её на части, token filters нормализуют каждый токен. Анализ применяется и при индексации, и при поиске — обе стороны должны быть согласованы. Проверить любой анализатор — хоть встроенный, хоть кастомный — можно в любой момент через POST /_analyze.

Quick recall
Почему текст анализируется дважды — при индексации и при поиске?
Quick recall
Почему порядок token filters имеет значение? Приведите пример.
Quick recall
Анализатор состоит из трёх стадий. Какая стадия обязательна и в каком количестве?

См. также