Как внедрить поиск в реальный проект всего за 10 минут
Введение
На часах было почти 3 часа дня, я хотел было сделать себе послеобеденный кофе, но получил новое задание от тимлида. Необходимо было сделать поиск для онлайн платформы просмотра ТВ, сериалов и фильмов.
Старт
Я приступил к выполнению задачи. Запросы в гугле были разного уровня: от “зачем нужен поиск”, до “имплементация обратного индекса в СУБД”. Вот что удалось найти:
- “Серьезные пацаны” используют для поиска всякие Sphinx и ElasticSearch. Минусы: их нужно настраивать, загонять в них данные и мониторить их потребности.
- Haystack обеспечивает модульный поиск Django. У него простой и понятный API для работы над поиском, который позволяет подключать различные поисковые движки. Однако выявилась проблема в настройке минимального количества символов в системе серча: при значении — 1 он переставал корректно работать.
- Любая современная база данных имеет в себе встроенный полнотекстовый поиск.
После прочтения “кучи” статей о настройках поиска в хорошем виде в django, я остановился на PostgreSql, так как у “постгреса” часто появляются новые “плюшки”.
Подготовка окружения
Установка и настройка PostgreSql и Django настолько простая в нашем случае, что вы можете ее сделать по гайду. Также прочтите немного теории.
Индексирование в Django проекте
Индексирование бывает прямым и обратным. Например, если с помощью индексирования мы узнаём, что в левой части супермаркета есть баклажаны, картофель, помидоры и мармелад; в правой — мороженое, сухофрукты, мармелад и хлеб, то это прямой индекс.
Но что если нам нужно найти места, где есть мармелад? Придется пробегать циклом по всем местам и выбирать те, в которых имеется неободимый продукт.
Есть и другое решение. Продолжив читать книгу “Поиск. Индексы. И так далее” мы увидим, что нам может помочь обратный индекс. Он работает противоположно прямому, и после применения можно увидеть такую структуру:
Помидоры — левая сторона, при входе
Картофель — конец, левая сторона
Мармелад — левая сторона, правая сторона
Теперь мы точно знаем, где есть наш мармелад!
Возникает новый вопрос, как безболезненно внедрить эту “фичу” в наш Django проект?
Нашел решение. Благодаря разработчикам “постгреса”, сегодня есть простой в использовании GIN, который работает на основе обратного индекса.
Для использования его в “джанге” нужно всего лишь поставить psycorg и импортировать необходимые зависимости.
Тем самым мы проиндексировали поле title. Теперь давайте посмотрим, создался ли индекс внутри базы данных. Проверить можно командой:
SELECT tablename, indexname, tablespace, indexdef FROM pg_indexes WHERE tablename = ‘ваша таблица’;
Мы видим, что буквально с помощью одной строки кода мы упростили жизнь. И так, у нас есть проиндексированные данные, но как с ними работать? Будет ли работать ‘__icontains’, если просто указать необходимое поле в запросе?
Ответ: Да
Давайте разберем детально, почему.
Работа с запросами
Выше мы уже определили модель Movies, давайте продолжим работать с ней же.
Посмотрите, как выглядит обычный запрос Django ORM и Sql.
Django
Sql
SELECT vod_movies.title, vod_movies.release_dateFROM vod_moviesWHERE vod_movies.title like ‘%Kings%’;
Вывод
<QuerySet [<Movies: Kingsman: The Golden Circle>]>
Чтобы выполнить более сложный запрос, мы должны использовать три новых класса: SearchVector, SearchQuery, SearchRank.
SearchVector
Можно использовать SearchVector для создания нашего запроса с использованием нескольких полей. Затем использовать <filter>.
Django
Sql
SELECT “vod_movies”.”title”, “vod_movies”.”release_date”, “vod_movies”.”summary”to_tsvector(COALESCE(“vod_movies”.”title”, ) || ‘ ‘ || COALESCE(“vod_movies”.”summary”, ))AS “search”FROM “vod_movies”WHERE to_tsvector(COALESCE(“vod_movies”.”title”, ) || ‘ ‘ ||COALESCE(“vod_movies”.”summary”, )) @@ (plainto_tsquery(Wilson)) = true
Вывод
<QuerySet [<Movies: Wilson>, <Movies: The Pursuit of Happyness>]>
Так мы нашли фильмы “Wilson” и “The Pursuit of Happiness”. В первом случае, слово “Wilson” содержится в названии, во втором, в описании картины (слово “Wilson” в описании ленты добавил лично, потому что было лень искать слово, которое будет удовлетворять запросу).
SearchQuery
Бывает и такое, когда нужно сделать поиск по нескольким словам в базе данных. Представьте, вы ищете фильм в описании которого содержится фраза “good life”. SearchQuery позволит задать порядок этих слов в описании.
Django
Sql
SELECT “vod_movies”.”title”, “vod_movies”.”release_date”to_tsvector(COALESCE(“vod_movies”.”title”, ) || ‘ ‘ || COALESCE(“vod_movies”.”summary”, ))AS “search”FROM “vod_movies”WHERE to_tsvector(COALESCE(“vod_movies”.”title”, ) || ‘ ‘ ||COALESCE(“vod_movies”.”summary”, )) @@ ((plainto_tsquery(good) && plainto_tsquery(life))) = true
Вывод:
<QuerySet [<Movies: Locke>]>
SearchRank
Думаю, лучшее в данной статье — это SearchRank, так как он дает возможность посмотреть, насколько каждый элемент удовлетворяет запросу.
Только представьте, что вы хотите найти фильм, но не помните его названия. Все что вы помните — это то, что в названии были слова “Girl” и “Tattoo”. Могу Вас обрадовать SearchRank отлично справится с задачей: найдёт и отсортирует по степени “схожести” результат.
Django
Sql
SELECT “vod_movies”.”title”,ts_rank(to_tsvector(COALESCE(“vod_movies”.”title”, )), (plainto_tsquery(Girl) || plainto_tsquery(Tattoo)))AS “rank”FROM “vod_movies” ORDER BY “rank” DESC
Вывод
<QuerySet [(u’The Girl with the Dragon Tattoo’, 0.0607927), (u’Inside Girl’, 0.0303964), (u’Kingsman: The Golden Circle’, 0.0)]
Заключение
Люди пользуются поиском везде, поэтому он неотъемлемая часть большинства проектов. Поиск, который я реализовал, внедрен на сайт, где количество фильмов и сериалов превышает несколько тысяч.
Более того, при тщательной настройке PostgreSql с Django можно получить реактивный поиск. Надеюсь, что после прочтения статьи вы захотите поработать с Postgresql.