Начало работы с Elasticsearch в Python. Часть 2

NOP
NOP::Nuances of Programming
5 min readJun 16, 2018

Перевод статьи Adnan Siddiqi: Getting started with Elasticsearch in Python

Предыдущие части: Часть 1

Доступ к ElasticSearch в Python

Если быть честным, то REST API ES достаточно хорош, чтобы вы могли использовать библиотеку запросов для выполнения всех ваших задач. Тем не менее, вы также можете использовать библиотеку Python для ElasticSearch, чтобы в дальнейшем не беспокоиться о том, как создавать те или иные запросы, а сосредоточиться на работе.

Библиотеку Python вы можете установить через pip, после чего вам останется получить к ней доступ в своих Python программах.

pip install elasticsearch

Чтобы убедиться, что библиотека установлена правильно, запустите следующее в вашей командной строке:

➜  elasticsearch-6.2.4 python
Python 3.6.4 |Anaconda custom (64-bit)| (default, Jan 16 2018, 12:04:33)
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from elasticsearch import Elasticsearch
>>> es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
>>> es
<Elasticsearch([{'host': 'localhost', 'port': 9200}])>

Web Scraping и Elasticsearch

Web Scraping (парсинг контента или парсинга сайтов).

Теперь давайте рассмотрим небольшой пример использования Elasticsearch на практике. Наша цель состоит в том, чтобы получить доступ к онлайн-рецептам с сайта и сохранить их в Elasticsearch для проведения дальнейшего поиска и аналитики. Сначала мы очистим данные от всех рецептов, так как все рецепты нам не нужны, и сохраним. Также мы создадим схему, с помощью которой мы убедимся, что наши данные индексируются в правильном формате и типе данных. Затем я просто выбираю рецепты салатов и сохраняю полный список себе.

Полученный контент

Таким образом, это основная программа, которая может парсить данные. Так как нам нужны данные в формате JSON — мне пришлось дополнительно их преобразовать.

Создаем индексы

Окей, мы с вами получили нужные данные и теперь должны их где-то хранить. Самое первое, что нам нужно сделать, это создать индекс. Назовем его recipes. Тип (type) будет называться salads. Далее нам нужно создать мэппинг нашей структуры документа.

Прежде чем создавать индекс, подключитесь к серверу Elasticsearch.

import logging
def connect_elasticsearch():
_es = None
_es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
if _es.ping():
print('Yay Connect')
else:
print('Awww it could not connect!')
return _es
if __name__ == '__main__':
logging.basicConfig(level=logging.ERROR)

_es.ping() фактически пингует сервер и выводит в консоль True, если удалось совершить подключение. Мне потребовалось некоторое время, чтобы выяснить как поймать стек-трейс, в итоге выяснилось, что он просто регистрируется.

def create_index(es_object, index_name='recipes'):
created = False
# index settings
settings = {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"members": {
"dynamic": "strict",
"properties": {
"title": {
"type": "text"
},
"submitter": {
"type": "text"
},
"description": {
"type": "text"
},
"calories": {
"type": "integer"
},
}
}
}
}
try:
if not es_object.indices.exists(index_name):
# Ignore 400 means to ignore "Index Already Exist" error.
es_object.indices.create(index=index_name, ignore=400, body=settings)
print('Created Index')
created = True
except Exception as ex:
print(str(ex))
finally:
return created

На верхнем скриншоте много чего происходит. Во-первых, мы передали конфигурационную переменную, которая отображает всю структуры документа. Мэппинг -это термин в Elasticsearch, обозначающий микроразметку. Подобно тому, как мы устанавливаем определенный тип данных поля в таблицах, мы делаем что-то похожее и здесь. Но чтобы лучше во всем этом разобраться — читайте официальную документацию. Все поля имеют тип text, но у calories тип integer.

Следующий шаг — удостовериться, что индекса вообще не существует и затем создать его. Параметр ignore = 400 можно удалить после проверки. Но в случае, если вы не проверили существование индекса, данный параметр может спровоцировать ошибку и перезаписать индекс. Это будет тоже самое, что перезаписать базу данных.

Когда индекс будет успешно создан, вы можете убедиться в этом, посетив страницу http://localhost:9200/recipes/_mappings. Должно получиться примерно следующее сообщение:

{
"recipes": {
"mappings": {
"salads": {
"dynamic": "strict",
"properties": {
"calories": {
"type": "integer"
},
"description": {
"type": "text"
},
"submitter": {
"type": "text"
},
"title": {
"type": "text"
}
}
}
}
}
}

Передавая dynamic:strict, мы заставляем Elasticsearch выполнять строгую проверку любого входящего документа. Здесь salads является типом document. Тип (type) в Elasticsearch это ответ на таблицы в RDBMS.

Документация индексов

Следующим шагом является хранение фактических данных или документов.

def store_record(elastic_object, index_name, record):
try:
outcome = elastic_object.index(index=index_name, doc_type='salads', body=record)
except Exception as ex:
print('Error in indexing data')
print(str(ex))

Запускаем и вот что мы увидим:

Error in indexing data
TransportError(400, 'strict_dynamic_mapping_exception', 'mapping set to strict, dynamic introduction of [ingredients] within [salads] is not allowed')

Знаете, почему это происходит? Так как мы не добавляли в наш мэппинг компонентов, ES решил не разрешать нам хранить документ, не содержащий, по сути, ничего. Если вы не назначите компоненты мэппингу — произойдет повреждение данных. Теперь давайте немного подправим наш мэппинг и посмотрим, что произойдет:

Мы добавили ingredients типу nested, а затем назначили тип данных внутреннему полю. В нашем случае это text.

Вложенный тип данных (nested) позволяет установить тип вложенных объектов JSON. Запустите снова и взгляните на результат:

{
'_index': 'recipes',
'_type': 'salads',
'_id': 'OvL7s2MBaBpTDjqIPY4m',
'_version': 1,
'result': 'created',
'_shards': {
'total': 1,
'successful': 1,
'failed': 0
},
'_seq_no': 0,
'_primary_term': 1
}

Так как вы не передали _id вообще, ES сам назначил динамический идентификатор сохраненному документу. Я использую браузер Chrome и ES data viewer с помощью инструмента Elasticsearch Toolbox для просмотра данных.

Прежде чем двигаться дальше, отправим строку в поле calories и посмотрим, как развернутся события. Вспомним тот факт, что мы присвоили ему тип integer. Учитывая это, при индексировании он выдаст следующую ошибку:

TransportError(400, ‘mapper_parsing_exception’, ‘failed to parse [calories]’)

Итак, теперь вы знаете преимущества назначения мэппинга для ваших документов. Если вы этого не сделаете, все равно все будет работать, поскольку Elasticsearch назначит свой собственный мэппинг во время среды выполнения.

Запрос записей

Теперь, когда записи индексируются, пришло время их запросить, в соответствии с нашими потребностями. Я собираюсь создать функцию и назвать ее search(). Она будет выводить на экран результаты наших запросов:

def search(es_object, index_name, search):
res = es_object.search(index=index_name, body=search)

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

if __name__ == '__main__':
es = connect_elasticsearch()
if es is not None:
search_object = {'query': {'match': {'calories': '102'}}}
search(es, 'recipes', json.dumps(search_object))

В приведенном выше запросе вернутся все записи, в которых calories равны 102. В нашем случае вывод будет:

А что делать, если вы хотите получить записи, в которых calories больше, чем 20?

'_source': ['title'], 'query': {'range': {'calories': {'gte': 20}}}}

Вы также можете указать, какие столбцы или поля вам хотелось бы вернуть. Приведенный выше запрос вернет все записи, в которых calories больше, чем 20. Кроме того, он будет отображать поле title только под _source.

Заключение

Elasticsearch — это мощный инструмент, который поможет сделать уже существующие или грядущие приложения доступными для поиска, предоставляя надежные функции для возврата наиболее точного набора результатов. Читайте официальные документы, мануалы и инструкции. Позвольте Elasticsearch упростить вашу жизнь. По традиции, весь код доступен на Github.

Спасибо за прочтение!

--

--