Статьи

MongoDB Полнотекстовый поиск — наконец-то!

Словарь отступов хедон

Wikimedia Commons

Вчера мы выпустили последнюю нестабильную версию 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избегает возвращений больших ненужных полей. Результаты сортируются с наиболее релевантными первыми, а предел применяется после сортировки.

В заключении

Просто, правда? Новый текстовый указатель обеспечивает простой, полностью согласованный способ выполнения базового поиска без развертывания каких-либо дополнительных служб. Читайте об этом в примечаниях к выпуску .