Вчера мы выпустили последнюю нестабильную версию MongoDB ; особенность заголовка — основной полнотекстовый поиск. Вы можете прочитать все о полнотекстовом поиске MongoDB в примечаниях к выпуску .
В этом блоге использовался действительно ужасный метод поиска, включающий регулярные выражения, полное сканирование коллекции для каждого поиска и отсутствие ранжирования результатов по релевантности. Я хотел заменить весь этот промысел полнотекстовым поиском MongoDB как можно скорее. Вот что я сделал.
Простой текст
Мой блог написан на Markdown и отображается как HTML. На самом деле я хочу найти простой текст сообщений, поэтому я настроил стандарт Python HTMLParser
для удаления тегов из HTML:
import re
from HTMLParser import HTMLParser
whitespace = re.compile('\s+')
class HTMLStripTags(HTMLParser):
"""Strip tags
"""
def __init__(self, *args, **kwargs):
HTMLParser.__init__(self, *args, **kwargs)
self.out = ""
def handle_data(self, data):
self.out += data
def handle_entityref(self, name):
self.out += '&%s;' % name
def handle_charref(self, name):
return self.handle_entityref('#' + name)
def value(self):
# Collapse whitespace
return whitespace.sub(' ', self.out).strip()
def plain(html):
parser = HTMLStripTags()
parser.feed(html)
return parser.value()
Вывод несовершенен — он добавляет дополнительные пробелы вокруг знаков препинания и обычно создает небольшой беспорядок — но он не предназначен для публикации в New Yorker, он предназначен для индексации.
Я написал скрипт, который проходит через все мои существующие посты , извлекает простой текст и сохраняет его в новом поле в каждом названном документе plain
. Я также обновил код моего блога, так что теперь он создает plain
поле для каждого сообщения, когда я сохраняю сообщение.
Создание индекса
Я установил MongoDB 2.3.2 и запустил его с помощью этой опции командной строки:
--setParameter textSearchEnabled=true
Без этой опции создание текстового индекса вызывает ошибку сервера, «текстовый поиск не включен».
Затем я создал текстовый указатель на заголовки сообщений, названия категорий, теги и простой текст, сгенерированный выше. Я могу установить разные значения релевантности для каждого поля. Заголовок больше всего влияет на оценку релевантности поста, за ним следуют категории и теги, и, наконец, текст. В Python объявление индекса выглядит так:
db.posts.create_index(
[
('title', 'text'),
('categories.name', 'text'),
('tags', 'text'), ('plain', 'text')
],
weights={
'title': 10,
'categories.name': 5,
'tags': 5,
'plain': 1
}
)
Обратите внимание, что вам нужно установить PyMongo из текущего мастера в GitHub или дождаться PyMongo 2.4.2 для создания текстового индекса. PyMongo 2.4.1 и более ранние версии выдают исключение:
TypeError: second item in each key pair must be ASCENDING, DESCENDING, GEO2D, or GEOHAYSTACK
Если вы не хотите обновлять PyMongo, просто используйте оболочку mongo для создания индекса:
db.posts.createIndex( { title: 'text', 'categories.name': 'text', tags: 'text', plain: 'text' }, { weights: { title: 10, 'categories.name': 5, tags: 5, plain: 1 } } )
Поиск в индексе
Чтобы использовать текстовый индекс, я не могу сделать нормальный find
, я должен запустить text
команду. В моем асинхронном драйвере Motor это выглядит так:
response = yield motor.Op(self.db.command, 'text', 'posts',
search=q,
filter={'status': 'publish', 'type': 'post'},
projection={
'display': False,
'original': False,
'plain': False
},
limit=50)
q
Переменное , что вы ввели в поле поиска слева, как «Монго» или «хомяк» или «нить местный Пайтона странные». В filter
опции гарантирует только опубликованные сообщения будут возвращены, и projection
избегает возвращений больших ненужных полей. Результаты сортируются с наиболее релевантными первыми, а предел применяется после сортировки.
В заключении
Просто, правда? Новый текстовый указатель обеспечивает простой, полностью согласованный способ выполнения базового поиска без развертывания каких-либо дополнительных служб. Читайте об этом в примечаниях к выпуску .