Допиливаем djapian. Встречайте django-xapian!

Обновление от 24 февраля 2009 года: проект я прекратил развивать, т.к. получил полный доступ к проекту Djapian. Все нижеперчисленные идеи уже туда портированы, успели проэволюционировать и дополниться другими. Так что смело пользуйтись самим Djapian.

Проблема выбора реализации для индексирования/поиска на сайте, поднятая мною недавно, пришла к своему разрешению. Как я уже говорил, остановился я на djapian. Но в том виде, в котором было данное приложение, его использовать было практически невозможно.

Решил немножко подкрутить там гайки и обтесать под свои нужды. Но поскольку изменений накопилось много, то я завел отдельный проект для моей ветки. Назвал я его просто и без затей django-xapian. И конечно же выкладываю в открытый доступ свои наработки. Вот домашняя страничка проекта http://webnewage.org/projects/p/django-xapian/

Основные изменения по сравнению с оригиналом:

  • Код подтянут до транка джанги
  • Исправлен NameError, который ставил крест на использовании оригинала:)
  • Объекты обрабатываются только при срабатывании пользовательского триггера, а не просто по событию
  • Перенесен сервис индексировани из отдельно скрипта в команду manage.py
  • Оптимизирован процесс индексирования - убраны лишние действия, которые выполнял исходный скрипт, не отфильтровывая неактуальные уже изменения в объектах.
  • Убрана махинация с неймспейсами
  • Добавлена возможность индексирования не только непосредственно полей данных модели и но результатов выполнения методов, либо связанных объектов
  • мелкие косметические изменения, добавляющий более прозрачности в работу приложения

Теперь короткий рассказ, как данным приложением пользоваться.

Для начала необходимо установить django-xapian, взяв из репозитория исходники и сделать так, чтобы dxapian директория была доступна в PYTHON_PATH ( я обычно делаю символическую ссылку в /lib/python/site-packages/). Конечно должен быть установлен и сам xapian, с биндиногом для питона.

Теперь непосредственно будем интегрировать. Возьмем абстрактную модель статьи:

class Article( models.Model ):
    title = models.CharField( max_length = 255 )

    text = models.TextField()

    published = models.BooleanField( defautl = True )

Совсем простая, но с флагом публикации, потом покажу зачем он нам в примере понадобиться.

Теперь важный этап создание индексера для этой модели:

from dxapian import XapianIndexer

indexer = XapianIndexer( model = Article,
                         fields = ( "text", ),
                         attributes = { "title" : "title",  },
                         trigger = ( lambda article: article.published )
                        )

Первым параметром передаем модель, которую хотим индексировать, в нашем случае Article. Второй параметр это последовательность полей модели, которые должны быть индексированы. Параметр attributes - это тоже поля для индексирования, но те которые будут доступны через префиксы в запросе поиска, в формате префикс : поле. В качестве поля для индексирования могут выступать и методы(конечно их результат, но указывает в перечислении именно имя метода), и "вложенные" объекты, например если бы у наши статьи были бы разбиты на категории то можно было бы добавить в attributes "category" : "catagory.title". Последний параметр это триггер, который сообщает должен ли быть проиндексирован объект, в нашем случае он проверяет флаг "опубликованности" статьи.

Для того чтобы индексер точно знал куда ему сохранять индекс, необходимо в settings.py указать значение DXAPIAN_BASE, в котором должен быть абсолютный путь до директории хранения. Далее индексер сам разбирается куда сохранять данные (можно кастомизировать, передав параметр path при создании индексера).

Поскольку процесс индексрования может быть достаточно ресурсоемким, то его проводить не при добавлении/изменении объекта( и прохождении триггера), а по расписанию. Отвечает за сам процесс индексирования команда xapianindex. Чтобы узнать о ней поподробней напишите в командной строке:

> ./manage.py help xapianindex

Запуск это скрипта нужно прописать в планировщике (например cron), ну а интервал подобрать исходя из особенностей вашего проекта.

Всё. С самим индексированием покончено. Теперь приступим к реализации конечной цели - поиска. Тут всё тоже просто. Напишем вьюху для поиска:

class SearchForm( forms.Form ):
     query = forms.CharField()

def search( request )
    form = SearchForm( "query" in request.GET and request.GET or None )
    context = { "form" : form }

    if form.is_valid():
        query = form.cleaned_data[ "query" ]
        resultset = Article.indexer.search( query = query )

        context.update( { "resultset" : resultset } ) 

    return render_to_response( "search.html", context )

Итак, когда мы создавали индексер, он любезно добавил свой объект к модели как атрибут indexer. Через него и осуществляется поиск. Результатом поиска является объект типа ResultSet, в подробности вдаваться не буду, просто скажу, что он инкапсулирует в себе итерацию по результату поиска (что и из названия понятно:)).

Теперь выведем результат в шаблоне, где будет понятно в каком виде поиск возвращает "строки" результата:

{% for result in resultset %}
   <li>{{result.score}}% - {{result.instance.title}}</li>
{% endfor %}

Видно, что score - это показатель релевантности, а result.instance это сам объект модели, который соответствует результату. Он кешируется при первом доступе к атрибуту, поэтому его можно "дергать" часто:)

Всё. Поиск готов. Теперь его можно испытать. Конечно в индексе что-то уже должно быть. А поисковый запрос может выглядеть например так:

title:блог OR джанго

За более подробной информацией по синтаксису запросов отправляю в документацию по xapian, там всё очень подробно расписано.

Я конечно не все особенности использования django-xapian осветил, но основные моменты постарался.

Работа по доработке продолжается. Есть несколько идей которые хотелось бы ещё реализовать.

Пожелания, замечания и всё что связано с django-xapian прошу писать сюда, в комментарии. Помогу всем кто решиться попробовать его в действии:)

P.S. Поиск, который недавно появился на блоге, сделан именно с использованием данного приложения.

Комментарии 11

  1. aeriman написал:

    А кастомизируемый по языкам поиск на нём возможно реализовать?

    Оставлен 09 Февраль 2008 в 06:50
  2. L0rda написал:

    Спасибо, отличная работа, будем пробовать 8)

    Оставлен 09 Февраль 2008 в 12:32
  3. Александр Кошелев написал:

    А кастомизируемый по языкам поиск на нём возможно реализовать?

    Хороший вопрос. Если я его правильно понял, то уже сейчас можно сделать вариативный поиск в зависимости от параметра, причем 2мя способами. Допустим к модели статей добавили поле lang, где язык статьи хранится. Теперь 2 пути:

    1.Добавить при создании ндексера, в словарь атрибутов "lan":"lang" и изменить вьюху поиска примерно так:

     def search( request ):
         form = SearchForm( "query" in request.GET and request.GET or None )
         context = { "form" : form }
    
         if form.is_valid():
             query = form.cleaned_data[ "query" ]
    
             lang = get_lang( request )# магическая функция, определяющая на каком языковом\
                                                         разделе сайта мы находимся
             query += " lang:%s" % lang
             resultset = Article.indexer.search( query = query )
    
             context.update( { "resultset" : resultset } ) 
    
         return render_to_response( "search.html", context )
    

    И всё, движок сам будет отфильтровывать только документы с указанным параметром lang.

    2.Вести поиск по всем документам, а потом уже средствами ORM отфилтровывать id только тех, которые соотвествуют нужному языку

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

    В TODO лист добавлю пункт:) Спасибо за наводку.

    Оставлен 09 Февраль 2008 в 12:55
  4. Александр Кошелев написал:

    Я правильно понимаю, что оно отдельным документом считает один конкретный объект модели, и сделать документ из разных моделей не получится? Или есть способ?

    Да. Объект модели это документ, но поскольку он может состоять не только из атрибутов самой модели, по и атрибутов связанных с ней( как в примере с категориями) объектов, то в некотором роде документ можно считать и составным.

    Изначально в djapian есть механизм создания документа из нескольких моделей, но он уж очень мудреный и я его выбросил. Тем более документации не было и нет.

    Второй вопрос, перестраивает ли он индекс по крону целиком или только изменения добавляет?

    Только изменения. Для каждой модели цепляются обработчики postsave/predelete сигналов, которые отлавливают изменения. Сам же xapian хранит индекс в своём собственном формате flint, про который написано в документации:

    It supports incremental modifications, concurrent single-writer and multiple-reader access to a database. It's very efficient and highly scalable.

    Оставлен 09 Февраль 2008 в 13:17
  5. Rafael написал:

    Hi, I'm the owner of the Djapian project, I'd like you are using the project and I see that you do somethings into it, why don't you send patches to the project, I'll be happy if you come to the djapian-dev (http://groups.google.com/group/djapian-dev) list and participate. :)

    (Sorry I don't understand anything of your blog, I'm brazilian, I'm telling what I see in the codes and your subversion repository)

    Оставлен 16 Февраль 2008 в 13:21
  6. Кирилл написал:

    Господа, позвольте вопрос не совсем по теме - если я все правильно понимаю, то xapian поддерживает поиск с учетом морфологии русского языка, но по дефолту эта возможность не включена. Как ее включить? В доке по xapian я этого не нашел, подскажите, что надо сделать.

    Оставлен 24 Апрель 2008 в 13:06
  7. kmmbvnr написал:

    В djaxpian поддержка стемминга уже есть,

    http://code.google.com/p/djapian/wiki/Stemming

    А в этом "форке" еще нет.

    Оставлен 25 Май 2008 в 07:54
  8. Александр Кошелев написал:

    Да, да. Мне ещё давно дали комит в джапиан, но всё руки не дойдут взять всё лучшее из двух миров и "индексер мечты". Но может в каникулы доберусь:)

    Оставлен 25 Май 2008 в 10:45
  9. kmm написал:

    Скрестил ужа с ежом, за основу взял эту версию, добавил куски из djapian'a ответственный за стемминг. Искать стало как то странно, впечатление что отдельные слова просто не попадают в индекс. Дебаггером, все кошерно.

    Оставлен 28 Май 2008 в 15:16
  10. diadya_vova написал:

    Привет. Спасибо за dxapian, очень пригодилось.

    Маленькое замечание: то-ли у меня что-то с руками, то-ли не знаю даже. Но когда создаешь indexer в models.py, то \dxapian\backend\base.py ругается на line #124. Закоментировал #123-124 и всё заработало.

    Оставлен 21 Сентябрь 2008 в 06:33
  11. Александр Кошелев написал:

    Я сейчас занят обновлением самого djapian и напишу об этом подробно. Так что скоро будет одна правильная xapian интеграция.

    Оставлен 21 Сентябрь 2008 в 07:54