在本指南中,我们将逐步指导您完成使用 Meilisearch 实现搜索结果提升的过程。我们的目标是当某些关键字与用户的查询匹配时,搜索结果会优先显示特定文档。这些提升后的文档应该出现在搜索结果的顶部。
概述
以下是使用第二个索引实现“固定文档”的文档提升以及 多重搜索功能 的简化分解。
- 创建索引: 设置两个索引:一个用于常规搜索,另一个用于提升的结果。提升的索引将有一个特殊的属性
keywords
,用于触发提升。 - 填充“games”索引: 使用提供的 JSON 文件,将您的数据集填充到
games
索引中。此索引将作为我们提升文档的来源。 - 配置“pinned_games”索引: 配置
pinned_games
索引以显示属性,而不显示关键字。相应地调整可搜索属性和显示属性。 - 提升文档: 确定您要提升的文档,并为其分配相关的关键字。例如,您可以为游戏
Counter-Strike
分配关键字fps
和shooter
。 - 实现多重搜索: 使用 Meilisearch 的多重搜索功能,跨常规索引和提升索引执行搜索查询。这样,匹配关键字的提升文档将首先出现。
- 显示结果: 以用户友好的格式呈现搜索结果,并使用视觉指示突出显示提升的文档。
实施
安装
在深入研究之前,请确保您已启动并运行 Meilisearch。如果您尚未安装它,请按照以下步骤操作
- 启动 Meilisearch 实例 - 您可以在 本地 或通过 Meilisearch 云 运行 Meilisearch。
- 确保您安装了您喜欢的 语言 SDK(或框架集成)。
初始化索引
在我们的示例中,我们将使用 Steam 游戏数据集。您可以将此过程调整到您自己的数据
- 下载
steam-games.json
和settings.json
文件,以获取我们的 Steam 游戏数据集 - 通过 添加文档 来自
steam-games.json
文件,将数据集加载到 Meilisearch 实例中。
games
索引
import meilisearch
import json
from typing import Callable
client = meilisearch.Client(url="http://localhost:7700")
games = client.index("games")
# helper to wait for Meilisearch tasks
def wait_with_progress(client: meilisearch.Client, task_uid: int):
while True:
try:
client.wait_for_task(task_uid, timeout_in_ms=1000)
break
except meilisearch.errors.MeilisearchTimeoutError:
print(".", end="")
task = client.get_task(task_uid)
print(f" {task.status}")
if task.error is not None:
print(f"{task.error}")
print("Adding settings...", end="")
with open("settings.json") as settings_file:
settings = json.load(settings_file)
task = games.update_settings(settings)
wait_with_progress(client, task.task_uid)
with open("steam-games.json") as documents_file:
documents = json.load(documents_file)
task = games.add_documents_json(documents)
print("Adding documents...", end="")
wait_with_progress(client, task.task_uid)
pinned_games
索引
此索引将包含提升的文档。pinned_games
索引的设置与 games
索引相同,但以下不同
- 唯一一个
searchableAttributes
是keywords
属性,该属性包含触发固定该文档的单词。 displayedAttributes
是所有文档的属性,除了keywords
(我们不想向最终用户显示关键字)
pinned = client.index("pinned_games")
print("Adding settings...", end="")
with open("settings.json") as settings_file:
settings = json.load(settings_file)
settings["searchableAttributes"] = ["keywords"]
# all but "keywords"
settings["displayedAttributes"] = ["name", "description", "id", "price", "image", "releaseDate", "recommendationCount", "platforms", "players", "genres", "misc"]
task = pinned.update_settings(settings)
# see `wait_with_progress` implementation in previous code sample
wait_with_progress(client, task.task_uid)
更新提升的文档索引
现在,我们将使用来自 games
索引的文档来填充索引,这些文档是我们想要提升的。
例如,假设我们想要将游戏 "Counter-Strike"
固定到 "fps"
和 "first", "person", "shooter"
关键字。
counter_strike = games.get_document(document_id=10)
counter_strike.keywords = ["fps", "first", "person", "shooter"]
print("Adding pinned document...", end="")
task = pinned.add_documents(dict(counter_strike))
wait_with_progress(client, task.task_uid)
自定义搜索结果
现在,让我们创建一个函数来返回带有固定文档的搜索结果。
from copy import deepcopy
from typing import Any, Dict, List
from dataclasses import dataclass
@dataclass
class SearchResults:
pinned: List[Dict[str, Any]]
regular: List[Dict[str, Any]]
def search_with_pinned(client: meilisearch.Client, query: Dict[str, Any]) -> SearchResults:
pinned_query = deepcopy(query)
pinned_query["indexUid"] = "pinned_games"
regular_query = deepcopy(query)
regular_query["indexUid"] = "games"
results = client.multi_search([pinned_query, regular_query])
# fetch the limit that was passed to each query so that we can respect that value when getting the results from each source
limit = results["results"][0]["limit"]
# fetch as many results from the pinned source as possible
pinned_results = results["results"][0]["hits"]
# only fetch results from the regular source up to limit
regular_results = results["results"][1]["hits"][:(limit-len(pinned_results))]
return SearchResults(pinned=pinned_results, regular=regular_results)
我们可以使用此函数检索包含提升文档的搜索结果
results = search_with_pinned(client, {"q": "first person shoot", "attributesToRetrieve": ["name"]})
results
对象应该如下所示
SearchResults(pinned=[{'name': 'Counter-Strike'}], regular=[{'name': 'Rogue Shooter: The FPS Roguelike'}, {'name': 'Rocket Shooter'}, {'name': 'Masked Shooters 2'}, {'name': 'Alpha Decay'}, {'name': 'Red Trigger'}, {'name': 'RAGE'}, {'name': 'BRINK'}, {'name': 'Voice of Pripyat'}, {'name': 'HAWKEN'}, {'name': 'Ziggurat'}, {'name': 'Dirty Bomb'}, {'name': 'Gunscape'}, {'name': 'Descent: Underground'}, {'name': 'Putrefaction'}, {'name': 'Killing Room'}, {'name': 'Hard Reset Redux'}, {'name': 'Bunny Hop League'}, {'name': 'Kimulator : Fight for your destiny'}, {'name': 'Intrude'}])
太棒了 🎉 您现在拥有一个搜索结果对象,其中包含两个数组:提升的结果和“常规”结果。
更进一步
本教程探索了实现提升结果的一种方法。另一种技术是在前端实现文档固定;查看我们的 React 实现指南。这种不同的方法的优点是 与 InstantSearch 兼容。
两种技术都可以实现类似的结果。我们还计划在 Meilisearch 引擎中集成 提升的文档。请在之前的链接中提供您的反馈,以帮助我们优先考虑它。
有关更多 Meilisearch 内容,您可以订阅我们的 时事通讯。您可以通过查看 路线图 并参与我们的 产品讨论 来了解有关我们产品的更多信息。
对于其他任何事项,请加入我们开发者社区,在 Discord 上。
干杯!