搜索 DSL

Search 对象

Search 对象代表整个搜索请求

  • 查询

  • 过滤器

  • 聚合

  • k-最近邻搜索

  • 排序

  • 分页

  • 高亮

  • 建议

  • 折叠

  • 附加参数

  • 关联的客户端

API 设计为可链式调用。除了聚合功能外,这意味着 Search 对象是不可变的 - 对对象的任何更改都会导致创建包含更改的浅层副本。这意味着您可以安全地将 Search 对象传递给外部代码,而不必担心它会修改您的对象,只要它坚持使用 Search 对象 API。

您可以在实例化 Search 对象时传递低级 elasticsearch 客户端 的实例

from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search

client = Elasticsearch()

s = Search(using=client)

您也可以在稍后定义客户端(有关更多选项,请参阅 配置 章节)

s = s.using(client)

注意

所有方法都返回对象的副本,使其可以安全地传递给外部代码。

API 是可链式的,允许您在一个语句中组合多个方法调用

s = Search().using(client).query("match", title="python")

要将请求发送到 Elasticsearch

response = s.execute()

如果您只想遍历搜索返回的命中结果,您可以遍历 Search 对象

for hit in s:
    print(hit.title)

搜索结果将被缓存。后续对 execute 的调用或尝试遍历已执行的 Search 对象不会触发发送到 Elasticsearch 的额外请求。要强制请求,请在调用 execute 时指定 ignore_cache=True

为了调试目的,您可以将 Search 对象显式序列化为 dict

print(s.to_dict())

根据查询删除

您可以通过在 Search 对象上调用 delete 而不是 execute 来删除匹配搜索的文档,如下所示

s = Search(index='i').query("match", title="python")
response = s.delete()

查询

该库为所有 Elasticsearch 查询类型提供了类。将所有参数作为关键字参数传递。这些类接受任何关键字参数,然后 dsl 将所有传递给构造函数的参数序列化为结果字典中的顶级键(以及发送到 elasticsearch 的结果 json)。这意味着原始查询与其在 DSL 中的等效项之间存在明确的一对一映射

from elasticsearch_dsl.query import MultiMatch, Match

# {"multi_match": {"query": "python django", "fields": ["title", "body"]}}
MultiMatch(query='python django', fields=['title', 'body'])

# {"match": {"title": {"query": "web framework", "type": "phrase"}}}
Match(title={"query": "web framework", "type": "phrase"})

注意

在某些情况下,由于 python 对标识符的限制,这种方法不可行 - 例如,如果您的字段名为 @timestamp。在这种情况下,您必须回退到解包字典:Range(** {'@timestamp': {'lt': 'now'}})

您可以使用 Q 快捷方式使用带有参数的名称或原始 dict 来构造实例

from elasticsearch_dsl import Q

Q("multi_match", query='python django', fields=['title', 'body'])
Q({"multi_match": {"query": "python django", "fields": ["title", "body"]}})

要将查询添加到 Search 对象,请使用 .query() 方法

q = Q("multi_match", query='python django', fields=['title', 'body'])
s = s.query(q)

该方法还接受所有参数作为 Q 快捷方式

s = s.query("multi_match", query='python django', fields=['title', 'body'])

如果您已经拥有查询对象或表示查询对象的 dict,则可以覆盖 Search 对象中使用的查询

s.query = Q('bool', must=[Q('match', title='python'), Q('match', body='best')])

点分隔字段

有时您想引用另一个字段中的字段,无论是作为多字段 (title.keyword) 还是在结构化的 json 文档中,例如 address.city。为了简化操作,Q 快捷方式(以及 Search 类上的 queryfilterexclude 方法)允许您在关键字参数中使用 __(双下划线)代替点

s = Search()
s = s.filter('term', category__keyword='Python')
s = s.query('match', address__city='prague')

或者,如果您愿意,您始终可以回退到 python 的 kwarg 解包

s = Search()
s = s.filter('term', **{'category.keyword': 'Python'})
s = s.query('match', **{'address.city': 'prague'})

查询组合

可以使用逻辑运算符组合查询对象

Q("match", title='python') | Q("match", title='django')
# {"bool": {"should": [...]}}

Q("match", title='python') & Q("match", title='django')
# {"bool": {"must": [...]}}

~Q("match", title="python")
# {"bool": {"must_not": [...]}}

当您多次调用 .query() 方法时,内部将使用 & 运算符

s = s.query().query()
print(s.to_dict())
# {"query": {"bool": {...}}}

如果您想精确控制查询形式,请使用 Q 快捷方式直接构造组合查询

q = Q('bool',
    must=[Q('match', title='python')],
    should=[Q(...), Q(...)],
    minimum_should_match=1
)
s = Search().query(q)

过滤器

如果您想在 过滤器上下文 中添加查询,可以使用 filter() 方法来简化操作

s = Search()
s = s.filter('terms', tags=['search', 'python'])

在幕后,这将生成一个 Bool 查询并将指定的 terms 查询放置到其 filter 分支中,使其等效于

s = Search()
s = s.query('bool', filter=[Q('terms', tags=['search', 'python'])])

如果您想使用 post_filter 元素进行面向导航,请使用 .post_filter() 方法。

您还可以像这样从查询中 exclude()

s = Search()
s = s.exclude('terms', tags=['search', 'python'])

这是 s = s.query('bool', filter=[~Q('terms', tags=['search', 'python'])]) 的简写

聚合

要定义聚合,可以使用 A 快捷方式

from elasticsearch_dsl import A

A('terms', field='tags')
# {"terms": {"field": "tags"}}

要嵌套聚合,可以使用 .bucket().metric().pipeline() 方法

a = A('terms', field='category')
# {'terms': {'field': 'category'}}

a.metric('clicks_per_category', 'sum', field='clicks')\
    .bucket('tags_per_category', 'terms', field='tags')
# {
#   'terms': {'field': 'category'},
#   'aggs': {
#     'clicks_per_category': {'sum': {'field': 'clicks'}},
#     'tags_per_category': {'terms': {'field': 'tags'}}
#   }
# }

要将聚合添加到 Search 对象,请使用 .aggs 属性,它充当顶级聚合

s = Search()
a = A('terms', field='category')
s.aggs.bucket('category_terms', a)
# {
#   'aggs': {
#     'category_terms': {
#       'terms': {
#         'field': 'category'
#       }
#     }
#   }
# }

s = Search()
s.aggs.bucket('articles_per_day', 'date_histogram', field='publish_date', interval='day')\
    .metric('clicks_per_day', 'sum', field='clicks')\
    .pipeline('moving_click_average', 'moving_avg', buckets_path='clicks_per_day')\
    .bucket('tags_per_day', 'terms', field='tags')

s.to_dict()
# {
#   "aggs": {
#     "articles_per_day": {
#       "date_histogram": { "interval": "day", "field": "publish_date" },
#       "aggs": {
#         "clicks_per_day": { "sum": { "field": "clicks" } },
#         "moving_click_average": { "moving_avg": { "buckets_path": "clicks_per_day" } },
#         "tags_per_day": { "terms": { "field": "tags" } }
#       }
#     }
#   }
# }

您可以通过名称访问现有的桶

s = Search()

s.aggs.bucket('per_category', 'terms', field='category')
s.aggs['per_category'].metric('clicks_per_category', 'sum', field='clicks')
s.aggs['per_category'].bucket('tags_per_category', 'terms', field='tags')

注意

在链接多个聚合时,.bucket().metric() 方法的返回值之间存在差异 - .bucket() 返回新定义的桶,而 .metric() 返回其父桶以允许进一步链接。

Search 对象上的其他方法不同,定义聚合是在原地完成的(不返回副本)。

K-最近邻搜索

要发出 kNN 搜索,请使用 .knn() 方法

s = Search()
vector = get_embedding("search text")

s = s.knn(
    field="embedding",
    k=5,
    num_candidates=10,
    query_vector=vector
)

fieldknum_candidates 参数可以作为位置参数或关键字参数给出,并且是必需的。除了这些参数外,还必须给出 query_vectorquery_vector_builder

.knn() 方法可以被多次调用以在请求中包含多个 kNN 搜索。

排序

要指定排序顺序,请使用 .sort() 方法

s = Search().sort(
    'category',
    '-title',
    {"lines" : {"order" : "asc", "mode" : "avg"}}
)

它接受位置参数,这些参数可以是字符串或字典。字符串值是字段名称,可以选择以 - 符号为前缀以指定降序。

要重置排序,只需在不带参数的情况下调用该方法即可

s = s.sort()

分页

要指定 from/size 参数,请使用 Python 切片 API

s = s[10:20]
# {"from": 10, "size": 10}

s = s[:20]
# {"size": 20}

s = s[10:]
# {"from": 10}

s = s[10:20][2:]
# {"from": 12, "size": 8}

如果您想访问查询匹配的所有文档,可以使用 scan 方法,该方法使用 scan/scroll elasticsearch API

for hit in s.scan():
    print(hit.title)

请注意,在这种情况下,结果将不会被排序。

高亮

要设置高亮的通用属性,请使用 highlight_options 方法

s = s.highlight_options(order='score')

使用 highlight 方法为各个字段启用高亮

s = s.highlight('title')
# or, including parameters:
s = s.highlight('title', fragment_size=50)

然后,响应中的片段将在每个 Result 对象上作为 .meta.highlight.FIELD 提供,其中将包含片段列表

response = s.execute()
for hit in response:
    for fragment in hit.meta.highlight.title:
        print(fragment)

建议

要在您的 Search 对象上指定一个建议请求,请使用 suggest 方法

# check for correct spelling
s = s.suggest('my_suggestion', 'pyhton', term={'field': 'title'})

第一个参数是建议的名称(将在返回结果中使用的名称),第二个参数是您希望建议器处理的实际文本,关键字参数将按原样添加到建议的 JSON 中,这意味着它应该是 termphrasecompletion 之一,以指示应使用哪种类型的建议器。

折叠

要折叠搜索结果,请在您的 Search 对象上使用 collapse 方法

s = Search().query("match", message="GET /search")
# collapse results by user_id
s = s.collapse("user_id")

热门结果将只包含每个 user_id 的一个结果。您还可以使用 inner_hits 参数扩展每个折叠的热门结果,max_concurrent_group_searches 是允许用于检索每个组的内部命中结果的并发请求数

inner_hits = {"name": "recent_search", "size": 5, "sort": [{"@timestamp": "desc"}]}
s = s.collapse("user_id", inner_hits=inner_hits, max_concurrent_group_searches=4)

类似内容查询

要使用 Elasticsearch 的 more_like_this 功能,您可以使用 MoreLikeThis 查询类型。

下面是一个简单的示例

from elasticsearch_dsl.query import MoreLikeThis
from elasticsearch_dsl import Search

my_text = 'I want to find something similar'

s = Search()
# We're going to match based only on two fields, in this case text and title
s = s.query(MoreLikeThis(like=my_text, fields=['text', 'title']))
# You can also exclude fields from the result to make the response quicker in the normal way
s = s.source(exclude=["text"])
response = s.execute()

for hit in response:
    print(hit.title)

额外属性和参数

要设置搜索请求的额外属性,请使用 .extra() 方法。这可以用于定义无法通过特定 API 方法(如 explainsearch_after)定义的主体中的键

s = s.extra(explain=True)

要设置查询参数,请使用 .params() 方法

s = s.params(routing="42")

如果您需要限制 Elasticsearch 返回的字段,请使用 source() 方法

# only return the selected fields
s = s.source(['title', 'body'])
# don't return any fields, just the metadata
s = s.source(False)
# explicitly include/exclude fields
s = s.source(includes=["title"], excludes=["user.*"])
# reset the field selection
s = s.source(None)

序列化和反序列化

搜索对象可以使用 .to_dict() 方法序列化为字典。

您还可以使用 from_dict 类方法从 dict 创建一个 Search 对象。这将创建一个新的 Search 对象,并使用字典中的数据填充它

s = Search.from_dict({"query": {"match": {"title": "python"}}})

如果您希望修改现有的 Search 对象,覆盖其属性,请改用 update_from_dict 方法,该方法会**就地**更改实例

s = Search(index='i')
s.update_from_dict({"query": {"match": {"title": "python"}}, "size": 42})

响应

您可以通过调用 .execute() 方法执行搜索,该方法将返回一个 Response 对象。 Response 对象允许您通过属性访问访问响应字典中的任何键。它还提供了一些方便的助手

response = s.execute()

print(response.success())
# True

print(response.took)
# 12

print(response.hits.total.relation)
# eq
print(response.hits.total.value)
# 142

print(response.suggest.my_suggestions)

如果您想检查 response 对象的内容,只需使用其 to_dict 方法即可访问原始数据以进行漂亮打印。

命中结果

要访问搜索返回的命中结果,请访问 hits 属性,或者直接迭代 Response 对象

response = s.execute()
print('Total %d hits found.' % response.hits.total)
for h in response:
    print(h.title, h.body)

注意

如果您只看到部分结果(例如 10000 甚至 10 个结果),请考虑使用选项 s.extra(track_total_hits=True) 来获取完整的命中结果计数。

结果

单个命中结果被包装在一个方便的类中,该类允许对返回字典中的键进行属性访问。所有结果的元数据都可以通过 meta(没有前导的 _)访问

response = s.execute()
h = response.hits[0]
print('/%s/%s/%s returned with score %f' % (
    h.meta.index, h.meta.doc_type, h.meta.id, h.meta.score))

注意

如果您的文档有一个名为 meta 的字段,您必须使用获取项语法访问它:hit['meta']

聚合

聚合可以通过 aggregations 属性访问

for tag in response.aggregations.per_tag.buckets:
    print(tag.key, tag.max_lines.value)

MultiSearch

如果您需要同时执行多个搜索,可以使用 MultiSearch 类,它将使用 _msearch API

from elasticsearch_dsl import MultiSearch, Search

ms = MultiSearch(index='blogs')

ms = ms.add(Search().filter('term', tags='python'))
ms = ms.add(Search().filter('term', tags='elasticsearch'))

responses = ms.execute()

for response in responses:
    print("Results for query %r." % response._search.query)
    for hit in response:
        print(hit.title)

EmptySearch

EmptySearch 类可以用作 Search 的完全兼容版本,它将不返回任何结果,无论配置了哪些查询。