Многоязычный поиск в Elasticsearch: от Hello до Donaudampfschifffahrtsgesellschaftskapitän
Привет, Хабр!
Когда ваш поиск начинает обслуживать пользователей, говорящих на разных языках, кажется, что начинается некий лингвистический хаос. Однако с Elasticsearch это не так уж сложно — если знаешь, как правильно настроить токенизацию, стемминг и аннотирование документов под каждый язык. Сегодня мы рассмотрим тему многоязычного поиска: разберём, как Elasticsearch понимает текст на разных языках, как обрабатывает сложные случаи вроде китайских иероглифов или немецких сложных слов, и как сделать так, чтобы результаты поиска были релевантны для каждого пользователя, независимо от его языка.
Токенизация текста
Токенизация — это процесс разбиения текста на минимальные значимые единицы, или токены. Обычно в английском тексте такими токенами будут слова, отделённые пробелами. В идеальном мире на этом всё бы закончилось, но мы живём в мире с китайскими иероглифами, сложносоставными немецкими словами и агглютинативными языками вроде финского. Короче, просто разрезать текст на пробелах — это не всегда решение.
Проблемы токенизации в разных языках
Английский: Кажется, всё просто, но тут всплывают вопросы: что делать с пунктуацией? Как обработать сокращения? И что делать со словами вроде «isn’t»?
Китайский: Здесь токенизация становится игрой в угадайку, потому что иероглифы идут подряд без пробелов. Как разделить слова, если пробелов нет?
Немецкий: Встречал слова вроде Donaudampfschifffahrtsgesellschaftskapitän? Это слово из 40+ букв — и вот его надо как-то правильно разрезать на части.
Русский: Проблемы падежей, склонений и приставок. Слово «машина» может быть «машин», «машинами», «машине» — и каждая форма должна попадать в результат поиска.
Теперь разберёмся, как это решить с помощью анализаторов и фильтров в Elasticsearch.
Elasticsearch использует анализаторы для токенизации и обработки текста. Эти анализаторы — не волшебные существа, которые знают все языки мира, а набор фильтров, которые применяются по цепочке к тексту. Каждый язык имеет свои нюансы, и правильный выбор анализатора — ключ к успеху.
Токенизация для английского текста
Начнём с простого. Для английского языка можно использовать базовый Standard Analyzer, который разделяет текст на слова, игнорируя пунктуацию и приводя всё к нижнему регистру. Но иногда этого недостаточно.
Пример настройки анализатора:
{
"settings": {
"analysis": {
"analyzer": {
"english_custom": {
"type": "standard",
"stopwords": "_english_",
"filter": ["lowercase", "porter_stem"]
}
}
}
}
}
Здесь мы добавили porter_stem фильтр, который сокращает слова до их основы (например,»running» станет »run»).
Токенизация для китайского текста
В китайском нет пробелов, а каждый иероглиф может быть самостоятельным словом или частью большого составного слова. Для китайского текста Elasticsearch предлагает использовать Smart Chinese Analyzer.
Пример:
{
"settings": {
"analysis": {
"analyzer": {
"chinese": {
"type": "smartcn"
}
}
}
}
}
Этот анализатор использует встроенные словари для токенизации текста. Smart Chinese Analyzer автоматически разделит иероглифы на слова.
Токенизация для немецкого текста
Теперь немецкий. Токенизация в немецком может стать настоящей проблемой из-за составных слов. Классический пример: Donaudampfschifffahrtsgesellschaftskapitän. Для работы с такими словами можно использовать German Analyzer, который включает фильтры для стемминга и обработки сложных слов.
Пример настройки:
{
"settings": {
"analysis": {
"analyzer": {
"german_custom": {
"type": "standard",
"stopwords": "_german_",
"filter": ["lowercase", "german_normalization", "german_stem"]
}
}
}
}
}
Тут у нас german_normalization, который обрабатывает такие особенности языка, как умлауты (ä, ö, ü), и german_stem, который сокращает слова до их корня, сохраняя смысл.
Токенизация для русского текста
Для русского языка проблемы, как я уже упомянул, возникают с падежами и склонениями. Здесь нам на помощь приходит Russian Analyzer, который умеет обрабатывать морфологию русского языка.
Пример:
{
"settings": {
"analysis": {
"analyzer": {
"russian_custom": {
"type": "standard",
"stopwords": "_russian_",
"filter": ["lowercase", "russian_morphology", "russian_stop"]
}
}
}
}
}
Здесь фильтр russian_morphology работает с падежами и словоформами, а russian_stop фильтрует ненужные слова вроде «и», «в», «на».
Как не облажаться с токенизацией
Даже если ты считаешь, что выбрал правильный анализатор, всегда тестируй его на реальном тексте. Некоторые анализаторы могут работать отлично на одних данных и ужасно на других.
Иногда тебе придётся создавать свои собственные цепочки анализаторов, комбинируя несколько фильтров. Например, если стандартный анализатор для русского языка не справляется, можно добавить кастомный фильтр для обработки каких-то специфических слов.
Не забывайте про языковые особенности. Например, в немецком умлауты могут быть заменены на гласные без точек, а в русском необходимо учитывать, что буква «ё» и «е» могут быть взаимозаменяемы в поиске.
Stemming и Lemmatization
А сейчас поговорим о том, как не потерять смысл в многоязычном поиске, когда есть такие слова, как »идти»,»идёт»,»шел», и все они должны быть поняты Elasticsearch как одно и то же понятие. Именно здесь помогают stemming и lemmatization — два мощных инструмента, которые помогают поисковому движку делать текстовое сравнение умнее.
Разберемся в терминах:
Stemming — это процесс, который отрезает окончания слов, оставляя их основу (stem). Например,»playing»,»played» и »plays» будут сведены к »play». Метод грубый, но быстрый.
Lemmatization — более интеллектуальный процесс. Лемматизация приводит слово к его базовой форме (lemma) с учётом грамматического контекста. Например, слова »better» и »good» сведутся к одной лемме — »good». Более сложная задача, но результат точнее.
В разных языках лемматизация и стемминг работают по-разному. Что эффективно для английского языка, не всегда сработает для русского или немецкого. Основная проблема — грамматические различия между языками. Например:
В русском языке слово »идти» может изменяться по времени, числу и падежам.
В немецком сложные слова объединяются в одну строку.
В китайском нет морфологии, но важны контекстные лексические значения.
Stemming и lemmatization в Elasticsearch
Elasticsearch имеет встроенные анализаторы и фильтры для стемминга и лемматизации на разных языках. =
Stemming для английского языка
Для английского стемминг можно настроить с использованием Porter Stemming Algorithm — он преобразует слова к их основным формам.
Пример настройки:
{
"settings": {
"analysis": {
"filter": {
"english_stemmer": {
"type": "stemmer",
"language": "english"
}
},
"analyzer": {
"english_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"english_stemmer"
]
}
}
}
}
}
Этот пример настроит фильтр стемминга для английского языка. Когда ты отправляешь текст, вроде »running»,»ran», или »runs», анализатор приводит все эти слова к »run».
Stemming для немецкого языка
Для немецкого в есть German Normalization Filter и German Light Stemmer.
Пример настройки:
{
"settings": {
"analysis": {
"filter": {
"german_normalization": {
"type": "german_normalization"
},
"german_stemmer": {
"type": "stemmer",
"language": "light_german"
}
},
"analyzer": {
"german_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"german_normalization",
"german_stemmer"
]
}
}
}
}
}
Фильтр german_normalization обрабатывает буквы с умлаутами и заменяет ß на »ss», что улучшает корректность токенизации.
Лемматизация для русского языка
С русским языком сложнее: из-за развитой морфологии стемминг не всегда даёт точные результаты. Поэтому для русского языка часто лучше использовать лемматизацию. В Elasticsearch для русского языка можно воспользоваться специальным фильтром морфологии.
Пример настройки:
{
"settings": {
"analysis": {
"filter": {
"russian_morphology": {
"type": "russian_morphology"
}
},
"analyzer": {
"russian_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"russian_morphology"
]
}
}
}
}
}
Здесь мы используем russian_morphology — этот фильтр обрабатывает различные формы слов, приводя их к одной основе. Например,»идти»,»идёт» и »шел» будут рассматриваться как одно и то же слово.
Лемматизация для испанского языка
Испанский язык имеет свои особенности. Для него также применим лемматизатор, который приводит к нормальной форме глаголы и существительные.
Пример настройки:
{
"settings": {
"analysis": {
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
}
},
"analyzer": {
"russian_analyzer_with_stop": {
"tokenizer": "standard",
"filter": [
"lowercase",
"russian_morphology",
"russian_stop"
]
}
}
}
}
}
Здесь мы используем лёгкий стеммер для испанского, который обрабатывает флективные формы, такие как »hablando» (говорящий) и »hablar» (говорить).
Настройка стоп-слов для многоязычного поиска
Стоп-слова — это слова, которые не несут смысловой нагрузки для поиска (например, предлоги и союзы). В разных языках свои стоп-слова. Elasticsearch имеет встроенные списки стоп-слов для популярных языков, которые можно настроить по своему усмотрению.
Пример настройки анализатора со стоп-словами:
{
"settings": {
"analysis": {
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
}
},
"analyzer": {
"russian_analyzer_with_stop": {
"tokenizer": "standard",
"filter": [
"lowercase",
"russian_morphology",
"russian_stop"
]
}
}
}
}
}
Здесь фильтр russian_stop автоматически удаляет из текста русские предлоги и другие часто встречающиеся слова, которые не нужны для индексации.
Итак, если хочешь сделать многоязычный поиск умнее — настройка стемминга и лемматизации станет первым шагом на этом пути.
Аннотирование и метаданные
Сейчас поговорим про то, что многие недооценивают при работе с Elasticsearch, особенно в многоязычных проектах — это аннотирование документов и управление метаданными. Правильная аннотация и работа с метаданными не просто помогают в поиске, они обеспечивают релевантность и ускоряют доступ к информации, особенно в контексте многоязычных проектов.
Если твои пользователи из разных регионов и говорят на разных языках, ты обязан учитывать это в индексации и настройке поиска. Допустим, твой поиск должен обрабатывать документы на русском, английском и китайском. Для каждого языка у тебя должны быть свои настройки токенизации, фильтров, и самое главное — идентификация языка. Без грамотной работы с метаданными ты либо потеряешь релевантность, либо будешь перегружать поиск ненужной информацией.
Аннотирование — это процесс добавления метаданных в документы, которые помогают Elasticsearch правильно обрабатывать и индексировать их. Это как прицепить ярлык к каждому документу, чтобы Elasticsearch знал, как с ним обращаться. Метаданные могут включать информацию о языке, типе контента, дате создания и многом другом.
Поле _lang: как определить язык документа
Первое, с чем сталкиваются при создании многоязычного поиска, — это необходимость определять язык документа. Для этого можно использовать специальное поле _lang
, которое помогает Elasticsearch выбрать правильные анализаторы и фильтры для токенизации и стемминга.
Пример структуры данных с полем _lang
:
{
"title": "Добро пожаловать",
"body": "Это пример русского текста.",
"_lang": "ru"
}
Здесь явно указываем, что язык документа — русский. Это важно, потому что для каждого языка Elasticsearch использует свои токенизаторы, стеммеры и фильтры.
Рассмотрим, как можно настроить индекс, который будет учитывать языковую аннотацию. Допустим, есть данные на нескольких языках, и хочется, чтобы Elasticsearch применял разные анализаторы в зависимости от значения поля _lang
.
Пример конфигурации индекса:
{
"settings": {
"analysis": {
"analyzer": {
"default": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase"]
},
"english_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "english_stemmer"]
},
"russian_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "russian_morphology"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"english": {
"type": "text",
"analyzer": "english_analyzer"
},
"russian": {
"type": "text",
"analyzer": "russian_analyzer"
}
}
},
"body": {
"type": "text",
"fields": {
"english": {
"type": "text",
"analyzer": "english_analyzer"
},
"russian": {
"type": "text",
"analyzer": "russian_analyzer"
}
}
},
"_lang": {
"type": "keyword"
}
}
}
}
Здесь задаём разные анализаторы для английского и русского языков. Поле _lang
используется как ключ для определения языка документа, что помогает Elasticsearch автоматически применять нужный анализатор.
Как работать с метаданными
Само поле _lang
— это лишь один из примеров. Можно расширять аннотирование, добавляя метаданные о типе контента, версии документа и времени создания. Это позволяет строить более сложные запросы и фильтровать данные на основе этих параметров.
Вот несколько метаданных, которые помогут в многоязычном поиске:
_lang (язык) — как мы уже говорили, это обязательное поле для многоязычных проектов.
version (версия) — полезно для поиска по версиям документа. Например, если ты хранишь черновики и финальные версии.
timestamp (время создания) — важно для фильтрации документов по дате, особенно когда нужно искать самые свежие материалы на заданном языке.
Пример структуры с дополнительными метаданными:
{
"title": "Welcome",
"body": "This is an example of English text.",
"_lang": "en",
"version": "1.0",
"timestamp": "2024-09-20T12:00:00"
}
Теперь рассмотрим, как правильно настроить индекс Elasticsearch для работы с метаданными. Предположим, есть документы на разных языках, и нужно, чтобы поиск был быстрым и эффективным, а релевантность результатов сохранялась на высоте.
Пример настройки индекса:
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"analysis": {
"analyzer": {
"default": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text"
},
"body": {
"type": "text"
},
"_lang": {
"type": "keyword"
},
"version": {
"type": "keyword"
},
"timestamp": {
"type": "date",
"format": "yyyy-MM-dd'T'HH:mm:ss"
}
}
}
}
Здесь указываем, что поле _lang
должно быть проиндексировано как keyword (точное значение).
Теперь, когда документы содержат метаданные, можно использовать их для фильтрации и сортировки. Например, хочется получить все русскоязычные документы, отсортированные по дате создания:
Пример запроса:
{
"query": {
"bool": {
"must": [
{ "match": { "_lang": "ru" } }
]
}
},
"sort": [
{ "timestamp": { "order": "desc" } }
]
}
Этот запрос вернёт все документы на русском языке, отсортированные по времени создания (от более новых к старым).
Так что не ленитесь настраивать метаданные правильно — это тот случай, когда вложенные усилия окупаются качественным и быстрым поиском.
Заключение
В этой статье мы разобрали, как грамотно настраивать анализаторы для разных языков, использовать метаданные для фильтрации и оптимизировать процесс обработки документов. Помните, что поиск — это не просто индексация данных, это создание комфортного интерфейса между пользователем и информацией.
Надеюсь, данное руководство поможет вам сделать этот процесс более удобным.
Больше про инфраструктуру приложений коллеги из OTUS рассказывают в рамках практических онлайн-курсов. С полным списком курсов можете ознакомиться в каталоге.
Каталог курсов