Перейти к содержанию
Innovialabs

Анализаторы Elasticsearch для поиска на русском и узбекском

Практика4 мин

Коротко

Индексируйте название товара дважды — русским анализатором (стемминг, синонимы) и анализатором транслитерации кириллицы в узбекскую латиницу — и ищите по обоим полям с fuzziness, тогда «non», «нон» и «nonn» найдут хлеб.

Короткий ответ

В Узбекистане один и тот же пользователь может набрать «non» (узбекская латиница), «нон» (узбекская кириллица) или «хлеб» (русский), часто с опечаткой. Один анализатор с этим не справится. Рабочая схема:

  • русское поле с приведением к нижнему регистру, стоп-словами, русским стеммером и синонимами на этапе поиска;
  • подполе транслитерации, где любая кириллица превращается в узбекскую латиницу, а апострофы удаляются;
  • запрос, который ищет по обоим полям, с fuzziness на поле транслитерации.

Каждое правило проверяется через API _analyze до выхода в продакшен.

Шаг 1. Анализаторы и маппинг

PUT /products
{
  "settings": {
    "analysis": {
      "char_filter": {
        "strip_apostrophes": {
          "type": "pattern_replace",
          "pattern": "[‘’ʻʼ'`ъьЪЬ]",
          "replacement": ""
        },
        "ru_yo": { "type": "mapping", "mappings": ["ё => е", "Ё => Е"] },
        "cyr_to_lat": { "type": "mapping", "mappings_path": "analysis/uz_cyr_lat.txt" }
      },
      "filter": {
        "ru_stop": { "type": "stop", "stopwords": "_russian_" },
        "ru_stemmer": { "type": "stemmer", "language": "russian" },
        "product_synonyms": {
          "type": "synonym_graph",
          "synonyms": ["non, нон, хлеб, лепешка", "sharbat, шарбат, сок"]
        }
      },
      "analyzer": {
        "ru_index": {
          "tokenizer": "standard",
          "char_filter": ["ru_yo"],
          "filter": ["lowercase", "ru_stop", "ru_stemmer"]
        },
        "ru_search": {
          "tokenizer": "standard",
          "char_filter": ["ru_yo"],
          "filter": ["lowercase", "product_synonyms", "ru_stop", "ru_stemmer"]
        },
        "uz_translit": {
          "tokenizer": "standard",
          "char_filter": ["strip_apostrophes", "cyr_to_lat"],
          "filter": ["lowercase", "asciifolding"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "ru_index",
        "search_analyzer": "ru_search",
        "fields": {
          "translit": { "type": "text", "analyzer": "uz_translit" }
        }
      }
    }
  }
}

Файл config/analysis/uz_cyr_lat.txt лежит на каждом узле и содержит по одному правилу в строке — для строчных и заглавных букв:

ў => o
Ў => O
қ => q
ғ => g
ҳ => h
ш => sh
ч => ch
ё => yo
ю => yu
я => ya
х => x
ц => ts
н => n
о => o

…и так далее по всему алфавиту. Ключевые решения:

  • Сначала удаляются апострофы, поэтому «o‘zbek», «o'zbek», «ozbek» и «ўзбек» превращаются в ozbek. Правильный символ ‘ пользователи набирают редко.
  • Синонимы — только при поиске. synonym_graph ставится в поисковый анализатор, и изменение списка не требует переиндексации документов.
  • Синонимы стоят до стеммера, иначе «лепешка» уже обрезана и не совпадает с правилом.

Шаг 2. Проверяем всё через _analyze

POST /products/_analyze
{ "field": "name.translit", "text": "Нон Ўзбекча" }

Ожидаемые токены: non, ozbekcha. Теперь русская часть:

POST /products/_analyze
{ "analyzer": "ru_search", "text": "лепёшка" }

Вы должны увидеть основы слов «лепешка» и «хлеб», а также non и нон. Если чего-то нет — проблема в порядке фильтров или в нормализации ё. Учтите, что синонимы совпадают с точной формой слова, поэтому при необходимости добавляйте в правила множественное число и другие частые формы. Сохраните эти вызовы как небольшой набор тестов и запускайте после каждого изменения анализаторов.

Шаг 3. Запрос с исправлением опечаток

GET /products/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "name": { "query": "нонн" } } },
        { "match": { "name.translit": {
            "query": "нонн",
            "fuzziness": "AUTO",
            "prefix_length": 1
        } } }
      ]
    }
  }
}
  • fuzziness: AUTO не допускает правок в очень коротких словах, одну правку в средних и две в длинных, поэтому «nonn» и «nan» всё равно находят «non».
  • prefix_length: 1 оставляет первую букву точной: меньше ложных совпадений и быстрее запросы.
  • Fuzziness применяется только к полю транслитерации. В сочетании с многословными синонимами результаты становятся менее предсказуемыми.

Узбекские суффиксы

Встроенного узбекского стеммера в Elasticsearch нет, а узбекский — агглютинативный язык: «nonlar», «nonni», «nonning». Варианты от простого к сложному:

  • подполе с edge_ngram для поиска по префиксу и автодополнения;
  • фильтр pattern_replace, который отрезает короткий список частых суффиксов, — аккуратно, чтобы не повредить корни слов;
  • собственный словарь через stemmer_override для самых популярных названий товаров.

Частые ошибки

  • Встроенный анализатор russian для всего подряд: запросы латиницей никогда не встретятся с кириллическими названиями.
  • Забытые заглавные буквы в файле маппинга.
  • Fuzziness на артикулах и штрихкодах — для них нужно поле keyword и точное совпадение.
  • Изменение анализаторов индексации без переиндексации: старые документы хранят старые токены.

Частые вопросы

Не обязательно. Char filter типа mapping с собственной таблицей полностью предсказуем и соблюдает узбекские правила (х становится x, ш — sh). Преобразования ICU удобны для многих языков сразу, но их результат не всегда совпадает с узбекской орфографией.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.