Сегодня, в эпоху Web 2.0, когда контента на сайтах становится все больше и больше, перед разработчиками встает задача реализации полнотекстового поиска.
Вариантов немного:
Третий вариант кажется самым лучшим, ведь он сочетает достоинства двух других вариантов. Правда и здесь не обошлось без недостатков — библиотека требует установки, иногда даже запуска демона (например Sphinx), что может быть неприемлемо.
Решений существует масса, у каждого есть свои достоинства и недостатки. Я бы хотел подробнее остановиться на относительно малоизвестной библиотеке Xapian.
Эта открытая (GPL) кроссплатформенная библиотека написана на C++, имеются привязки к Python, PHP, Ruby, Perl, Java, Tcl, и C#.
Особенности библиотеки:
В некотором смысле основным недостатком Xapian являются привязки к языкам программирования отличным от C++. Для генерации кода привязки используется SWIG, поэтому API в ней полностью совпадает с таковым у версии для C++.
К счастью для Python есть простая и эффективная обертка Xappy которая берет на себя всю грязную работу.
Первым делом нужно установить сам Xapian, привязку к Python и Xappy. В репозиториях большинства дистрибутивов GNU/Linux уже есть все нужные пакеты, например в Ubuntu 10.10 нужно установить пакеты:
Xappy также доступен через easy_install или pip:
Попробуем что-нибудь проиндексировать:
При открытии соединения для индексации будет создана новая (или открыта уже существующая) база поискового индекса — папка с набором файлов. Формат базы независим от операционной системы.
После открытия требуется указать свойства полей индекса: название, тип и другие атрибуты.
Тип поля может быть:
Для добавления документа подойдет такой код:
У каждого документа должен быть уникальный идентификатор, в примере выше он будет добавлен автоматически, однако можно указать свой:
После добавления документов нужно обязательно записать все изменения на диск и закрыть соединение:
Все, индекс создан!
Для поиска по имеющемуся индексу требуется открыть соединение для поиска по базе поискового индекса:
Возможна ситуация когда новые документы были проиндексированы уже после открытия поискового соединения. В этом случае нужно переоткрыть соединение для получения доступа к актуальной базе:
Есть несколько методов для выполнения поискового запроса (класс SearchConnection), самым простым является query_parse:
Для полей с типом STORE_CONTENT или INDEX_EXACT можно вывести их содержимое, что позволяет, например, не выбирать из основной базы данных выбранные записи по ID, а обойтись лишь поисковым индексом:
Конечно это далеко не все на что способен Xapian. Эти и другие возможности более подробно рассмотрены в документации Xappy 0.5, также можно обратиться к официальной документации по Xapian и кое-какие материалы есть в этом блоге о Xapian на английском.
Вариантов немного:
- использовать виджеты от разработчиков поисковых систем (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 на английском.