Как реализовать ранжирование результатов полнотекстового поиска по тексту и тагам в Django + Sphinx (sphinxit библиотека)?

Всем привет,

Была поставлена передо мной задача реализовать полнотекстовый поиск по записям с тагами. Поиск я реализовал с использованием Sphinx и библиотеки sphinxit. Но сейчас запутался, как правильно организовать ранжирование по убыванию, когда пользователь указал текст для поиска и выделил несколько тагов (например: seo, django, google)? Ведь может быть так, что текст найден, но не все таги присутствуют в записи, как в этом случае ранжировать? Сначала отобразить все записи где присутствуют все таги, потом записи, где есть 90% тагов, и так далее?

Спасибо.
  • Вопрос задан
  • 1161 просмотр
Решения вопроса 1
У вас не совсем полный вопрос. Он про ранжирование или про то, как подружить теги со сфинксом? А как теги реализованы? Поэтому я напишу обзорный ответ, который покажет, куда копать, что делать. Сам я использовал однажды django-sphinx (ныне заброшенный) для похожих задач (только по тегам не сортировал, ибо задача у меня была по ним уточнять). А вот sphinxit не использовал, поэтому будем рассматривать сначала "голый" сфинкс, а потом уже sphinxit как надстройку. Так что могу где ошибиться, наврать.

Примем, что у вас классическая реализация тегов (тагов? хы) в виде таблички с тегами + many-to-many в табличку с элементами (так, например, делает django-taggit).

В сфинксе есть полнотекстовые поля (по ним он строит специальную структуру (индекс), чтобы быстро искать совпадения по ключевым словам). Они не хранятся в первоначальном виде, только в виде индекса. Есть еще атрибуты, они прикрепляются к индексу, по ним потом можно фильтровать, они именно хранятся в индексе. Это разные числовые значения (типа price или customer_id) или строковые или даже JSON, по ним обычно удобно фильтровать, уточнять запрос. Есть еще MVA - это тоже атрибуты, но представляют из себя множества числовых значений. А как их задать в сфинксе - пример. Вообще, такая возможность (атрибуты и MVA в частности) сделана, чтобы разгрузить БД и нагрузить сфинкс. :)

MVA-атрибуты подходят, чтобы в них хранить тэги и подобное (many-to-many), даже в доках написано:

That is useful to implement article tags, product categories, etc.

Ранжирование - это очень комплексный алгоритм. Для полнотекстовых полей в сфинксе есть специальные ранкеры, среди которых есть алгоритм BM25 и наивные ранкеры, например SPH_RANK_WORDCOUNT, который как раз и выполняет простой подсчет вхождений ключевых слов и учитывает вес поля. По атрибутам же можно фильтровать и сортировать. По MVA тоже можно фильтровать и сортировать.

Дальше буду говорить в рамках языка запросов SphinxQL. Можно еще обращаться к его API, но все равно вы используйте обертку, да и язык запросов как-то попроще для объяснения. В доках встречаются примеры из реализации API на PHP иногда, причем про SphinxQL пояснений нет, но это не значит, что через него нельзя это сделать (или значит, надо смотреть).

Для вашей задачи нужно сделать SELECT с функцией LENGTH() по MVA-атрибуту с тегами, далее по нему отсортировать с помощью ORDER_BY в нужном порядке, а также сделать фильтрацию с помощью WHERE, где указать вхождение тегов в атрибут с помощью оператора IN. Оператор WHERE - это комбайн фильтров по атрибутам и полнотекстового поиска. Таким образом можно рассортировать результаты по длине MVA-атрибута (количеству тегов).

Можно сюда добавить полнотекстовое ранжирование еще (включается с помощью SetRankingMode или с помощью OPTION в SphinxQL) у полнотекстовых полей (по алгоритму BM25, например, что по умолчанию), и сортировать в ORDER_BY и по длине MVA-атрибута с тегами и по WEIGHT() от ранкера (см. доки на предмет примеров); возможно или даже скорее всего, тут придется выбрать еще WEIGHT() в SELECT (раньше он неявно возвращался в ORDER_BY, я это тоже не тестировал). В таком случае будет круто: например, если подряд попадется 50 записей с одинаковым количеством тегов, то они будут отсортированы по весу ранжирующего алгоритма.

В общем, ваша задача красиво решается. Учитывая пример выше, можно с помощью исключительно только сфинкса реализовать и полное совпадение по тегам (сравнивать LENGTH() с количеством тегов в WHERE). И подобное.

Если же теги хранятся как-то неклассически (highload и т.п.), то их в любом случае можно будет достать либо в MVA, либо сделать полнотекстовым полем (в таком случае ранкер SPH_RANK_WORDCOUNT как раз применим). Поэтому суть та же.

Думаю, на все "теоретические" вопросы касательно ранжирования и сфинкса я ответил. Тут есть поле и для хорошего ранжирования и для оптимизации запросов, надо экспериментировать.

Теперь же в рамках sphinxit. Согласно его докам, он умеет и ранкеры включать и на SphinxQL говорить. Надо просто там все это оформить. Не исключены подводные камни, возможно, что-то он не сможет сделать, возможно, где-то и я наврал. По идее нужно уметь делать фильтрацию (тык), сортировку (тык), а тут даже пример, как включать ранкер + описание возможных ранкеров. Кажется, если что, то можно и в селект дописать условие. В общем, все выглядит дружелюбно, удачи!

Кстати, в документации sphinxit в примерах опций (ссылка выше) есть пример, где включен ранкер, но в ORDER BY условие только сортировать по атрибуту name. По идее это должно убить весь смысл ранкера для сортировки, ведь его надо явно указывать в ORDER BY. Видимо, там просто пример, как это конвертится в SphinxQL.

p.s. Странно, что уже как 4+ дня прошло, и на этот вопрос никто не написал ответа. Sphinx не популярен? :(
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы