Python 操作 Elasticsearch 超全实战教程
Python 操作 Elasticsearch 超全实战教程
前言
Elasticsearch(简称 ES)是目前最主流的分布式全文搜索引擎,广泛用于日志检索、业务模糊查询、内容搜索、数据分析等场景。Python 操作 ES 分为同步和异步两种方式,在 Web 异步项目(FastAPI/Starlette)、爬虫、高并发业务中,异步 AsyncElasticsearch 是最优选择,不会阻塞事件循环,并发性能更强。
本文基于 elasticsearch 8.x 版本,从零讲解 Python 异步操作 ES 全套核心用法,包含:异步连接配置、索引管理、文档 CRUD、各类常用查询,所有代码可直接运行,解决新手连接报错、连接不释放、查询不会写等问题。
一、环境准备
1.1 安装依赖
安装官方 ES 异步客户端依赖:
pip install elasticsearch
1.2 环境说明
-
ES 服务:本地 localhost:9200(无账号密码、无 HTTPS)
-
Python 版本:3.8+
-
客户端:AsyncElasticsearch(官方异步客户端)
-
规避问题:自动管理连接,杜绝 Unclosed connector 连接泄露警告
二、ES 异步连接初始化(最佳实践)
核心要点:使用 async with 上下文管理器,自动创建、释放连接,无需手动关闭,彻底解决连接未关闭警告,是生产环境标准写法。
import asyncio
from elasticsearch import AsyncElasticsearch
async def get_es_client() -> AsyncElasticsearch:
"""
【单函数封装】获取异步ES客户端实例 + 校验连接状态
整合原有连接创建、连接测试逻辑,极简调用,自动管理连接
:return: 可用的异步ES客户端对象
"""
# 初始化ES异步客户端
client = AsyncElasticsearch(
hosts=["http://localhost:9200"],
request_timeout=30, # 请求超时时间
retry_on_timeout=True # 超时自动重试
)
# 校验连接有效性
is_connect = await client.ping()
if is_connect:
print("✅ ES 异步连接成功!")
else:
print("❌ ES 连接失败!请检查ES服务是否启动、地址是否正确")
return client
# 调用示例
if __name__ == "__main__":
# 一行获取可直接使用的ES客户端
es_client = asyncio.run(get_es_client())
三、索引(Index)核心操作
索引可以理解为 MySQL 的数据表,是存储文档数据的载体,操作包含:查询是否存在、创建索引、删除索引、获取索引映射。
3.1 索引字段类型说明
日常开发最常用两种字段类型:
-
text(分词文本类型):支持分词、全文模糊检索,不支持精确匹配、聚合排序。适用于用户名、简介、文章内容、地址描述等需要模糊搜索的文本场景,检索时会根据词条重合度计算相关性分数。
-
keyword(精确文本类型):不分词、完整字符串匹配,支持精确查询、聚合、排序、过滤。适用于用户ID、手机号、分类标签、城市名称、状态码等不需要模糊匹配的固定文本字段。
-
integer(整型数值):存储整数数字,支持数值区间筛选、大小排序、聚合统计。适用于年龄、浏览量、点赞数、状态数值等整型业务字段。
-
long(长整型):超大整数存储,取值范围远大于integer,适用于订单号、日志ID、超大计数等数值过大的场景。
-
float/double(浮点型):存储小数数值,float精度较低、double高精度,适用于价格、评分、比例、经纬度小数数据。
-
boolean(布尔类型):仅存储 true/false,适用于是否启用、是否删除、是否VIP等二元状态字段。
-
date(时间日期类型):专用时间类型,支持时间范围筛选、时间排序,适配yyyy-MM-dd、yyyy-MM-dd HH:mm:ss、时间戳等格式,比字符串存储更节省空间、查询效率更高。
-
array(数组类型):无单独声明类型,字段写入数组即可自动识别,数组内元素类型统一,适用于用户标签、多分类、多角色等多值场景。
-
object(对象类型):存储嵌套JSON对象,支持多层级字段映射,适用于用户拓展信息、地址详情、设备信息等结构化嵌套数据。
3.2 完整索引操作代码
async def index_operate():
idx_name = "user_test" # 定义索引名称
async with get_es_client() as client:
# 1. 判断索引是否存在
index_exists = await client.indices.exists(index=idx_name)
print(f"索引是否存在: {index_exists}")
# 2. 不存在则创建索引 + 自定义映射
if not index_exists:
await client.indices.create(
index=idx_name,
mappings={
"properties": {
"user_id": {"type": "keyword"}, # 精确匹配
"username": {"type": "text"}, # 模糊检索
"age": {"type": "integer"}, # 数值类型
"city": {"type": "keyword"} # 城市精确筛选
}
}
)
print(f"✅ 索引【{idx_name}】创建成功")
# 3. 获取索引映射结构
mapping = await client.indices.get_mapping(index=idx_name)
print("📋 索引结构:", mapping["user_test"]["mappings"]["properties"])
# 4. 删除索引(谨慎使用!)
# await client.indices.delete(index=idx_name)
# print(f"❌ 索引【{idx_name}】删除成功")
if __name__ == "__main__":
asyncio.run(index_operate())
四、文档(Document)CRUD 操作
ES 文档对应 MySQL 的一行数据,是 JSON 格式数据,核心操作:新增、查询、修改、删除、批量插入。
4.1 单条文档新增/查询/修改/删除
async def doc_crud():
idx_name = "user_test"
async with get_es_client() as client:
# 1. 新增文档(指定文档id,不指定则ES自动生成)
doc_data = {
"user_id": "u001",
"username": "王五",
"age": 18,
"city": "北京"
}
# 创建/覆盖文档
await client.index(index=idx_name, id="1", document=doc_data)
print("✅ 文档新增/更新成功")
# 2. 根据ID查询单条文档
res = await client.get(index=idx_name, id="1")
print("📄 单条文档数据:", res["_source"])
# 3. 更新文档(局部更新,只修改指定字段)
update_data = {"doc": {"age": 19, "city": "上海"}}
await client.update(index=idx_name, id="1", doc=update_data["doc"])
print("✅ 文档局部更新成功")
# 4. 删除单条文档
# await client.delete(index=idx_name, id="1")
# print("❌ 文档删除成功")
if __name__ == "__main__":
asyncio.run(doc_crud())
4.2 批量插入文档(高效批量写入)
批量插入适合初始化数据、批量同步业务数据,比单条插入性能提升数十倍:
from elasticsearch.helpers import async_bulk
async def batch_insert_doc():
idx_name = "user_test"
async with get_es_client() as client:
# 构造批量数据
bulk_data = [
{"_index": idx_name, "_id": 2, "_source": {"user_id": "u002", "username": "张三", "age": 20, "city": "北京"}},
{"_index": idx_name, "_id": 3, "_source": {"user_id": "u003", "username": "李四", "age": 22, "city": "广州"}},
{"_index": idx_name, "_id": 4, "_source": {"user_id": "u004", "username": "王五", "age": 25, "city": "深圳"}}
]
# 异步批量插入
success, fail = await async_bulk(client=client, actions=bulk_data)
print(f"✅ 批量插入成功:{success} 条,失败:{fail} 条")
if __name__ == "__main__":
asyncio.run(batch_insert_doc())
五、ES 核心查询操作(最全业务场景)
本章覆盖企业开发100%常用ES查询语法,包含基础查询、精准查询、模糊查询、组合条件、高亮、排序、分页、去重、聚合统计等所有核心能力,适配搜索、筛选、统计、后台列表等各类业务场景,所有异步代码开箱即用。
这是开发最常用的部分,包含:全量查询、模糊检索、精确匹配、条件筛选、否定查询、分页查询,同时解答你之前的「多字少字能否查询」问题。
5.1 全量查询(查询所有数据)
async def search_all():
idx_name = "user_test"
async with get_es_client() as client:
res = await client.search(
index=idx_name,
query={"match_all": {}} # 匹配所有文档
)
# 解析结果
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("📋 所有数据:", result_list)
if __name__ == "__main__":
asyncio.run(search_all())
5.2 全文模糊查询(match 分词查询)
核心特性:文本字段分词匹配,搜索词多字、少字、部分匹配都能查到,适合搜索内容模糊匹配。
例:文档内容「王五」,搜索「王五测试」也能匹配到(部分词条重合)
async def search_match():
idx_name = "user_test"
async with get_es_client() as client:
# 模糊匹配username字段
res = await client.search(
index=idx_name,
query={
"match": {
"username": "王五测试"
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 模糊查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_match())
5.3 精确匹配查询(term 关键字查询)
核心特性:针对 keyword 字段,严格完整匹配字符串,多字少字都查不到,适合ID、城市、状态筛选。
async def search_term():
idx_name = "user_test"
async with get_es_client() as client:
# 精确匹配city=北京
res = await client.search(
index=idx_name,
query={
"term": {
"city": "北京"
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 精确查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_term())
5.4 条件筛选 + 否定查询(must_not)
解决你之前的「不在某地、不包含某内容」查询需求,ES 不识别自然语言「不」,需手动用 must_not 实现否定筛选。
async def search_must_not():
idx_name = "user_test"
async with get_es_client() as client:
# 查询:城市 不是北京 的所有用户
res = await client.search(
index=idx_name,
query={
"bool": {
"must_not": [
{"term": {"city": "北京"}}
]
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 非北京用户:", result_list)
if __name__ == "__main__":
asyncio.run(search_must_not())
5.5 分页查询 + 数值范围筛选
async def search_page():
idx_name = "user_test"
async with get_es_client() as client:
# 分页参数:第1页,每页2条
page = 1
size = 2
res = await client.search(
index=idx_name,
from_=(page - 1) * size,
size=size,
query={
"bool": {
"filter": [ # filter无打分、不影响相关性,查询性能更高
{"range": {"age": {"gte": 20, "lte": 30}}} # 年龄20-30区间
]
}
}
)
total = res["hits"]["total"]["value"]
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print(f"✅ 总条数:{total},分页数据:", result_list)
if __name__ == "__main__":
asyncio.run(search_page())
5.6 短语精确匹配查询(match_phrase)
核心特性:要求搜索词条连续、顺序一致,无错乱、无遗漏,严格匹配完整短句,解决你之前「多字少字查不到」的精准场景。
async def search_match_phrase():
idx_name = "user_test"
async with get_es_client() as client:
# 精准匹配连续短语,顺序必须一致
res = await client.search(
index=idx_name,
query={
"match_phrase": {
"username": "王五"
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 短语精确查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_match_phrase())
5.7 多字段联合查询(multi_match)
核心特性:同时对多个text字段模糊检索,只需匹配任意一个字段即可命中,适合全站搜索场景。
async def search_multi_match():
idx_name = "user_test"
async with get_es_client() as client:
# 同时匹配 username、city 两个字段
res = await client.search(
index=idx_name,
query={
"multi_match": {
"query": "北京 王五",
"fields": ["username", "city"] # 指定查询字段
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 多字段联合查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_multi_match())
5.8 模糊纠错查询(fuzzy)
核心特性:支持错别字、少字、多字纠错匹配,适合用户输入错误的搜索场景(例:输入“王武”匹配“王五”)。
async def search_fuzzy():
idx_name = "user_test"
async with get_es_client() as client:
res = await client.search(
index=idx_name,
query={
"fuzzy": {
"username": {
"value": "王武", # 错误输入
"fuzziness": 1 # 允许1个字符误差
}
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 模糊纠错查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_fuzzy())
5.9 通配符查询(wildcard)
核心特性:支持占位符匹配,*匹配任意多个字符,?匹配单个字符,仅适用于keyword/text字段。
async def search_wildcard():
idx_name = "user_test"
async with get_es_client() as client:
# 匹配所有 王开头 的用户名
res = await client.search(
index=idx_name,
query={
"wildcard": {
"username": "王*"
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 通配符查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_wildcard())
5.10 多条件组合查询(must + must_not + should + filter)
Bool查询四剑客,企业最常用组合查询:
-
must:必须匹配,参与相关性打分
-
must_not:必须不匹配,不打分
-
should:可选匹配,匹配到加分,不匹配不影响
-
filter:必须匹配,无打分、性能最优
async def search_bool_all():
idx_name = "user_test"
async with get_es_client() as client:
# 需求:年龄20-30、不是广州、用户名包含王、优先北京用户
res = await client.search(
index=idx_name,
query={
"bool": {
"must": [{"match": {"username": "王"}}],
"must_not": [{"term": {"city": "广州"}}],
"should": [{"term": {"city": "北京"}}],
"filter": [{"range": {"age": {"gte": 20, "lte": 30}}}]
}
}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 多条件组合查询结果:", result_list)
if __name__ == "__main__":
asyncio.run(search_bool_all())
5.11 搜索结果高亮显示(highlight)
核心特性:自动给匹配关键词添加标签高亮,前端直接渲染,文章搜索必备功能。
async def search_highlight():
idx_name = "user_test"
async with get_es_client() as client:
res = await client.search(
index=idx_name,
query={"match": {"username": "王五"}},
highlight={
"fields": ["username"], # 高亮字段
"pre_tags": ["<span style='color:red'>"],
"post_tags": ["</span>"]
}
)
# 解析高亮结果
for hit in res["hits"]["hits"]:
print("原数据:", hit["_source"])
print("高亮关键词:", hit.get("highlight", {}))
if __name__ == "__main__":
asyncio.run(search_highlight())
5.12 结果排序查询(sort)
支持字段升序、降序排序,支持多字段优先级排序。
async def search_sort():
idx_name = "user_test"
async with get_es_client() as client:
# 先按年龄降序,年龄相同按id升序
res = await client.search(
index=idx_name,
sort=[
{"age": "desc"},
{"user_id": "asc"}
],
query={"match_all": {}}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 排序后数据:", result_list)
if __name__ == "__main__":
asyncio.run(search_sort())
5.13 去重查询(collapse)
核心特性:根据指定字段去重,保留每条唯一数据,适配重复数据过滤场景。
async def search_collapse():
idx_name = "user_test"
async with get_es_client() as client:
# 根据city字段去重,每个城市只保留一条数据
res = await client.search(
index=idx_name,
collapse={"field": "city"},
query={"match_all": {}}
)
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print("🔍 去重后数据:", result_list)
if __name__ == "__main__":
asyncio.run(search_collapse())
5.14 简单聚合查询(分组统计)
实现分组计数、平均值、最值统计,替代数据库group by,适配大数据统计场景。
async def search_agg():
idx_name = "user_test"
async with get_es_client() as client:
res = await client.search(
index=idx_name,
size=0, # 不需要原始数据,只看统计结果
aggs={
# 按城市分组统计数量
"group_by_city": {
"terms": {"field": "city"}
},
# 年龄平均值、最大最小值
"age_stat": {
"stats": {"field": "age"}
}
}
)
print("📊 城市分组统计:", res["aggregations"]["group_by_city"]["buckets"])
print("📊 年龄统计:", res["aggregations"]["age_stat"])
if __name__ == "__main__":
asyncio.run(search_agg())
async def search_page():
idx_name = "user_test"
async with get_es_client() as client:
# 分页参数:第1页,每页2条
page = 1
size = 2
res = await client.search(
index=idx_name,
from_=(page - 1) * size,
size=size,
query={
"bool": {
"filter": [ # filter无打分,查询性能更高
{"range": {"age": {"gte": 20}}} # 年龄>=20
]
}
}
)
total = res["hits"]["total"]["value"]
result_list = [hit["_source"] for hit in res["hits"]["hits"]]
print(f"✅ 总条数:{total},分页数据:", result_list)
if __name__ == "__main__":
asyncio.run(search_page())
5.15 搜索匹配核心规则总结(解答多字/少字匹配问题)
-
text字段 + match查询:部分词条重合即可命中,多字、少字、语序错乱均可查到,分数随匹配度变化
-
text字段 + match_phrase短语查询:必须词条连续、顺序完全一致,轻微差异即查不到,用于精准短句匹配
-
keyword字段 + term查询:严格全量字符串匹配,多字少字、符号差异均无法命中,用于精确筛选
-
fuzzy模糊查询:支持错别字、字符误差匹配,适配用户输入错误场景
-
wildcard通配符查询:支持前后模糊匹配,适配前缀、后缀检索
六、全文总结
1. 异步 ES 核心优势:高并发不阻塞,适配异步 Python 项目,生产环境首选AsyncElasticsearch + async with;
2. 索引操作核心是字段映射区分 text/keyword,模糊查 text、精确查 keyword;
3. 文档操作优先使用批量写入,提升性能,局部更新使用 update 避免覆盖全量数据;
4. 查询核心:match 模糊、term 精确、must_not 否定、filter 条件筛选,覆盖 90% 业务场景。
(注:部分内容可能由 AI 生成)
更多推荐


所有评论(0)