При работе с query_string в Elasticsearch часто возникает проблема: специальные символы (например, + ー & | ! ( ) { } [ ] ^ " ~ * ? : /) воспринимаются как часть синтаксиса запроса и могут ломать поиск или давать неожиданные результаты․ В этой статье мы рассмотрим несколько проверенных способов заставить Elasticsearch игнорировать такие символы при выполнении query_string‑запросов․
Почему специальные символы мешают?
В query_string Elasticsearch использует собственный язык запросов․ Любой символ из набора операторов рассматривается как специальный, поэтому:
- символ
-интерпретируется как оператор исключения; - символ
:, как разделение поля и значения; - скобки, кавычки и звёздочки меняют логику группировки и подстановочных знаков․
Если в индексируемом поле встречаются такие символы (например, коды вида D! или 8652/58996-3), обычный query_string либо не найдёт документ, либо вернёт ошибку парсинга․
Стратегии игнорирования специальных символов
Существует три основных подхода:
- Создать пользовательский анализатор, который удаляет или заменяет специальные символы на этапе индексации и поиска․
- Воспользоваться параметром
lenientуsimple_query_string– он автоматически игнорирует синтаксические ошибки, вызванные специальными символами․ - Экранировать специальные символы в самом запросе (обратный слеш ) – подходит, когда нужно оставить символы в поиске, но избежать их интерпретации как операторов․
Ниже подробно описан каждый метод с примерами настроек и запросов․
Метод 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 достигается несколькими способами:
- Создание пользовательского анализатора с
pattern_replacecharacter filter – самый надёжный и прозрачный метод; - Использование
simple_query_stringс параметромlenient– быстрое решение для простых запросов; - Экранирование специальных символов обратным слешом – когда нужно искать их как literal значения․
Выбор метода зависит от частоты появления специальных символов в данных, требуемой точности поиска и готовности менять маппинг/анализатор индекса․ Надеюсь, приведённые примеры и рекомендации помогут вам построить устойчивый поиск, который не будет «сломаться» из‑за знаков препинания или математических операторов․