Pull to refresh

Использование библиотеки для полнотекстового поиска Xapian в Python

Reading time 4 min
Views 10K
Сегодня, в эпоху Web 2.0, когда контента на сайтах становится все больше и больше, перед разработчиками встает задача реализации полнотекстового поиска.

Вариантов немного:
  • использовать виджеты от разработчиков поисковых систем (Google, Яндекс, etc): легко внедрить, привычный для пользователя интерфейс, поддержка морфологии, исправление слов по словарю, возможно более быстрая индексация сайта поисковыми системами, но, как правило ограниченные возможности по настройке и неизбежное запаздывание индексации;
  • использовать встроенные в СУБД средства (например FULLTEXT-индекс для MySQL): достаточно легко внедрить, актуальный поисковый индекс, полный контроль над настройкой и внешним видом, но, чаще всего очень низкая производительность на больших объемах данных, отсутствие учета морфологии, либо, в худшем случае, полное отсутствие подобных средств в СУБД;
  • использовать отдельную библиотеку/систему полнотекствого поиска.

Третий вариант кажется самым лучшим, ведь он сочетает достоинства двух других вариантов. Правда и здесь не обошлось без недостатков — библиотека требует установки, иногда даже запуска демона (например Sphinx), что может быть неприемлемо.

Решений существует масса, у каждого есть свои достоинства и недостатки. Я бы хотел подробнее остановиться на относительно малоизвестной библиотеке Xapian.

Обзор


Эта открытая (GPL) кроссплатформенная библиотека написана на C++, имеются привязки к Python, PHP, Ruby, Perl, Java, Tcl, и C#.

Особенности библиотеки:
  • полная поддержка юникода;
  • булевый поиск, поиск с ранжированием, по маске, синонимам, есть поддержка сортировки результатов;
  • поддержка стемминга для 15 языков мира (включая русский);
  • поддержка коррекции запроса по словарю (например запрос xapain будет заменен на xapian)
  • поиск документов по подобию;
  • поддержка индексации документов в разных форматах из коробки (PDF, HTML, RTF, Microsoft Office, OpenDocument, даже пакеты RPM и Debian), несложно добавить фильтры для поддержки нового формата.

В некотором смысле основным недостатком Xapian являются привязки к языкам программирования отличным от C++. Для генерации кода привязки используется SWIG, поэтому API в ней полностью совпадает с таковым у версии для C++.

К счастью для Python есть простая и эффективная обертка Xappy которая берет на себя всю грязную работу.

Установка


Первым делом нужно установить сам Xapian, привязку к Python и Xappy. В репозиториях большинства дистрибутивов GNU/Linux уже есть все нужные пакеты, например в Ubuntu 10.10 нужно установить пакеты:
sudo apt-get install libxapian15 python-xapian python-xappy

Xappy также доступен через easy_install или pip:
sudo pip install xappy

Индексация


Попробуем что-нибудь проиндексировать:
import xappy

# открытие соединения для индексации с базой поискового индекса
# указывается полный или относительный путь к папке
connection = xappy.IndexerConnection('/path/to/base')

# свойства полей индекса
connection.add_field_action(
    'title', xappy.FieldActions.INDEX_FREETEXT, weight=5, language='ru')
connection.add_field_action(
    'description', xappy.FieldActions.INDEX_FREETEXT, language='ru')

При открытии соединения для индексации будет создана новая (или открыта уже существующая) база поискового индекса — папка с набором файлов. Формат базы независим от операционной системы.

После открытия требуется указать свойства полей индекса: название, тип и другие атрибуты.

Тип поля может быть:
  • INDEX_FREETEXT: в поле хранится текст, требуется создать лишь индекс без хранения самого текста. Для этого типа поля можно указать дополнительные атрибуты, в примере language='ru' для учета морфологии языка и weight=5 — «вес» поля при ранжировании;
  • INDEX_EXACT: в поле хранится точное значение слова (подойдет для поиска точных значений, например идентификаторов книг), текст хранится в индексе;
  • SORTABLE: по полю будет происходить сортировка. По умолчанию сортировка идет в лексикографическом формате независимо от того что в нем хранится. Такое поведение можно изменить через атрибут type='date' для сортировки дат (в формате YYYYMMDD, YYYY-MM-DD или YYYY/MM/DD) и type='float' для сортировки вещественных чисел (в любом поддерживаемом Python формате);
  • COLLAPSE: по полю будет происходить группировка (аналог GROUP BY в SQL, например найти по одному наиболее подходящему под запрос документу в каждой категории);
  • STORE_CONTENT: аналогично INDEX_FREETEXT только текст тоже хранится в индексе.

Для добавления документа подойдет такой код:
# создание нового документа
doc = xappy.UnprocessedDocument()

# заполнение полей
doc.fields.append(xappy.Field('title', 'Какой хороший день'))
doc.fields.append(xappy.Field('description', 'Моя струится лень'))

# добавление в индекс
connection.add(doc)

У каждого документа должен быть уникальный идентификатор, в примере выше он будет добавлен автоматически, однако можно указать свой:
# например индексируем посты в блоге

for posts_item in posts:
    doc = xappy.UnprocessedDocument()

    # не забывайте что идентификатор должен быть 
    # уникальным для поискового индекса!
    doc.id = posts_item.id
    doc.fields.append(xappy.Field('title', posts_item.title))
    doc.fields.append(xappy.Field('description', posts_item.description)

    connection.add(doc)

После добавления документов нужно обязательно записать все изменения на диск и закрыть соединение:
connection.flush()
connection.close()

Все, индекс создан!

Поиск


Для поиска по имеющемуся индексу требуется открыть соединение для поиска по базе поискового индекса:
import xappy
 
# открытие соединения для поиска с базой поискового индекса
# указывается полный или относительный путь к папке
connection = xappy.SearchConnection('/path/to/base')

Возможна ситуация когда новые документы были проиндексированы уже после открытия поискового соединения. В этом случае нужно переоткрыть соединение для получения доступа к актуальной базе:
connection.reopen()

Есть несколько методов для выполнения поискового запроса (класс SearchConnection), самым простым является query_parse:
# обычный поисковый запрос
query = connection.query_parse('день')

# нужны лишь первые 10 результатов
# для следующей десятки нужно указать 10, 20 и т.д.
results = connection.search(query, 0, 10)

# что-то нашлось
if results.matches_estimated > 0:
    for results_item in results:
        print(results_item.rank, results_item.id)
else:
    print('Ничего не найдено')

Для полей с типом STORE_CONTENT или INDEX_EXACT можно вывести их содержимое, что позволяет, например, не выбирать из основной базы данных выбранные записи по ID, а обойтись лишь поисковым индексом:
for results_item in results:
    print(results_item.data['title'])


Ссылки по теме


Конечно это далеко не все на что способен Xapian. Эти и другие возможности более подробно рассмотрены в документации Xappy 0.5, также можно обратиться к официальной документации по Xapian и кое-какие материалы есть в этом блоге о Xapian на английском.
Tags:
Hubs:
+36
Comments 19
Comments Comments 19

Articles