вторник, 6 октября 2015 г.

Нечёткий поиск с Elasticsearch

Что такое Elasticsearch прекрасно описано в wikipedia, поэтому здесь это описывать не вижу смысла. Совсем кратко и на свой лад — замечательная штука для поиска по большой базе (десятки тысяч документов, но не миллиарды) с возможностью нечёткого поиска, т.е. в запросе могут быть ошибки, опечатки, пропуски или даже не та раскладка. Замечательность elasticsearch'а в простоте разворачивания, простоте наполнения базы и простоте поиска.

Перед началом работы нужно установить Elasticsearch из пакетов, либо скачать архив и распаковать. После остаётся его запустить (bin/elasticsearch) и можно смотреть в браузере (http://localhost:9200/) как оно работает.

Для "потестить" можно всё делать через браузер/curl.

Добавление документа
$ curl -XPUT 'http://localhost:9200/test/doc/1' -d '{"name":"имя пользователя"}'
здесь
    test — индекс
    doc — doc_type (можно считать подиндексом индекса)

Поиск
$ curl -XGET 'http://localhost:9200/test/doc/_search?pretty=true' -d '{"query":{"fuzzy":{"name":"плзователь"}}}'
Ну или смотрим в браузере
(pretty=true в запросе просто чтобы в ответ получить json не в одну строку, а нормально отформатированный)


Для работы с Elasticsearch из python'а есть "родной" модуль elasticsearch и сторонний pyelasticsearch. Сторонний появился чуть раньше официального, но с определённой версии работает через официальный (т.е. для работы с ним нужно ставить оба модуля), развивается и дальше и старается быть более pythonic, нежели официальный.




Дальше на правах заметки "только чтобы не забыть" просто выдержки из кода:


Наполнение индекса кучей данных:

# -*- coding: utf-8 -*-

from pyelasticsearch import ElasticSearch, bulk_chunks
import json

# Данных много, данные грузим из файла
with open('file_with_data.json') as datafile:
    docs = json.load(datafile)

# На случай проблем с кириллицей перекодируем
for d in docs['documents']:
    d['title'] = d['title'].encode("utf-8", "ignore")
    d['title_full'] = d['title_full'].encode("utf-8", "ignore")

es = ElasticSearch('http://localhost:9200')

if not len(docs['documents']):
    exit

# Clear index
print es.delete_index('documents')

# Данных очень много, поэтому грузить в индекс будем чанками
# и следить за прогрессом в консоли (корявый вариант влоб)
cnt = 0
max_cnt = len(docs['documents']) / 100
print "max = %s" % max_cnt
for actions in bulk_chunks((es.index_op(doc, id=doc.pop('id')) for doc in docs['documents']), docs_per_chunk=100):
    cnt += 1
    print "%s of %s" % (cnt, max_cnt)
    es.bulk(actions, index='documents', doc_type='full')




Поиск по этой куче данных:

# Простой вариант запроса (обычно хватает)
query = 'title:' + string_query


# Запрос с выставлением своих параметров
query = {
        'query': {
            'multi_match': {
                'query': re.sub('"', 
'', string_query),
                'fields': ['title', 'title_full']
            }
        }
}


# Поиск
res = es.search(query, index='documents', doc_type='full')

print res['hits']


Список найденных документов будет в res['hits']['hits']


В моём случае акция была разовая, поэтому наполнение релизовано довольно грубо. Сперва добавлял документы по-одному через

es.index('documents',
         'full',
         {'title': 'title master title', 'full_title': 'QA Master'},
          id=1)


но на моём объёме данных elasticsearch переставал отвечать на запросы (поиск по проиндексированному) и уходил в постоянный жор одного ядра на 100%, в общем со стороны выглядел вполне залипшим на какой-то своей внутренней проблеме. Минут за десять я так и не дождался нормального поведения и просто переписал заливку данных на чанки по сто документов, добавив хоть какой-то вывод в консоль, чтобы понимать зилип/не_залип.

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

Комментариев нет:

Отправить комментарий