跳至内容

Elasticsearch 搜索与索引

倒排索引、中文分词与文档检索,以及过滤、排序和聚合的区别。

  • Elasticsearch
  • 全文检索
  • 倒排索引

Elasticsearch 把文档中的内容组织成可查询的索引,提供全文检索、字段过滤和聚合统计。 搜索一段文字时,它会分析查询中的词项,查找匹配文档,并按相关性或指定字段排序。

Elasticsearch 简称 ES,底层使用 Lucene。 Kibana 提供查询和可视化界面;Python 客户端直接访问 ES,不依赖 Kibana。

官网:https://www.elastic.co/elasticsearch

1. 文档与索引

1.1 文档、字段和 Mapping

一篇文章、一件商品或一条日志,都能表示成一份 JSON 文档(Document)。文档中的 title、category 等属性称为字段(Field)。

索引(Index) 组织一组文档,Mapping 规定字段的类型及其处理方式。

正文需要分词,类别需要按完整值匹配,发布时间需要按日期比较。

概念本文示例作用
索引article_demo_es保存用于检索的文章文档
文档ID 为 101 的文章一条完整的检索记录
字段title、text、category保存标题、正文和类别
Mappingtext 为文本,category 为关键词决定字段如何索引和查询
文档 ID_id="101"在同一个索引内标识文档

索引与关系数据库的表、文档与行,可以作为理解数据组织的粗略对应。但 ES 的 Mapping 不提供关系表的外键约束,多个文档的写入也不能当成一个数据库事务。

业务数据保存在 MySQL、ES 保存搜索副本时,应用或同步任务需要把业务库的修改写入 ES。

1.2 正排索引与倒排索引

文本经过分析得到的检索单位称为词项(Term)。正排索引和倒排索引保存这些词项与记录的对应关系,区别在于查找方向:

  • 正排索引按记录 ID 保存词项列表,回答“一条记录包含哪些词项”。
  • 倒排索引按词项保存记录 ID 列表,回答“哪些记录包含这个词项”。

假设商品库有三件商品,每条商品记录是一份 ES 文档。下面按给定词项拆分商品名称,仅演示索引关系,不代表后文 cjk 分析器的实际输出。

正排索引:按商品编号查看词项。

商品编号商品名称词项
P1红色运动鞋红色、运动鞋
P2蓝色运动鞋蓝色、运动鞋
P3红色背包红色、背包

已知编号 P1,能查到“红色、运动鞋”。如果只有这张正排表,想找出所有运动鞋,就需要逐条检查词项列表。

倒排索引:按词项查找商品编号。

词项商品编号
红色P1、P3
蓝色P2
运动鞋P1、P2
背包P3

查“运动鞋”即可找到 P1、P2。如果要求同时匹配“红色”和“运动鞋”,两组编号的交集只剩 P1;任意一个词项匹配即可时,并集为 P1、P2、P3。

倒排索引让词项查询直接定位候选记录,还能按字段配置保存词频、位置,供相关性评分和短语查询使用。正排索引与倒排索引描述的是数据组织方式,不能直接等同于 MySQL 与 ES 的区别。

词项不一定是完整词语。第 3 节的 cjk 分析器会把“全文检索”拆为“全文”“文检”“检索”。

Index 是文档容器,倒排索引是内部检索结构。 日期范围查询、排序和聚合还会使用其他数据结构。

1.3 节点、分片与副本

ES 服务进程称为节点(Node),多个节点组成集群(Cluster)。索引的数据分布在主分片(Primary shard) 中,每个分片是一个独立的 Lucene 索引。

  • 主分片拆分索引的数据。一篇文档通过路由定位到其中一个主分片。
  • 副本分片保存对应主分片的数据副本,能够服务搜索请求;主分片失效时,符合条件的副本可以被提升为主分片。
  • 节点承载分片。一个节点可以保存多个分片,分片数量不等于节点数量。

一个索引配置 2 个主分片、每个主分片 1 个副本,总共需要分配 4 份分片。对应的主分片与副本不能放在同一节点上。

一次搜索通常会从每个主分片及其副本中选择一份执行查询,由接收请求的协调节点汇总结果。

分片或副本越多,不代表查询一定更快。 分片增加协调开销,副本增加存储与复制开销。

副本不能代替备份。 误删除会被复制,恢复历史数据需要单独的快照。

2. 本地部署

2.1 启动服务

示例环境:ES 9.2.0、Python 3.13。Linux 安装 Docker Engine + Compose V2,macOS 安装 Docker Desktop。

Docker Desktop 至少分配 4 GB 内存,本例 ES 容器限用 2 GB。

在新目录中保存 compose.yaml:

代码块 Yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.2.0
    environment:
      discovery.type: single-node
      node.name: es01
      xpack.security.enabled: "false"
    ports:
      - "127.0.0.1:9200:9200"
    mem_limit: 2g
    volumes:
      - es-data:/usr/share/elasticsearch/data

volumes:
  es-data:

这份配置关闭认证,端口仅绑定 127.0.0.1。 自建 ES 默认启用认证与 TLS;远程服务需要按实际证书和凭据连接,不能直接套用这份配置对外开放。

在 compose.yaml 所在目录启动并检查服务:

代码块 Shell
1
2
3
4
docker compose up -d
docker compose ps
docker compose logs --tail=80 elasticsearch
curl --fail http://localhost:9200

容器运行中不等于接口已就绪。 curl 返回包含 version.number 的 JSON,表示 HTTP 接口已能响应;这里的版本应为 9.2.0。

9200 提供 HTTP API。节点间通信使用的 9300 无需在这个单节点示例中映射到宿主机。

2.2 Python 连接

创建虚拟环境(python3 为 Python 3.13):

代码块 Shell
1
2
3
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "elasticsearch==9.2.0"

在宿主机连接 ES,后续示例沿用当前 Python 会话:

代码块 Python
1
2
3
4
5
from elasticsearch import Elasticsearch, helpers

client = Elasticsearch("http://localhost:9200", request_timeout=30, max_retries=0)
print(client.info()["version"]["number"])
# 预期输出:9.2.0

request_timeout 是单次请求的客户端等待时间,单位为秒。max_retries=0 关闭客户端自动重试,便于在练习中直接看到失败。

宿主机连接 localhost:9200;同一 Compose 网络中的容器连接 elasticsearch:9200。HTTPS 服务需按配置提供 CA 证书和认证信息。

3. 字段与分词

3.1 text 与 keyword

  • text 对文本做分析,适合全文检索。
  • keyword 保留完整值形成词项,适合类别、标签和标识符。

本例 keyword 未配置额外的规范化规则。

字段类型本文用途
doc_idkeyword保存一份业务 ID,作为排序的次级条件
titletext搜索标题
title.rawkeyword按完整标题匹配或排序
texttext搜索正文
categorykeyword按类别过滤、统计数量
published_atdate按日期筛选和排序

title.raw 是 title 的多字段(Multi-field):同一个输入值按两种方式建立索引。写入文档时只传 title,不需要另传 raw。

_id 属于文档元数据,不写在 Mapping 的 properties 中。它不能直接用于字段排序,因此示例另存一份 doc_id。

keyword 保留完整值,不等于只能用等号查。 它仍支持前缀、通配符查询;字段存储方式与查询条件是两回事。

3.2 中文分析结果

分析器(Analyzer) 包含字符过滤、分词以及词项过滤等处理。写入 text 字段时,分析器决定保存哪些词项;执行全文查询时,查询文本也会经过分析。

示例选用内置 cjk 分析器,不额外安装插件。它会把连续中文字符组合成相邻的双字词项:

代码块 Python
1
2
3
analysis = client.indices.analyze(analyzer="cjk", text="全文检索")
print([item["token"] for item in analysis["tokens"]])
# 预期输出:['全文', '文检', '检索']

“文检”没有独立词义,但会参与匹配。中文文本能被拆开,不代表切分结果符合业务词汇。 人名、品牌和领域术语等查询,需要单独检查分词效果。

IK 等第三方插件提供不同的中文切分与词典机制,安装时需核对 ES 与插件版本。

修改分词规则不会自动更新已有词项。 调整索引分析规则通常需要新建索引并重新写入数据。

3.3 创建索引

为本文字段显式定义 Mapping,避免首批数据决定的动态类型与后续需求不符:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
index_name = "article_demo_es"
mappings = {
    "properties": {
        "doc_id": {"type": "keyword"},
        "title": {"type": "text", "analyzer": "cjk", "fields": {"raw": {"type": "keyword"}}},
        "text": {"type": "text", "analyzer": "cjk"},
        "category": {"type": "keyword"},
        "published_at": {"type": "date"},
    }
}
settings = {"number_of_shards": 1, "number_of_replicas": 0}
result = client.indices.create(index=index_name, mappings=mappings, settings=settings)
print(result["acknowledged"])
# 预期输出:True

本文的单节点索引配置 1 个主分片、0 个副本。

若改为 1 个副本,副本不能与主分片放在同一节点上,会处于未分配状态。主分片均正常、但有副本未分配时,集群状态为 yellow。

acknowledged=True 表示创建请求已获集群确认,不表示文档已经写入。

创建前确认 article_demo_es 尚不存在;重复练习时更换索引名,并同步用于后续代码。读取已有索引不需要再次创建。

搜索不存在的索引不会自动创建它;符合配置条件的写入请求才可能触发自动创建。

已有字段的类型通常不能直接修改。

category 已建成 text 时,不能直接改为 keyword。需要新建索引,按新的 Mapping 重新写入文档。

4. 文档写入与读取

4.1 准备文档

准备六篇示例文档:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
docs = [
    {"doc_id": "101", "title": "Elasticsearch 全文检索", "category": "检索", "published_at": "2026-10-01",
     "text": "Elasticsearch 建立倒排索引,支持全文检索和条件过滤。"},
    {"doc_id": "102", "title": "Milvus 向量检索", "category": "检索", "published_at": "2026-10-02",
     "text": "Milvus 根据向量相似度检索文档,支持类别过滤。"},
    {"doc_id": "103", "title": "MySQL 事务", "category": "数据库", "published_at": "2026-10-03",
     "text": "MySQL 使用关系表保存业务数据,事务包含提交和回滚。"},
    {"doc_id": "104", "title": "中文分词", "category": "检索", "published_at": "2026-10-04",
     "text": "中文分词将文本转换为词项,影响全文检索的匹配结果。"},
    {"doc_id": "105", "title": "Docker 数据卷", "category": "运维", "published_at": "2026-10-05",
     "text": "Docker 数据卷保存容器数据,移除容器后数据卷仍可保留。"},
    {"doc_id": "106", "title": "模型训练", "category": "机器学习", "published_at": "2026-10-06",
     "text": "梯度下降根据损失函数的梯度调整模型参数。"},
]

六条数据只用于观察接口行为,不能代表大规模搜索性能或实际业务中的分词效果。

4.2 批量写入

helpers.bulk 批量写入文档,每项操作指定索引、文档 ID 和正文:

代码块 Python
1
2
3
4
actions = ({"_op_type": "index", "_index": index_name, "_id": doc["doc_id"], "_source": doc} for doc in docs)
written, errors = helpers.bulk(client, actions, chunk_size=500, refresh="wait_for")
print(written, errors)
# 全部成功时预期输出:6 []

chunk_size=500 限制每批最多 500 个操作,请求大小限制还可能让批次更小。_source 是提交的文档内容,_id 决定文档在索引内的身份。

index 对新 ID 创建文档,对已有 ID 替换整份文档。 仅允许新增时使用 create,同 ID 已存在会报错。

按示例 ID 重复执行这段写入,不会新增六个 ID,但会把后续修改覆盖回 docs 中的内容。

批量写入失败不会回滚已成功的记录。 记录失败时,helpers.bulk 默认抛出 BulkIndexError;只有正常返回时,才能读取 written 和 errors。

4.3 写入与搜索可见性

写入请求成功,不等于搜索接口立即能查到新内容。 ES 的近实时搜索依赖 refresh 让近期变化对搜索可见。

refresh 参数请求行为使用场景
False,默认值不额外等待搜索可见连续写入,不要求紧接着搜索
"wait_for"等待这些变化对搜索可见再返回写入完成后立即执行搜索
True请求立即刷新相关分片明确需要主动刷新时使用,会增加刷新开销

本例写入后立即搜索,因此使用 refresh="wait_for"。等待会增加请求耗时,高频写入不必每条都等待。

4.4 按 ID 读取

get 按文档 ID 读取:

代码块 Python
1
2
3
document = client.get(index=index_name, id="101")
print(document["_id"], document["_source"]["title"])
# 预期输出:101 Elasticsearch 全文检索

GET 按 ID 读取默认是实时的,不依赖搜索 refresh。

写入后,GET 可能已读到新内容,搜索却暂时读不到。ID 不存在时,官方 Python 客户端会抛出 NotFoundError。

5. 搜索与过滤

5.1 全文搜索

Query DSL 用 JSON 对象描述查询,在 Python 中用字典表示:

代码块 Python
1
2
3
4
query = {"match": {"text": {"query": "全文检索", "operator": "and"}}}
response = client.search(index=index_name, query=query, size=10, source=["title", "category"])
print(sorted(hit["_id"] for hit in response["hits"]["hits"]))
# 预期输出:['101', '104']

match 按字段的分析器处理查询,and 要求所有词项匹配,or 允许任意词项匹配。 默认使用 or。

“全文检索”被拆成“全文”“文检”“检索”。设置 operator="and" 时,三个词项都要出现,但不要求相邻,匹配 101、104;改为 "or" 或省略参数后,含有“检索”的 102 也会命中。

size=10 是最多返回的命中条数,不是匹配条件。代码将 ID 排序打印,是为了检查命中文档;ES 返回的原始列表仍按相关性排序。

搜索响应中的主要字段:

字段含义
hits.hits本次返回的命中列表
hits.total.value总命中数量,需结合 relation 判断是否精确
hits.total.relationeq 表示精确值,gte 表示至少这么多
_id命中文档的 ID
_score该查询下的相关性分数
_source文档内容,受请求中的返回字段设置影响

需要精确总数时,设置 track_total_hits=True;这会增加命中计数的计算量。

5.2 词项匹配与条件过滤

term 匹配指定词项,不会像 match 一样对输入做全文分析。 类别字段是 keyword,完整的“检索”就是该字段的一个词项:

代码块 Python
1
2
3
response = client.search(index=index_name, query={"term": {"category": "检索"}}, size=10)
print(sorted(hit["_id"] for hit in response["hits"]["hits"]))
# 预期输出:['101', '102', '104']

两种查询的区别在于字段存了哪些词项、查询是否分析输入,不能概括成“match 模糊、term 精确”。

对 text 执行 term 查询“全文检索”时,ES 会查找完整的这个词项。但 cjk 分析器存下的是“全文”“文检”“检索”,没有“全文检索”,因此不会命中。

正文搜索、类别限制和日期范围可以放在同一个 bool 查询中:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
filtered_query = {
    "bool": {
        "must": [{"match": {"text": {"query": "检索", "operator": "and"}}}],
        "filter": [
            {"term": {"category": "检索"}},
            {"range": {"published_at": {"gte": "2026-10-02", "lt": "2026-10-05"}}},
        ],
    }
}
response = client.search(index=index_name, query=filtered_query, size=10, source=["title", "published_at"])
print(sorted(hit["_id"] for hit in response["hits"]["hits"]))
# 预期输出:['102', '104']

must 中的正文查询参与评分,filter 中的类别、日期只决定是否保留文档。

日期范围中,gte 包含下界,lt 不包含上界。这里包含 10 月 2 日,不包含 10 月 5 日。

bool 子句对命中的要求在这里是否参与评分
must所有子条件都满足是
filter所有子条件都满足否
must_not子条件均不匹配否
should满足多少项由 minimum_should_match 决定匹配项参与评分

should 是否必须命中,取决于同一个 bool 中的其他条件:

  • 没有 must 或 filter:默认至少匹配一项 should。
  • 已有 must 或 filter:默认允许所有 should 都不匹配。

需要固定至少命中几项时,显式设置 minimum_should_match。

类别条件应随查询交给 ES,才能在符合类别的文档中选出前若干条。

如果 ES 返回全量搜索的前 10 条,应用才按类别过滤,结果可能只剩两三条。符合类别、但没进入全量前 10 的文档也会被漏掉。

5.3 相关性、短语与多字段

普通全文查询默认按 _score 降序返回。默认评分方法 BM25 主要考虑词项在当前字段中的出现频率、在文档集合中的常见程度,以及字段长度。

词项越常见,区分作用越弱;在同一篇文档中反复出现,对相关性的贡献会增加,但不会无限线性增长。

_score 没有通用的“合格分数”。 查询、文档集合或字段权重变化,分数都会变化。

短语搜索还会考虑词项的位置。下面的 match_phrase 沿用 cjk 分析器,默认 slop=0,要求分析后的词项按对应顺序与位置匹配:

代码块 Python
1
2
3
response = client.search(index=index_name, query={"match_phrase": {"text": "全文检索"}}, size=10)
print(sorted(hit["_id"] for hit in response["hits"]["hits"]))
# 预期输出:['101', '104']

短语查询检查分析后的词项位置,不等同于原始字符串的逐字比较。

上面的 match_phrase 与 match 加 and 恰好都命中 101、104。若一篇文档包含全部查询词项,但词项位置分散,and 仍可能命中,默认的短语查询则不一定命中。

同时搜索标题和正文时,可以使用 multi_match:

代码块 Python
1
2
3
4
5
response = client.search(index=index_name,
                         query={"multi_match": {"query": "全文检索", "fields": ["title^2", "text"], "operator": "and"}},
                         size=10, source=["title"])
for hit in response["hits"]["hits"]:
    print(hit["_id"], round(hit["_score"], 3), hit["_source"]["title"])

title^2 提高标题字段的权重,但不保证标题命中的文档排第一。默认的 best_fields 取最佳匹配字段的分数。

这里的 and 分别作用于各字段的查询,不允许把词项随意分散到标题和正文中凑齐。分数和排序以实际响应为准。

6. 排序、分页与聚合

6.1 字段排序与分页

按发布时间从新到旧读取“检索”类别的文档,时间相同时按业务 ID 排序:

代码块 Python
1
2
3
4
5
response = client.search(index=index_name, query={"term": {"category": "检索"}},
                         sort=[{"published_at": "desc"}, {"doc_id": "asc"}], from_=0, size=2,
                         source=["title", "published_at"])
print([hit["_id"] for hit in response["hits"]["hits"]])
# 预期输出:['104', '102']

指定字段排序后,结果按字段值排列,默认不计算相关性分数。

from_ 对应请求中的 from,表示跳过的条数。保持上述数据和排序不变,下一页设为 from_=2,会返回 101。

页码越深,需要处理的候选结果越多。 各分片要提供覆盖 from + size 的候选,协调节点汇总后才能丢弃前面的记录。

默认 index.max_result_window 限制 from + size 不超过 10000,调高上限不会消除这些开销。

连续遍历大量结果时,使用 search_after 携带上一页末条结果的排序值;需要固定读取视图时配合 PIT(Point in time)。

6.2 返回匹配片段

高亮功能返回命中文本的片段,供搜索页面展示。这里把默认 HTML 标签替换为方括号,便于在终端查看:

代码块 Python
1
2
3
4
response = client.search(index=index_name, query={"match": {"text": "全文检索"}}, size=3,
                         highlight={"fields": {"text": {}}, "pre_tags": ["["], "post_tags": ["]"]})
for hit in response["hits"]["hits"]:
    print(hit["_id"], hit["highlight"]["text"])

highlight 与 _source 分开返回,不会改写原文。 片段可能只覆盖部分正文,标记边界取决于分析后的词项。

复杂布尔查询的高亮不一定完整反映全部条件,不能按标记数量判断相关性。

6.3 按类别统计

聚合(Aggregation)对查询范围内的文档做统计。 terms 聚合按字段值分组,返回各组的文档数量:

代码块 Python
1
2
3
4
5
6
7
8
9
response = client.search(index=index_name, size=0,
                         aggs={"by_category": {"terms": {"field": "category", "size": 10}}})
for bucket in sorted(response["aggregations"]["by_category"]["buckets"], key=lambda bucket: bucket["key"]):
    print(bucket["key"], bucket["doc_count"])
# 预期输出:
# 数据库 1
# 机器学习 1
# 检索 3
# 运维 1

外层 size=0 不返回命中文档,聚合仍会执行;聚合中的 size=10 限制最多返回 10 个类别。

请求没有 query 时统计全部文档,传入查询条件时只统计匹配的文档。

keyword 字段默认建立 doc values,按列组织字段值,适合读取文档的类别并执行排序、聚合。倒排索引则支持按词项查找文档,两者的访问方向不同。

当前索引只有一个主分片、四个类别,size=10 能完整返回这些类别。

多分片下,terms 的计数可能存在误差。 类别较多时,结果来自各分片候选桶的合并,不一定等于精确的全量分组统计。需要结合 doc_count_error_upper_bound、sum_other_doc_count 和业务要求判断。

7. 更新、删除与数据保存

7.1 更新文档

只修改 104 的类别时,update 接收需要更新的字段:

代码块 Python
1
2
3
4
5
6
result = client.update(index=index_name, id="104", doc={"category": "文本处理"}, refresh="wait_for")
print(result["result"])
print(client.get(index=index_name, id="104")["_source"]["category"])
# 首次修改时预期输出:
# updated
# 文本处理

update 合并指定字段后重新索引文档,保留其他字段。 再次提交相同内容时,可能返回 noop,表示无需修改。

整条替换则使用 index,与第 4 节 Bulk 中的 index 操作语义一致:

代码块 Python
1
2
3
4
replacement = {**docs[3], "category": "文本处理"}
result = client.index(index=index_name, id="104", document=replacement, refresh="wait_for")
print(result["result"])
# 对已有文档执行时预期输出:updated

局部修改使用 update,完整替换使用 index。

如果只把 {"category": "文本处理"} 传给 index,旧文档中的标题和正文会被删除。

update 默认要求文档已存在;不存在时需要创建,必须明确设置 upsert 行为。

修改正文后,ES 按字段分析器生成新词项,搜索可见性仍受 refresh 控制。

向量字段若由应用的 Embedding 模型生成,应用还需同步更新向量,普通文本分析不会生成这些向量。

7.2 删除文档

删除 106,并通过搜索检查它是否仍然存在:

代码块 Python
1
2
3
4
5
6
7
result = client.delete(index=index_name, id="106", refresh="wait_for")
print(result["result"])
response = client.search(index=index_name, query={"ids": {"values": ["106"]}})
print(response["hits"]["total"]["value"])
# 首次删除时预期输出:
# deleted
# 0

删除只影响 ES 文档,docs 列表不变。

按本文顺序更新 104、删除 106 后,重新执行第 4.2 节的批量写入,会恢复原始六篇文档,并把 104 的类别改回“检索”。

删除索引会移除全部文档和 Mapping。 重复练习不需要在自动初始化代码中加入删除索引。

7.3 关闭、重启与读取已有数据

关闭客户端只释放连接,不会关闭 ES 服务或删除数据:

代码块 Python
1
client.close()

在第 2 节的 Compose 目录中停止并重新启动服务:

代码块 Shell
1
2
3
docker compose down
docker compose up -d
curl --fail http://localhost:9200
  • down 保留数据:默认不删除命名卷 es-data,相同 Compose 项目启动时会挂载原卷。
  • down -v 删除数据卷:会删除项目声明的命名卷,示例数据随之丢失。
  • 项目名影响所用数据卷:Compose 默认以目录名确定项目名,更换目录或项目名可能挂载新的空卷,不代表原卷数据已消失。

服务恢复响应后,读取已有文档:

代码块 Python
1
2
3
4
5
6
from elasticsearch import Elasticsearch

client = Elasticsearch("http://localhost:9200", request_timeout=30, max_retries=0)
print(client.get(index="article_demo_es", id="104")["_source"]["category"])
client.close()
# 按本文顺序执行后预期输出:文本处理

这次直接读取已有索引,无需重新创建或写入。数据卷不等于备份。 误删或磁盘损坏造成的数据丢失,需要依靠单独保存的快照恢复。

8. 客户端复用

复用客户端,让多个请求共用连接。 配套类集中封装索引初始化、批量写入和文档操作。

Elasticsearch 索引客户端完整脚本

配套代码接收官方 Elasticsearch 实例和索引名,保留原生 Query DSL。调用方提供字段定义和查询条件,搜索不会自动创建索引。

下面读取已有的 article_demo_es,不依赖前面会话中的变量:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from elasticsearch import Elasticsearch
from elasticsearch_client import ElasticsearchIndexClient

client = Elasticsearch("http://localhost:9200", request_timeout=30, max_retries=0)
store = ElasticsearchIndexClient(client, "article_demo_es")
try:
    query = {"bool": {"must": [{"match": {"text": {"query": "全文检索", "operator": "and"}}}],
                      "filter": [{"term": {"category": "检索"}}]}}
    response = store.search(query, size=3, source=["title", "category"])
    for hit in response["hits"]["hits"]:
        print(hit["_id"], hit["_source"]["title"])
finally:
    store.close()
# 按本文顺序执行后预期输出:101 Elasticsearch 全文检索

第 7 节把 104 的类别改成了“文本处理”。这里要求类别为“检索”,因此只返回 101。

全文搜索依赖词项匹配,向量检索关注语义接近程度。 两类结果需要融合时,候选范围、排序和效果评估应单独设计。

评论

使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。

阅读到这里时加载评论。