本教程使用 GoodReads 的样本数据集,该数据集由 Jealous Leopard 在 Kaggle 上传。
本教程的目的是深入了解 Meilisearch 的高级概念,例如
- Meilisearch 如何处理嵌套对象
- 如何使用 facets 计算文档的分布
- 如何使用 distinct 属性
- 如何使用 settings 对象定义可搜索属性
那么,学习本教程需要哪些先决条件呢?
先决条件
要学习本独立教程,您需要对 Meilisearch 有基本了解。如果您不确定,请随时查看关于 搜索诺贝尔奖获得者 的先前教程,但这并不是必需的。
其他先决条件包括。
- 一个 Node.js 安装
- 一个 Meilisearch 实例:它可以运行在本地、通过 Docker 或在 DigitalOcean 虚拟机 上。不想设置自己的 Meilisearch 实例?试试我们的 Meilisearch 云,远程托管的实例
- cURL 或 Postman 用于从终端发送请求
- Meilisearch-js 包装器和依赖项(参见 安装指南)。
准备好了吗?让我们深入了解吧!
项目设置和 Meilisearch-js 安装
为了学习本教程,我们需要设置我们的 JavaScript 项目并安装 Meilisearch-js。创建一个新文件夹,并在您的终端中运行以下命令。
npm init -y
这将准备您的项目设置。接下来,我们可以添加 Meilisearch-js 依赖项。
npm install meilisearch
最后,让我们在您的项目中创建一个名为 index.js
的文件。我们将使用此文件添加我们的 JavaScript 代码。
touch index.js
完成了吗?让我们继续前进!
步骤 1:创建索引
此步骤将准备 index.js
文件,以便我们可以使用 meilisearch
包进行试验。
首先,我们需要连接到我们的 Meilisearch 实例。如果您使用了 Meilisearch 云,您会收到一个主密钥,该密钥保护您 Meilisearch 实例的所有 API 端点。如果您使用了其他安装方法,我们强烈建议您出于安全原因 设置主密钥。例如,一个未经保护的 DigitalOcean 虚拟机允许任何人通过公共可用的 IP 地址访问您的实例。
下面,您将找到一个代码片段,您可以将其在所有 Meilisearch 项目中重复使用。为了访问 async/await
语法,我们将代码包装在一个异步 main
函数中。我们还使用 client
对象连接到我们的 Meilisearch 实例。
将下面的代码片段添加到您的 index.js
文件中。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const indexes = await client.getIndexes()
console.log(indexes)
}
main()
请注意,我们对新创建的 client
对象调用了 getIndexes()
方法。
现在,使用 node
命令从您的终端执行该文件。
node index.js
如果您收到了来自客户端的响应,则连接对象有效。如果您没有收到,请仔细检查您的主机地址和 API 密钥。
对于下一步,让我们创建 books
索引以添加我们的 GoodReads 数据。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const indexes = await client.getIndexes()
console.log(indexes)
const indexCreationTask = await client.createIndex('books')
await client.waitForTask(indexCreationTask.taskUid)
const updatedIndexes = await client.getIndexes()
console.log(updatedIndexes)
}
main()
像之前一样使用 node
命令执行该文件。您应该会看到以下响应,其中包含您的 books
索引。请注意,您的 createdAt
和 updatedAt
值可能与我们的不同。
{
results: [
Index {
uid: 'books',
primaryKey: null,
httpRequest: [HttpRequests],
tasks: [TaskClient]
}
],
offset: 0,
limit: 20,
total: 1
}
我们的索引已创建,但我们还没有为其指定主键。当我们在下一步中添加数据时,Meilisearch 将 推断我们的主键,因为我们的数据集包含一个 id
字段。
索引已创建?很好!让我们探索 GoodReads 图书数据集。
步骤 2:添加 GoodReads 图书数据集
此步骤探索 GoodReads 图书数据集。为了清楚起见,我们使用了一个修改后的、更小的版本,但如果您有兴趣,可以在 Kaggle 上找到原始数据集。首先,让我们使用 cURL 命令下载数据集。
curl -L https://raw.githubusercontent.com/meilisearch/datasets/main/datasets/books/books.json -o books.json
那么,book
对象是什么样的呢?
{
id: "1",
title: "Harry Potter and the Half-Blood Prince",
author: "J.K. Rowling/Mary GrandPré",
cover: "hard cover with dust jacket",
language: "eng",
publisher: "Scholastic Inc.",
details: {
isbn: "0439785960",
rating: "4.57",
pages: "652"
}
}
每本书都有一个唯一的 id
。cover
属性有三个可能的值:hard cover
、hard cover with dust jacket
和 soft cover
。当我们查看 distinct 属性时,此属性将非常有用。
为了展示 Meilisearch 如何处理嵌套对象,我们创建了一个 details
属性,其中包含书籍的 isbn
代码、rating
和 pages
数量。
请注意,数据集包含一个 嵌套 JSON 对象。JSON 对象在索引过程中被分解成单独的字符串标记,这意味着单独的单词。这意味着每个值都会被标记化并索引,因此该值是可搜索的。
好的,让我们将数据添加到我们的 books
索引中。为此,我们将再次使用 curl
命令。请确保您在包含 books.json
文件的文件夹中执行该命令。
curl -i -X POST 'http://127.0.0.1:7700/indexes/books/documents' \
--header 'content-type: application/json' \
--header 'Authorization: Bearer your-master-key' \
--data @books.json
添加文档后,您应该会收到如下所示的响应
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2023-04-19T14:10:22.962629Z"
}
或者,您可以使用 JavaScript 代码将文档上传到您的索引。以下是一个示例。
const { MeiliSearch } = require('meilisearch')
const books = require('./books.json')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
index.addDocuments(books).then((res) => console.log(res))
}
main()
现在,如果您打开浏览器并导航到您的 Meilisearch 实例的主机地址(默认情况下:http://localhost:7700),您可以使用我们的 Web 界面 开始使用您刚创建的索引进行搜索。
这就是添加文档的全部内容!
步骤 3:使用 facets 计算文档的分布
为了改进我们的搜索,我们可以使用过滤器。它们会为特定属性索引数据,以便 Meilisearch 实例能够更快地检索数据。此外,过滤器允许构建 分面搜索界面,使用户能够按类别浏览数据并缩小搜索范围,从而加快搜索速度。
为了检索 facets 分布,我们首先必须定义一个 过滤器。对于本示例,我们想要确定 Douglas Adams
撰写的所有书籍的语言分布。因此,让我们为 language
属性定义一个过滤器。我们的数据集包含五种不同的语言。这对 facets 来说非常适合。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
await index.updateSettings({
filterableAttributes:
[
"language"
]
})
}
main()
假设我们想要确定 Douglas Adams
撰写的所有书籍的语言分布。换句话说,我们想知道有多少本书是用 eng
(英语)、esp
(西班牙语)或 fre
(法语)写成的。
您可以自己尝试解决这个问题。有关 facets 分布的信息和示例,请参阅 文档(向下滚动到“The facets distribution”)。您还可以在存储库 README 中找到 Meilisearch-js 函数列表。
我们预计 Douglas Adams
的结果如下。
{
hits:[ ... ],
query: 'Douglas Adams',
processingTimeMs: 0,
limit: 20,
offset: 0,
estimatedTotalHits: 11,
facetDistribution: { language: { eng: 10, esp: 1 } },
facetStats: {}
}
facets 分布的代码解决方案
解决方案:为了获得 facets 分布,我们可以将 facets
属性与我们的搜索查询一起传递。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
const distribution = await index
.search('Douglas Adams', {
facets: ['language']
})
console.log(distribution)
}
main()
很酷吧?让我们继续前进!
步骤 4:使用 distinct 属性避免重复
搜索 The Lord of the Rings 2
。注意到奇怪的地方了吗?
{
"id": "35",
"title": "The Lord of the Rings 2",
"author": "J.R.R. Tolkien/Alan Lee",
"cover": "hard cover",
"language": "eng",
"publisher": "Houghton Mifflin Harcourt",
"details": {
"isbn": "0618260587",
"rating": "4.50",
"pages": "1216"
},
"isbn13": "9780439785989"
},
{
"id": "38",
"title": "The Lord of the Rings 2",
"author": "J.R.R. Tolkien/Alan Lee",
"cover": "soft cover",
"language": "eng",
"publisher": "Houghton Mifflin Harcourt",
"details": {
"isbn": "0618260587",
"rating": "4.50",
"pages": "1216"
},
"isbn13": "9780439785989"
}
目前,我们的数据集包含具有不同 cover
类型的重复书籍。当用户搜索特定书籍时,我们不想让他们看到同一本书两次,仅仅因为它们有不同的封面类型。幸运的是,isbn13
属性对每本书都是唯一的;因此,我们可以将其用作distinct 属性以防止出现双重结果。
一个 distinct 属性 是一个字段,其值在返回的文档中始终是唯一的。我们想要将 isbn13
设置为 distinct 属性,这样 Meilisearch 不会返回具有相同 isbn13
值的结果。
我们鼓励您自己找到解决这个问题的方法,但如果您遇到困难,始终可以查看下面提供的代码解决方案。
为了验证您的解决方案是否有效,请尝试查询 The Lord of the Rings 2
。重复的结果应该消失了。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
const search = await index.search('The Lord of the Rings 2')
console.log(search)
}
main()
distinct 属性的代码解决方案
这里的解决方案是将 isbn13
定义为 distinct 属性,因为即使是不同封面版本的书籍,每本书也只有一个 ISBN。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
await index.updateDistinctAttribute('isbn13')
}
main()
都好了吗?让我们继续前进!
步骤 5:如何定义可搜索属性?
您认为当我们查询 13
时会发生什么?我给您几秒钟的时间思考。
好吧,它会返回包含数字 13
的所有对象。换句话说,我们会收到包含数字 13
的 isbn
代码的结果,以及 id = 13
或 id = 131
的对象。对于用户来说,搜索对象 ID 没有意义。
因此,我们可以手动将某些属性定义为 可搜索属性,而其他属性定义为不可搜索属性。使用 可搜索属性文档 自己尝试一下。别忘了 Meilisearch-js API 参考!
您可以通过查询 159
来验证您的解决方案。在没有定义可搜索属性的情况下,我们会收到 13 个结果,其中包含一个基于 ID 的匹配项。在将 id
设置为不可搜索之后,我们应该只收到 12 个结果。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
const search = await index.search('159')
console.log(search.estimatedTotalHits) // Output: 12
}
main()
可搜索属性的代码解决方案
解决方案如下所示。请注意,此可搜索属性数组按重要性顺序排序。
const { MeiliSearch } = require('meilisearch')
const main = async () => {
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'your-master-key'
})
const index = client.index('books')
await index.updateSearchableAttributes([
'author', 'title', 'details', 'publisher'
])
}
main()
太棒了,问题解决!
结论:是《哈利波特》还是《指环王》?
这就是本教程的结束。我们已经介绍了如何检索 facets 分布、设置可搜索属性、设置 distinct 属性以及 Meilisearch 如何处理嵌套对象。
随意修改代码以完全理解示例。 每个示例都链接到相关的 文档 页面,您可以在其中找到更多示例和有关不同 API 端点的信息。
祝您在搜索 GoodReads 图书数据时玩得开心! **您是否喜欢使用 Meilisearch? 请务必通过在 GitHub 上为 Meilisearch 点赞来表达您的喜爱!**
照片由 Susan Yin 提供