Как реализовать поиск по сайту: от фильтрации до полнотекстового поиска

Как реализовать поиск по сайту: от фильтрации до полнотекстового поиска

Поиск на сайте — это одна из тех вещей, на которую пользователь не обращает внимания, пока она работает хорошо. Зато когда строка поиска выдаёт мусор или вовсе ничего не находит, раздражение наступает мгновенно. Хороший поиск удерживает людей на сайте, плохой — отправляет их к конкурентам. Разберёмся, как выстроить поиск грамотно: с чего начать, какие подходы существуют и когда стоит переходить к серьёзным решениям.

Простая фильтрация: когда этого достаточно

Как реализовать поиск по сайту: от фильтрации до полнотекстового поиска. Простая фильтрация: когда этого достаточно

Не каждому сайту нужен полноценный поисковый движок. Если у вас каталог из 50 товаров, интернет-магазин с фиксированными категориями или корпоративный сайт с небольшим блогом — обычная фильтрация справится с задачей без лишней сложности. Фильтрация работает по точному совпадению атрибутов: пользователь выбирает цвет, цену, категорию, и система просто отбирает записи, которые соответствуют этим параметрам.

Технически это реализуется SQL-запросами с условиями WHERE и дополнительными JOIN-ами, если данные хранятся в нескольких таблицах. Такой подход хорошо работает, когда структура данных чёткая и предсказуемая. Главное ограничение — пользователь должен точно знать, что ищет, и выбирать из готовых вариантов. Как только он хочет написать произвольный запрос в строку, фильтрация начинает подводить.

Как правильно строить фильтры

Хорошо спроектированный фильтр — это не просто набор чекбоксов. Фильтры должны быть взаимосвязаны: если пользователь выбрал категорию «Ноутбуки», то в фильтре по бренду должны остаться только те производители, у которых есть ноутбуки в наличии. Такой подход называют зависимыми или каскадными фильтрами.

Реализуется это динамическими запросами: при каждом изменении фильтра клиент отправляет запрос на сервер, который пересчитывает доступные варианты и возвращает обновлённый список. Если делать фильтры статическими, пользователь рискует выбрать комбинацию параметров, по которой нет ни одного результата. Это один из самых частых UX-провалов в каталогах.

Поиск по строке: LIKE и его пределы

Когда нужно искать по произвольному тексту, первое, что приходит в голову, — оператор LIKE в SQL. Запрос вида WHERE title LIKE ‘%ноутбук%’ найдёт все записи, где в заголовке встречается это слово. Для небольших баз и простых задач этого вполне хватает. Но у LIKE есть серьёзные проблемы, которые быстро дают о себе знать при росте данных.

Шаблон с ведущим процентом (%слово%) не использует индексы базы данных. Это значит, что при каждом поиске система просматривает все строки таблицы — так называемое полное сканирование (full table scan). На таблице из тысячи записей это незаметно, на миллионе — уже тормоза. Ещё хуже то, что LIKE не понимает морфологию: запрос «ноутбука» не найдёт статью, где написано «ноутбуки».

Триграммы как промежуточное решение

В PostgreSQL есть расширение pg_trgm, которое позволяет строить индексы для нечёткого поиска подстрок. Триграмма — это последовательность из трёх символов, на которые разбивается каждое слово. Поиск происходит не по символам, а по этим фрагментам, что позволяет использовать индекс GIN или GiST и работает значительно быстрее чистого LIKE.

Читайте также:  Этапы и инструменты тестирования веб-сайтов перед запуском: как обеспечить безупречную работу

Триграммный поиск хорошо справляется с опечатками и неточными запросами. Если пользователь напишет «ноутбуки» вместо «ноутбук» или допустит ошибку в слове, pg_trgm всё равно найдёт похожие совпадения за счёт расчёта коэффициента схожести. Это уже ощутимо лучше LIKE, хотя полноценной лингвистикой здесь и не пахнет.

Полнотекстовый поиск средствами базы данных

PostgreSQL и MySQL имеют встроенный полнотекстовый поиск (Full-Text Search, FTS). Принцип работы следующий: при индексировании текст разбивается на токены (отдельные слова), каждый токен нормализуется до базовой формы (лемматизация или стемминг), и строится инвертированный индекс — структура, в которой каждому слову соответствует список документов, где оно встречается.

В PostgreSQL для этого используются типы данных tsvector (нормализованный вектор документа) и tsquery (поисковый запрос). Функция to_tsvector(‘russian’, text) разбирает текст с учётом русской морфологии, а to_tsquery формирует запрос. Поиск по индексу работает на порядки быстрее LIKE и понимает словоформы: «запуск», «запустил», «запускает» — всё это будет найдено по запросу «запуск».

Важно: встроенный FTS в PostgreSQL хорошо обрабатывает русский язык при указании правильной конфигурации. Если не указать ‘russian’, по умолчанию применяется ‘english’, и морфология работать не будет.

Ранжирование результатов

Просто найти все документы с нужным словом — это полдела. Гораздо важнее показать пользователю самые релевантные результаты первыми. В PostgreSQL для этого есть функции ts_rank и ts_rank_cd. Они вычисляют числовой рейтинг каждого документа на основе того, как часто встречается поисковый термин, в каком поле он найден и насколько он сосредоточен в документе.

На практике это выглядит так: совпадение в заголовке даёт больший вес, чем совпадение в теле статьи. Для этого при формировании tsvector разным полям присваиваются веса от A до D, где A — самый высокий приоритет. Например, setweight(to_tsvector(‘russian’, title), ‘A’) || setweight(to_tsvector(‘russian’, body), ‘C’) позволяет показывать наверху те документы, где термин встречается в заголовке.

Когда нужны специализированные поисковые движки

Встроенный FTS в PostgreSQL — отличный выбор для большинства проектов, пока объём данных не вырастает до сотен гигабайт или пока требования к поиску не становятся сложнее. Если нужны автодополнение в реальном времени, фасетный поиск (фильтры по динамически вычисляемым категориям), синонимы, поиск по геолокации или обработка нескольких языков — пора смотреть в сторону специализированных инструментов.

Самые популярные решения — Elasticsearch и OpenSearch (форк Elasticsearch под открытой лицензией). Оба построены на библиотеке Apache Lucene и предоставляют REST API для индексирования и поиска. Из более простых альтернатив выделяется Meilisearch — он заметно проще в настройке, хорошо работает с небольшими и средними данными, поддерживает опечатки «из коробки» и ориентирован именно на сайтовый поиск.

Читайте также:  Веб, который распахивает комнату: как AR и VR приходят на страницы

Elasticsearch: основные возможности

Elasticsearch хранит данные в документах формата JSON и строит по ним инвертированные индексы. Поисковые запросы пишутся на языке Query DSL — это JSON-структуры, которые позволяют комбинировать условия произвольной сложности. Например, можно одновременно искать по тексту, фильтровать по дате, ограничивать по числовому диапазону и сортировать по релевантности.

Elasticsearch поддерживает анализаторы — это конвейеры обработки текста, которые включают токенизацию, удаление стоп-слов, стемминг и синонимы. Для русского языка есть плагин Morphological Analyzer от компании «Просто об Elasticsearch», а также встроенный анализатор russian, основанный на алгоритме Snowball. Правильно настроенный анализатор существенно влияет на качество результатов.

Интересно: Elasticsearch изначально разрабатывался как поисковый движок для кулинарного сайта. Автор, Шай Бэнон, написал его для жены, которая хотела удобно искать рецепты. В итоге проект вырос в один из самых популярных поисковых инструментов для корпоративного использования.

Meilisearch как простая альтернатива

Если проект не требует горизонтального масштабирования на несколько серверов и объём данных не превышает нескольких миллионов документов, Meilisearch будет гораздо удобнее Elasticsearch. Он написан на Rust, устанавливается как один бинарный файл, и поиск с опечатками работает без дополнительной настройки. Интерфейс API интуитивно понятен, а время отклика обычно не превышает 50 мс.

Meilisearch поддерживает фасеты, настраиваемое ранжирование, стоп-слова и синонимы. Из минусов — меньше гибкости в сложных аналитических запросах и отсутствие встроенной поддержки кластеризации в бесплатной версии. Для интернет-магазина, новостного портала или документационного сайта это практически идеальное соотношение простоты и функциональности.

Автодополнение и поиск по мере ввода

Как реализовать поиск по сайту: от фильтрации до полнотекстового поиска. Автодополнение и поиск по мере ввода

Автодополнение (autocomplete или typeahead) — это не просто удобная фича, а реальный инструмент, который снижает количество нулевых результатов. Пользователь не всегда знает точное название того, что ищет, и подсказки помогают ему сформулировать запрос правильно. Технически это работает через отправку частичного запроса по мере ввода каждого символа, хотя на практике запросы отправляются с задержкой (debounce) в 200-300 мс, чтобы не перегружать сервер.

В Elasticsearch для автодополнения есть специальный тип поля — completion. Он хранит возможные варианты продолжения и работает быстрее обычных текстовых запросов. Менее элегантный, но простой вариант — хранить все уникальные поисковые фразы в отдельной таблице и искать по ним через prefix-запрос. PostgreSQL поддерживает такие запросы через индекс типа B-tree или через всё то же расширение pg_trgm.

Важно: при реализации автодополнения не стоит отправлять запрос при каждом нажатии клавиши — это создаёт избыточную нагрузку на сервер. Debounce в 250-300 мс почти незаметен для пользователя, но снижает количество запросов в несколько раз.

Индексирование и производительность

Любой поиск — это компромисс между скоростью записи и скоростью чтения. Индексы ускоряют поиск, но замедляют вставку и обновление данных. Для большинства сайтов данные меняются реже, чем ищутся, поэтому индексы — это всегда правильный выбор. Вопрос только в том, какие именно индексы строить.

Читайте также:  Сайты без очередей: как уйти от серверов и прийти к скорости

Для полнотекстового поиска в PostgreSQL нужен индекс типа GIN по полю tsvector. Если tsvector вычисляется «на лету» при каждом запросе, это медленно. Правильное решение — хранить tsvector как отдельный столбец и обновлять его через триггер при изменении исходных полей. Такой подход позволяет индексировать данные один раз при записи и не тратить время на вычисления при каждом поисковом запросе.

Кэширование результатов

Популярные запросы повторяются. Если сотни пользователей ищут одно и то же слово, нет смысла каждый раз делать полный поиск по базе. Результаты таких запросов удобно кэшировать в Redis или Memcached на несколько минут. Это особенно актуально для страниц с высоким трафиком, где один и тот же запрос может выполняться тысячи раз в минуту.

Ключ кэша обычно формируется из нормализованного запроса, активных фильтров и номера страницы. При изменении данных (добавление нового товара, публикация статьи) соответствующие записи кэша инвалидируются. Это немного усложняет логику, но экономия ресурсов окупает затраты на разработку уже при умеренном трафике.

Как выбрать подход под конкретный проект

Как реализовать поиск по сайту: от фильтрации до полнотекстового поиска. Как выбрать подход под конкретный проект

Выбор инструмента зависит от трёх факторов: объём данных, сложность запросов и ресурсы на поддержку. Не нужно сразу тянуть Elasticsearch ради каталога из 500 товаров, но и LIKE на базе из миллиона статей — заведомо плохое решение.

Ситуация Рекомендуемое решение
До 10 000 записей, простые фильтры SQL + LIKE или pg_trgm
До 1 млн записей, нужна морфология PostgreSQL FTS с GIN-индексом
Средний проект, нужны опечатки и автодополнение Meilisearch
Большой проект, аналитика, масштабирование Elasticsearch / OpenSearch

Нет универсального ответа, потому что нет одинаковых проектов. Разумная стратегия — начать с простого встроенного решения и переходить на более мощное только тогда, когда текущее перестаёт справляться. Переусложнять архитектуру на старте — дорого и редко оправдывает себя.

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

Понравилась статья? Поделиться с друзьями:
Разработка сайтов — это просто!