Как игнорировать специальные символы в query_string Elasticsearch

Спецсимволы ломают query_string в Elasticsearch? Есть решение! Покажем проверенные способы заставить поиск игнорировать их и работать без сбоев.

При работе с query_string в Elasticsearch часто возникает проблема: специальные символы (например, + ー & | ! ( ) { } [ ] ^ " ~ * ? : /) воспринимаются как часть синтаксиса запроса и могут ломать поиск или давать неожиданные результаты․ В этой статье мы рассмотрим несколько проверенных способов заставить Elasticsearch игнорировать такие символы при выполнении query_string‑запросов․

Почему специальные символы мешают?

В query_string Elasticsearch использует собственный язык запросов․ Любой символ из набора операторов рассматривается как специальный, поэтому:

  • символ - интерпретируется как оператор исключения;
  • символ :, как разделение поля и значения;
  • скобки, кавычки и звёздочки меняют логику группировки и подстановочных знаков․

Если в индексируемом поле встречаются такие символы (например, коды вида D! или 8652/58996-3), обычный query_string либо не найдёт документ, либо вернёт ошибку парсинга․

Стратегии игнорирования специальных символов

Существует три основных подхода:

  1. Создать пользовательский анализатор, который удаляет или заменяет специальные символы на этапе индексации и поиска․
  2. Воспользоваться параметром lenient у simple_query_string – он автоматически игнорирует синтаксические ошибки, вызванные специальными символами․
  3. Экранировать специальные символы в самом запросе (обратный слеш ) – подходит, когда нужно оставить символы в поиске, но избежать их интерпретации как операторов․

Ниже подробно описан каждый метод с примерами настроек и запросов․

Метод 1: Пользовательский анализатор с pattern_replace фильтром

Самый надёжный способ – избавиться от специальных символов ещё на этапе индексации, а затем использовать тот же анализатор при поиске․ Для этого применяется character filter типа pattern_replace․

Шаг 1: Определяем settings с char_filter и analyzer

PUT my_index
{
 "settings": {
 "analysis": {
 "char_filter": {
 "remove_special": {
 "type": "pattern_replace",
 "pattern": "[^A-Za-z0-9\s]", // оставляем только буквы, цифры и пробелы
 "replacement": ""
 }
 },
 "analyzer": {
 "alphanumeric": {
 "type": "custom",
 "tokenizer": "standard",
 "filter": ["lowercase"],
 "char_filter": ["remove_special"]
 }
 }
 }
 },
 "mappings": {
 "properties": {
 "title": {
 "type": "text",
 "analyzer": "alphanumeric",
 "search_analyzer": "alphanumeric"
 },
 "code": {
 "type": "keyword",
 "normalizer": "alphanumeric_norm"
 }
 }
 }
}

В примере выше:

  • pattern [^A-Za-z0-9\s] означает «любой символ, который НЕ является буквой, цифрой или пробелом»;
  • такие символы заменяются пустой строкой (replacement: "");
  • анализатор alphanumeric применяется к полю title (тип text) и к нормализатору alphanumeric_norm для keyword‑поля code․

Шаг 2: Индексируем документы

PUT my_index/_doc/1
{
 "title": "Quick Brown-Fox!",
 "code": "D!"
}

PUT my_index/_doc/2
{
 "title": "Another example",
 "code": "D"
}

После индексации в поле title будут храниться токены quick, brown, fox (дефис и восклицательный знак удалены)․ В поле code (keyword) значение будет нормализовано до d․

Шаг 3: Выполняем query_string поиск, игнорируя специальные символы

GET my_index/_search
{
 "query": {
 "query_string": {
 "query": "D!", // восклицательный знак будет удалён анализатором
 "fields": ["title", "code"]
 }
 }
}

Ответ вернёт оба документа, потому что при анализе запроса строка D! превратится в d, которая совпадает с нормализованным значением поля code․

Метод 2: simple_query_string с параметром lenient

Если менять индекс и анализатор нежелательно, можно воспользоваться запросом simple_query_string, который более толерантен к синтаксическим ошибкам․ Параметр lenient заставляет Elasticsearch просто игнорировать части запроса, которые не могут быть распарсены․

GET my_index/_search
{
 "query": {
 "simple_query_string": {
 "query": "D! / 8652-58996", // специальные символы здесь не ломают запрос
 "fields": ["title", "code"],
 "lenient": true
 }
 }
}

В этом случае:

  • символы !, /, - traktуются как обычные символы;
  • если часть запроса не может быть интерпретирована как оператор, она просто игнорируется;
  • результат будет таким же, как если бы мы искали по очищенной строке D 865258996․

Важно: simple_query_string поддерживает ограниченный набор операторов (+ ⏤ & | ! ( ) { } [ ] ^ " ~ * ? : ), поэтому если вам нужны более сложные конструкции (например, фразовый поиск с "phrase"), лучше вернуться к методу с пользовательским анализатором․

Метод 3: Экранирование специальных символов в query_string

Иногда требуется сохранить специальные символы в поиске (например, искать literal C++), но при этом избежать их интерпретации как операторов․ В этом случае каждый специальный символ нужно предварять обратным слешем ․

Список символов, требующих экранирования в query_string:

  • + ⏤ & | ! ( ) { } [ ] ^ " ~ * ? : /
GET my_index/_search
{
 "query": {
 "query_string": {
 "query": "C\+\+", // ищем literal "C++"
 "fields": ["title"]
 }
 }
}

Если в поле есть значение C++, запрос вернёт документ․ Экранирование делает символ обычным литералом, а не оператором․

Практический пример: поиск по коду с дефисами и слэшами

Допустим, у нас есть поле refNo со значениями типа 8652/58996-3․ Нужно найти документ, зная только цифры 8652589963․

Вариант A: Пользовательский анализатор (рекомендуется)

PUT ref_index
{
 "settings": {
 "analysis": {
 "char_filter": {
 "strip_non_digit": {
 "type": "pattern_replace",
 "pattern": "[^0-9]",
 "replacement": ""
 }
 },
 "analyzer": {
 "digits_only": {
 "type": "custom",
 "tokenizer": "keyword",
 "filter": [],
 "char_filter": ["strip_non_digit"]
 }
 }
 }
 },
 "mappings": {
 "properties": {
 "refNo": {
 "type": "text",
 "analyzer": "digits_only",
 "search_analyzer": "digits_only"
 }
 }
 }
}

Индексируем:

PUT ref_index/_doc/1
{
 "refNo": "8652/58996-3"
}

Ищем:

GET ref_index/_search
{
 "query": {
 "query_string": {
 "query": "8652589963",
 "fields": ["refNo"]
 }
 }
}

Результат – документ найден, потому что при индексации и при поиске все не‑цифровые символы удаляются․

Вариант B: simple_query_string с lenient (быстрое решение)

GET ref_index/_search
{
 "query": {
 "simple_query_string": {
 "query": "8652589963",
 "fields": ["refNo"],
 "lenient": true
 }

 }
}

Здесь Elasticsearch попытается разобрать запрос; поскольку в нём нет операторов, он будет обработан как обычный терм‑поиск, а специальные символы в поле просто не будут мешать․

Советы и лучшие практики

  • Если специальные символы встречаются часто и их значение не важно для поиска – создавайте отдельный анализатор с pattern_replace․ Это гарантирует одинаковое поведение при индексации и поиске․
  • Для редких случаев, когда нужно просто «пережить» ошибочный запрос, используйте simple_query_string с lenient:true․
  • Экранирование удобно, когда вы хотите искать literal значения, содержащие операторы (например, C++ или price:$100)․ Не забывайте экранировать каждый символ из списка выше․
  • После изменения анализатора обязательно выполните reindex существующих данных, иначе старые токены останутся с символами․
  • Проверяйте работу анализатора через API _analyze:
GET my_index/_analyze
{
 "analyzer": "alphanumeric",
 "text": "D! ー test"
}

Ответ покажет, какие токены действительно будут индексироваться․

Игнорирование специальных символов в query_string‑поиске в Elasticsearch достигается несколькими способами:

  1. Создание пользовательского анализатора с pattern_replace character filter – самый надёжный и прозрачный метод;
  2. Использование simple_query_string с параметром lenient – быстрое решение для простых запросов;
  3. Экранирование специальных символов обратным слешом – когда нужно искать их как literal значения․

Выбор метода зависит от частоты появления специальных символов в данных, требуемой точности поиска и готовности менять маппинг/анализатор индекса․ Надеюсь, приведённые примеры и рекомендации помогут вам построить устойчивый поиск, который не будет «сломаться» из‑за знаков препинания или математических операторов․