Elasticsearch-py – работа с Elasticsearch

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

Elasticsearch — это мощная распределённая поисковая система на базе Apache Lucene, предназначенная для полнотекстового поиска, логирования, мониторинга и аналитики больших объёмов данных. Для интеграции с Python используется официальный клиент elasticsearch-py, который предоставляет полный доступ ко всем API Elasticsearch через Python-код.

Библиотека elasticsearch-py позволяет разработчикам реализовать продвинутый поиск, агрегации и обработку больших объёмов данных напрямую в Python-приложениях, включая веб-сервисы, аналитические инструменты и системы мониторинга.

Описание библиотеки elasticsearch-py

Elasticsearch-py — это официальный Python-клиент для работы с Elasticsearch. Библиотека предоставляет низкоуровневый доступ ко всем функциям Elasticsearch через RESTful API, обеспечивая при этом удобный Python-интерфейс.

Основные характеристики библиотеки:

  • Официальная поддержка: Разрабатывается и поддерживается командой Elastic
  • Полная совместимость: Поддерживает все версии Elasticsearch
  • Автоматическое обнаружение узлов: Может автоматически определять и подключаться к узлам кластера
  • Пулы соединений: Эффективное управление соединениями с кластером
  • Обработка ошибок: Встроенная система обработки исключений
  • Сериализация: Автоматическое преобразование Python-объектов в JSON

Установка и подключение к кластеру

Установка

pip install elasticsearch

Для работы с SSL-сертификатами:

pip install elasticsearch[async]

Подключение к локальному кластеру

from elasticsearch import Elasticsearch

es = Elasticsearch("http://localhost:9200")

Подключение к удалённому кластеру с авторизацией

es = Elasticsearch(
    "https://your-cluster:9200",
    basic_auth=("user", "password"),
    verify_certs=True
)

Подключение с использованием API-ключей

es = Elasticsearch(
    "https://your-cluster:9200",
    api_key="your_api_key",
    verify_certs=True
)

Подключение к нескольким узлам

es = Elasticsearch(
    [
        "https://node1:9200",
        "https://node2:9200",
        "https://node3:9200"
    ],
    basic_auth=("user", "password")
)

Основные концепции Elasticsearch

  • Индекс — аналог таблицы в реляционных БД, содержит документы схожей структуры
  • Документ — JSON-объект, хранящийся в индексе, аналог записи в таблице
  • Поле — атрибут документа, аналог колонки в таблице
  • Маппинг (mapping) — описание структуры документа и типов полей
  • Шард — физическая единица хранения данных индекса
  • Реплика — копия шарда для обеспечения отказоустойчивости

Управление индексами

Создание индексов и схем

mapping = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "analyzer": "russian"},
            "tags": {"type": "keyword"},
            "published": {"type": "date"},
            "content": {"type": "text"},
            "author": {"type": "keyword"},
            "views": {"type": "integer"}
        }
    },
    "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 0
    }
}

es.indices.create(index="articles", body=mapping)

Удаление индекса

es.indices.delete(index="articles")

Проверка существования индекса

if es.indices.exists(index="articles"):
    print("Индекс существует")

Получение информации об индексе

index_info = es.indices.get(index="articles")
print(index_info)

Обновление маппинга

new_mapping = {
    "properties": {
        "category": {"type": "keyword"}
    }
}

es.indices.put_mapping(index="articles", body=new_mapping)

Работа с документами

Добавление документа

doc = {
    "title": "Elasticsearch для начинающих",
    "tags": ["поиск", "данные"],
    "published": "2024-01-01",
    "content": "Подробное руководство по работе с Elasticsearch",
    "author": "Иван Иванов",
    "views": 150
}

es.index(index="articles", id=1, document=doc)

Создание документа (только если не существует)

try:
    es.create(index="articles", id=2, document=doc)
except Exception as e:
    print(f"Документ уже существует: {e}")

Получение документа

doc = es.get(index="articles", id=1)
print(doc["_source"])

Обновление документа

# Частичное обновление
es.update(index="articles", id=1, doc={"title": "Обновлённый заголовок"})

# Обновление с помощью скрипта
es.update(
    index="articles",
    id=1,
    script={
        "source": "ctx._source.views += params.increment",
        "params": {"increment": 10}
    }
)

Удаление документа

es.delete(index="articles", id=1)

Проверка существования документа

if es.exists(index="articles", id=1):
    print("Документ существует")

Поиск и фильтрация данных

Простой поиск

query = {
    "query": {
        "match": {
            "title": "Elasticsearch"
        }
    }
}

response = es.search(index="articles", body=query)

Поиск по нескольким полям

query = {
    "query": {
        "multi_match": {
            "query": "поиск данных",
            "fields": ["title", "content"]
        }
    }
}

Фильтрация и булевы запросы

query = {
    "query": {
        "bool": {
            "must": [
                {"match": {"title": "поиск"}}
            ],
            "filter": [
                {"term": {"tags": "данные"}},
                {"range": {"published": {"gte": "2024-01-01"}}}
            ],
            "must_not": [
                {"term": {"author": "anonymous"}}
            ]
        }
    }
}

Точный поиск

query = {
    "query": {
        "term": {
            "tags": "данные"
        }
    }
}

Поиск по фразе

query = {
    "query": {
        "match_phrase": {
            "title": "поиск данных"
        }
    }
}

Поиск с использованием wildcard

query = {
    "query": {
        "wildcard": {
            "title": "*поиск*"
        }
    }
}

Пагинация

response = es.search(
    index="articles",
    body=query,
    from_=0,
    size=10
)

Сортировка результатов

query = {
    "query": {"match_all": {}},
    "sort": [
        {"published": {"order": "desc"}},
        {"views": {"order": "desc"}}
    ]
}

Анализаторы и полнотекстовый поиск

Создание индекса с пользовательским анализатором

settings = {
    "settings": {
        "analysis": {
            "analyzer": {
                "russian_analyzer": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "russian_stop",
                        "russian_stemmer"
                    ]
                }
            },
            "filter": {
                "russian_stop": {
                    "type": "stop",
                    "stopwords": "_russian_"
                },
                "russian_stemmer": {
                    "type": "stemmer",
                    "language": "russian"
                }
            }
        }
    },
    "mappings": {
        "properties": {
            "title": {
                "type": "text",
                "analyzer": "russian_analyzer"
            }
        }
    }
}

es.indices.create(index="articles", body=settings)

Нечёткий поиск (fuzzy)

query = {
    "query": {
        "fuzzy": {
            "title": {
                "value": "поисс",
                "fuzziness": "AUTO"
            }
        }
    }
}

Поиск с автодополнением

query = {
    "query": {
        "match_phrase_prefix": {
            "title": "поиск дан"
        }
    }
}

Агрегации и аналитика

Агрегация по терминам

query = {
    "size": 0,
    "aggs": {
        "tags_count": {
            "terms": {
                "field": "tags",
                "size": 10
            }
        }
    }
}

Статистические агрегации

query = {
    "size": 0,
    "aggs": {
        "views_stats": {
            "stats": {
                "field": "views"
            }
        }
    }
}

Гистограмма по датам

query = {
    "size": 0,
    "aggs": {
        "articles_over_time": {
            "date_histogram": {
                "field": "published",
                "calendar_interval": "month"
            }
        }
    }
}

Вложенные агрегации

query = {
    "size": 0,
    "aggs": {
        "authors": {
            "terms": {
                "field": "author"
            },
            "aggs": {
                "avg_views": {
                    "avg": {
                        "field": "views"
                    }
                }
            }
        }
    }
}

Работа с Bulk API

Массовая загрузка данных

from elasticsearch.helpers import bulk

actions = [
    {
        "_index": "articles",
        "_id": i,
        "_source": {
            "title": f"Article {i}",
            "content": f"Content of article {i}",
            "published": "2024-01-01"
        }
    }
    for i in range(1000)
]

bulk(es, actions)

Массовое обновление

actions = [
    {
        "_op_type": "update",
        "_index": "articles",
        "_id": i,
        "_source": {
            "doc": {"views": i * 10}
        }
    }
    for i in range(1, 100)
]

bulk(es, actions)

Потоковая загрузка данных

from elasticsearch.helpers import streaming_bulk

def generate_docs():
    for i in range(10000):
        yield {
            "_index": "articles",
            "_id": i,
            "_source": {
                "title": f"Article {i}",
                "content": f"Content {i}"
            }
        }

for success, info in streaming_bulk(es, generate_docs(), chunk_size=100):
    if not success:
        print(f"Ошибка: {info}")

Scroll API для больших объёмов данных

Использование scroll для выгрузки данных

def scroll_documents(index, query, scroll_size=1000):
    data = es.search(
        index=index,
        body=query,
        scroll='2m',
        size=scroll_size
    )
    
    scroll_id = data['_scroll_id']
    scroll_size = len(data['hits']['hits'])
    
    while scroll_size > 0:
        # Обрабатываем текущую порцию данных
        for hit in data['hits']['hits']:
            yield hit['_source']
        
        # Получаем следующую порцию
        data = es.scroll(scroll_id=scroll_id, scroll='2m')
        scroll_id = data['_scroll_id']
        scroll_size = len(data['hits']['hits'])
    
    # Очищаем scroll
    es.clear_scroll(scroll_id=scroll_id)

# Использование
query = {"query": {"match_all": {}}}
for doc in scroll_documents("articles", query):
    print(doc)

Обработка ошибок и исключений

from elasticsearch.exceptions import (
    NotFoundError,
    RequestError,
    ConflictError,
    ConnectionError,
    TransportError
)

try:
    es.get(index="articles", id=999)
except NotFoundError:
    print("Документ не найден")
except RequestError as e:
    print(f"Ошибка запроса: {e}")
except ConnectionError:
    print("Ошибка соединения с Elasticsearch")
except TransportError as e:
    print(f"Ошибка транспорта: {e}")

Мониторинг и статистика кластера

Получение информации о кластере

# Здоровье кластера
cluster_health = es.cluster.health()
print(f"Статус кластера: {cluster_health['status']}")

# Статистика узлов
nodes_stats = es.nodes.stats()
print(f"Количество узлов: {len(nodes_stats['nodes'])}")

# Информация об индексах
indices_stats = es.indices.stats()
print(f"Общее количество документов: {indices_stats['_all']['total']['docs']['count']}")

Интеграция с популярными фреймворками

Интеграция с Django

# settings.py
ELASTICSEARCH_DSL = {
    'default': {
        'hosts': 'localhost:9200'
    },
}

# models.py
from django.db import models
from elasticsearch_dsl import Document, Text, Keyword, Date

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published = models.DateTimeField()

class ArticleDocument(Document):
    title = Text()
    content = Text()
    published = Date()
    
    class Index:
        name = 'articles'
    
    def save(self, **kwargs):
        return super().save(**kwargs)

Интеграция с FastAPI

from fastapi import FastAPI, Depends
from elasticsearch import Elasticsearch

app = FastAPI()

def get_es():
    return Elasticsearch("http://localhost:9200")

@app.get("/search/")
async def search_articles(q: str, es: Elasticsearch = Depends(get_es)):
    query = {
        "query": {
            "multi_match": {
                "query": q,
                "fields": ["title", "content"]
            }
        }
    }
    
    response = es.search(index="articles", body=query)
    return response["hits"]["hits"]

Интеграция с Flask

from flask import Flask, request, jsonify
from elasticsearch import Elasticsearch

app = Flask(__name__)
es = Elasticsearch("http://localhost:9200")

@app.route('/search')
def search():
    query = request.args.get('q', '')
    
    search_query = {
        "query": {
            "multi_match": {
                "query": query,
                "fields": ["title", "content"]
            }
        }
    }
    
    response = es.search(index="articles", body=search_query)
    return jsonify(response["hits"]["hits"])

Интеграция с Pandas и анализ данных

import pandas as pd
import matplotlib.pyplot as plt

# Выгрузка данных в DataFrame
def es_to_dataframe(index, query):
    response = es.search(index=index, body=query, size=10000)
    docs = [hit["_source"] for hit in response["hits"]["hits"]]
    return pd.DataFrame(docs)

# Пример использования
query = {"query": {"match_all": {}}}
df = es_to_dataframe("articles", query)

# Анализ данных
print(df.describe())
print(df.groupby('author')['views'].mean())

# Визуализация
df.groupby('author')['views'].sum().plot(kind='bar')
plt.show()

Полная таблица методов и функций elasticsearch-py

Категория Метод Описание Пример использования
Подключение Elasticsearch() Создание клиента es = Elasticsearch("http://localhost:9200")
Документы index() Индексирование документа es.index(index="test", id=1, document=doc)
  create() Создание документа es.create(index="test", id=1, document=doc)
  get() Получение документа es.get(index="test", id=1)
  get_source() Получение только источника es.get_source(index="test", id=1)
  exists() Проверка существования es.exists(index="test", id=1)
  update() Обновление документа es.update(index="test", id=1, doc={"field": "value"})
  delete() Удаление документа es.delete(index="test", id=1)
  update_by_query() Обновление по запросу es.update_by_query(index="test", body=query)
  delete_by_query() Удаление по запросу es.delete_by_query(index="test", body=query)
Поиск search() Поиск документов es.search(index="test", body=query)
  msearch() Множественный поиск es.msearch(body=queries)
  count() Подсчёт документов es.count(index="test", body=query)
  scroll() Прокрутка результатов es.scroll(scroll_id=scroll_id, scroll="2m")
  clear_scroll() Очистка прокрутки es.clear_scroll(scroll_id=scroll_id)
Индексы indices.create() Создание индекса es.indices.create(index="test", body=settings)
  indices.delete() Удаление индекса es.indices.delete(index="test")
  indices.exists() Проверка существования es.indices.exists(index="test")
  indices.get() Получение настроек es.indices.get(index="test")
  indices.put_settings() Обновление настроек es.indices.put_settings(index="test", body=settings)
  indices.get_settings() Получение настроек es.indices.get_settings(index="test")
  indices.put_mapping() Обновление маппинга es.indices.put_mapping(index="test", body=mapping)
  indices.get_mapping() Получение маппинга es.indices.get_mapping(index="test")
  indices.refresh() Обновление индекса es.indices.refresh(index="test")
  indices.flush() Сброс на диск es.indices.flush(index="test")
  indices.close() Закрытие индекса es.indices.close(index="test")
  indices.open() Открытие индекса es.indices.open(index="test")
Кластер cluster.health() Здоровье кластера es.cluster.health()
  cluster.stats() Статистика кластера es.cluster.stats()
  cluster.state() Состояние кластера es.cluster.state()
  cluster.pending_tasks() Ожидающие задачи es.cluster.pending_tasks()
Узлы nodes.info() Информация об узлах es.nodes.info()
  nodes.stats() Статистика узлов es.nodes.stats()
  nodes.hot_threads() Горячие потоки es.nodes.hot_threads()
Bulk операции bulk() Массовые операции es.bulk(body=actions)
  mget() Получение множества документов es.mget(body={"docs": docs})
Helpers helpers.bulk() Упрощённые bulk операции bulk(es, actions)
  helpers.streaming_bulk() Потоковые bulk операции streaming_bulk(es, actions)
  helpers.parallel_bulk() Параллельные bulk операции parallel_bulk(es, actions)
  helpers.scan() Сканирование результатов scan(es, query=query, index="test")
  helpers.reindex() Переиндексация reindex(es, source_index="old", target_index="new")
Шаблоны indices.put_template() Создание шаблона es.indices.put_template(name="template", body=template)
  indices.get_template() Получение шаблона es.indices.get_template(name="template")
  indices.delete_template() Удаление шаблона es.indices.delete_template(name="template")
Alias indices.put_alias() Создание алиаса es.indices.put_alias(index="test", name="alias")
  indices.get_alias() Получение алиаса es.indices.get_alias(name="alias")
  indices.delete_alias() Удаление алиаса es.indices.delete_alias(index="test", name="alias")

Безопасность и авторизация

Настройка SSL/TLS

es = Elasticsearch(
    "https://localhost:9200",
    ca_certs="/path/to/ca.crt",
    client_cert="/path/to/client.crt",
    client_key="/path/to/client.key",
    verify_certs=True
)

Использование API ключей

es = Elasticsearch(
    "https://localhost:9200",
    api_key=("api_key_id", "api_key_secret"),
    verify_certs=True
)

Настройка ролей и доступа

# Создание роли
role_body = {
    "cluster": ["monitor"],
    "indices": [
        {
            "names": ["articles*"],
            "privileges": ["read", "write", "create_index"]
        }
    ]
}

es.security.put_role(name="article_writer", body=role_body)

Тестирование с elasticsearch-py

Настройка тестовой среды

import pytest
from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulk

@pytest.fixture(scope="session")
def es_client():
    return Elasticsearch("http://localhost:9200")

@pytest.fixture(scope="function")
def test_index(es_client):
    index_name = "test_articles"
    
    # Создаём тестовый индекс
    es_client.indices.create(index=index_name, ignore=400)
    
    yield index_name
    
    # Удаляем после теста
    es_client.indices.delete(index=index_name, ignore=[400, 404])

def test_document_indexing(es_client, test_index):
    doc = {"title": "Test Article", "content": "Test content"}
    
    response = es_client.index(index=test_index, id=1, document=doc)
    assert response["result"] == "created"
    
    # Обновляем индекс для немедленного поиска
    es_client.indices.refresh(index=test_index)
    
    # Проверяем, что документ найден
    search_result = es_client.search(
        index=test_index,
        body={"query": {"match": {"title": "Test"}}}
    )
    assert search_result["hits"]["total"]["value"] == 1

Использование Docker для тестов

# docker-compose.test.yml
version: '3.8'
services:
  elasticsearch:
    image: elasticsearch:8.5.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    mem_limit: 1g

Оптимизация производительности

Настройка пула соединений

es = Elasticsearch(
    "http://localhost:9200",
    max_retries=3,
    retry_on_timeout=True,
    timeout=30,
    maxsize=25
)

Использование connection pooling

from elasticsearch import Elasticsearch
from urllib3 import HTTPSConnectionPool

es = Elasticsearch(
    "https://localhost:9200",
    connection_class=HTTPSConnectionPool,
    maxsize=20
)

Асинхронный клиент

from elasticsearch import AsyncElasticsearch
import asyncio

async def async_search():
    es = AsyncElasticsearch("http://localhost:9200")
    
    response = await es.search(
        index="articles",
        body={"query": {"match_all": {}}}
    )
    
    await es.close()
    return response

# Использование
asyncio.run(async_search())

Сравнение с другими решениями

Решение Поиск Агрегации Скорость Масштабируемость Интеграция
Elasticsearch Отлично Отлично Очень высокая Отлично Широкая
PostgreSQL FTS Хорошо Ограничено Средняя Хорошо Стандартная
Solr Отлично Хорошо Высокая Хорошо Хорошая
Typesense Хорошо Частично Очень высокая Хорошо REST API
Meilisearch Хорошо Ограничено Очень высокая Ограничено Простая
Sphinx Хорошо Базовое Высокая Средне Ограниченная

Мониторинг и отладка

Логирование запросов

import logging

# Включаем логирование
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('elasticsearch')
logger.setLevel(logging.DEBUG)

# Создаём клиент с логированием
es = Elasticsearch(
    "http://localhost:9200",
    logging=True
)

Профилирование запросов

query = {
    "profile": True,
    "query": {
        "match": {
            "title": "elasticsearch"
        }
    }
}

response = es.search(index="articles", body=query)
print(response["profile"])

Часто задаваемые вопросы

Что такое elasticsearch-py?

Elasticsearch-py — это официальный Python-клиент для работы с Elasticsearch. Он предоставляет полный доступ к REST API Elasticsearch через удобный Python-интерфейс.

Можно ли использовать Elasticsearch как основную базу данных?

Elasticsearch не рекомендуется использовать как основную базу данных для критически важных данных. Он лучше подходит для поиска, аналитики и временного хранения данных.

Поддерживает ли elasticsearch-py работу с кластером?

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

Как эффективно загружать большие объёмы данных?

Используйте Bulk API через функцию bulk() из модуля helpers. Это позволяет выполнять множество операций за один HTTP-запрос.

Как выполнять сложные агрегации?

Агрегации выполняются через ключ aggs в теле запроса. Elasticsearch поддерживает множество типов агрегаций: terms, stats, date_histogram, nested и другие.

Подходит ли Elasticsearch для автодополнения?

Да, Elasticsearch отлично подходит для реализации автодополнения через запросы типа match_phrase_prefix, completion suggester или search_as_you_type.

Как обеспечить безопасность при работе с Elasticsearch?

Используйте HTTPS-соединения, настройте аутентификацию (basic auth, API keys), ограничьте доступ по IP-адресам и настройте роли пользователей.

Какие есть альтернативы elasticsearch-py?

Существуют высокоуровневые библиотеки как elasticsearch-dsl-py для более удобной работы с запросами, но elasticsearch-py остаётся основным официальным клиентом.

Как отслеживать производительность запросов?

Используйте параметр profile: true в запросах, включите логирование и мониторьте метрики кластера через API статистики.

Можно ли использовать elasticsearch-py в продакшене?

Да, elasticsearch-py стабилен и подходит для использования в продакшене. Важно правильно настроить пулы соединений, обработку ошибок и мониторинг.

Рекомендаций