现在,您已经建立了 Elasticsearch 索引,并向其中加载了一些文档,您已经准备好实施全文搜索了。
搜索如何进行
让我们快速回顾一下搜索解决方案在教程应用中是如何工作的。运行 Flask 应用程序后,您可以登录http://localhost:5001访问主页面,它看起来像这样:

渲染该页面的代码在app.py文件中实现:
这是一个非常简单的端点,用于渲染 HTML 模板。在 Flask 应用程序中,模板位于templates子目录中,因此您可以在该目录中找到此模板和应用程序中包含的其他模板。
让我们看看模板/index.html 文件中搜索字段的实现。以下是该模板的相关部分:
在这里,您可以看到这是一个 HTML 表单,其中有一个text 类型的字段,名为query 。表单的method 属性设置为POST ,这就告诉浏览器以 POST 请求的方式提交该表单。action 属性设置为与 Flask 应用程序的handle_search 端点相对应的 URL。提交表单后,将执行handle_search() 函数。
handle_search() 目前的实施情况如下:
该函数从 Flask 的request.form 字典中获取用户在文本字段中键入的文本,并将其存储到query 本地变量中。然后,函数会渲染index.html模板,但这种类型的模板会传递一些额外的参数,以便页面能显示搜索结果。模板接收的四个参数是
query:用户在表单中输入的查询文本。results:搜索结果列表from_:第一个结果的零基索引total:结果总数
由于搜索功能尚未实现,目前传递给render_template() 函数的参数表示未找到结果。
现在的任务是实现全文查询并传递实际结果,以便index.html页面可以显示这些结果。
Elasticsearch 服务使用基于 JSON 格式的查询 DSL(特定域语言)来定义查询。
Elasticsearch Python 客户端有一个用于提交搜索查询的search() 方法。让我们在search.py中添加一个使用该方法的search() 辅助方法:
该方法使用索引名称调用 Elasticsearch 客户端的search() 方法。query_args 参数会捕获提供给方法的所有关键字参数,然后将它们传递给es.search() 方法。这些参数就是调用者指定搜索内容的方式。
匹配查询
Elasticsearch查询 DSL提供了多种不同的索引查询方法。通过查看文档中的各分节,您将熟悉可以进行的不同类型的查询。全文查询部分涵盖了非常常见的文本搜索任务。
在第一次搜索中,我们使用匹配查询。下面是一个使用该查询的示例:
上述示例的格式类似于原始 HTTP 请求。熟悉这种格式非常有用,因为它在 Elasticsearch 文档和 ElasticsearchAPI 控制台中被广泛使用。幸运的是,使用 Python 客户端库很容易将这种格式转化为调用。下面是上述示例的 Python 代码:
将 API Console 示例转换为 Python 时,请记住查询正文中的顶级键必须转换为 Python 调用中的关键字参数。示例也没有指定索引,而在 Python 调用时需要指定索引。
通过观察查询结构,你大概可以推断出这是什么类型的搜索请求。调用请求对名为name 的字段进行match 查询,要搜索的文本是search text here 。
这种查询方式很容易纳入教程应用程序中。打开app.py,找到handle_search() 方法。用这个新版本替换当前版本:
新版终端第二行中对es.search() 的调用会调用上文在search.py 中添加的search() 方法、然后调用 Elasticsearch 客户端的search() 方法。
你能想出查询要做什么吗?这是一个与上述示例类似的match 查询。要搜索的字段是name ,该字段包含上一节建立的my_documents 索引中的文档标题。要搜索的文本是用户在网页搜索栏中键入的内容,存储在query 本地变量中。
搜索响应中包含结果的部分是response['hits'] 。这是一个有几个键的对象,其中两个键在本实现中很重要:
response['hits']['hits']:搜索结果列表。response['hits']['total']:可用结果的总数。结果数在value子键中给出,因此在实际操作中,获取结果总数的表达式为results['hits']['total']['value']。请注意,当结果数量较多时,结果总数可能只是一个近似值。详情请查看响应体文档。
在这个新版本的终端中,调用render_template() 时会在results 模板参数中传递结果列表,并在total 中传递结果总数。query 参数和之前一样接收查询字符串,而from_ 参数仍被硬编码为 0,因为稍后添加分页功能时将会实现。
因此,该应用程序首次实现了全文搜索。返回网络浏览器,导航至http://localhost:5001打开应用程序。如果由于某种原因没有运行 Flask 应用程序,请在执行此操作前重新启动该应用程序。输入搜索文本,如policy 或work from home ,您将看到相关结果。下面是搜索work from home 的结果:

您随启动程序一起下载的index.html模板包含呈现搜索结果的所有逻辑。如果您对此感到好奇,下面是该模板中渲染结果列表的部分:
从这段代码中我们可以注意到,与返回结果相关的数据可在_source 关键字下获得。还有一个_id 字段,包含分配给结果的唯一标识符。
与每个结果相关的得分可从_score 中获得。分数是相关性的衡量标准,分数越高,表示与查询文本的匹配度越高。默认情况下,结果按得分从高到低的顺序返回。Elasticsearch 中的分数使用Okapi BM25算法计算。
如果您有兴趣更详细地了解本节所涵盖的主题,请使用以下链接:
检索单个结果
您可能已经注意到,index.html模板会将每个搜索结果的标题显示为一个链接。链接指向 Flask 初始应用程序中实现的第三个也是最后一个端点,名为get_document 。所提供的实现会返回"Document not found" 硬编码文本,因此,如果你在使用应用程序时点击任何结果,就会看到这个结果。
为了正确渲染单个文档,让我们在search.py 中添加一个retrieve_document() 辅助方法、使用 Elasticsearch 客户端的get() 方法:
在这里,您可以看到为每个文档分配的这些唯一标识符是如何发挥作用的,因为应用程序可以使用这些标识符来引用单个文档。
以下是get_document() 端点的当前实施情况:
您可以看到,与该端点相关联的 URL 包含文档id ,而为每个搜索结果呈现的链接也在各自的 URL 中包含了 id,因此只需将这种简单的实现替换为检索文档并呈现文档的实现即可。用该更新版本替换端点:
这里使用search.py中的retrieve_document() 方法来获取请求的文档。然后,document.html将被渲染,标题来自name 字段,段落列表来自content 。
尝试运行更多查询,然后点击结果,现在应该可以看到完整内容了。
搜索多个字段
在使用该程序一段时间后,你可能会发现很多查询都没有结果。正如你所记得的,搜索目前是在每个文档的name 字段上实现的,该字段是文档标题的存储位置。文档中还有summary 和content 字段,这些字段的文本较长,也容易被搜索到,但现在这些字段被忽略了。
本节将介绍另一种常见的全文搜索查询--多匹配,它要求在索引的多个字段中进行搜索。
下面是文档中的多重匹配查询示例:
让我们以这个示例为基础,扩展handle_search() 端点,对name 、summary 和content 字段组合运行多重匹配查询。以下是更新后的端点代码:
有了这一变化,可搜索的文本就多了很多,以至于有些查询可能会超过默认返回的最多 10 个结果。在下一章中,你将学习如何通过分页来处理长长的结果列表。