👉
这篇文章最初由客座作者 Michiel Mulders 于 2021 年 2 月发表。当时,Meilisearch 版本为 v0.18。它已由 Carolina Ferreira 更新以适用于 Meilisearch v1。

本教程使用 GoodReads 的样本数据集,该数据集由 Jealous Leopard 在 Kaggle 上传。

本教程的目的是深入了解 Meilisearch 的高级概念,例如

  • Meilisearch 如何处理嵌套对象
  • 如何使用 facets 计算文档的分布
  • 如何使用 distinct 属性
  • 如何使用 settings 对象定义可搜索属性

那么,学习本教程需要哪些先决条件呢?

先决条件

要学习本独立教程,您需要对 Meilisearch 有基本了解。如果您不确定,请随时查看关于 搜索诺贝尔奖获得者 的先前教程,但这并不是必需的。

其他先决条件包括。

准备好了吗?让我们深入了解吧!

项目设置和 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 索引。请注意,您的 createdAtupdatedAt 值可能与我们的不同。

{
  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"
    }
}

每本书都有一个唯一的 idcover 属性有三个可能的值:hard coverhard cover with dust jacketsoft cover。当我们查看 distinct 属性时,此属性将非常有用。

为了展示 Meilisearch 如何处理嵌套对象,我们创建了一个 details 属性,其中包含书籍的 isbn 代码、ratingpages 数量。

请注意,数据集包含一个 嵌套 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 的所有对象。换句话说,我们会收到包含数字 13isbn 代码的结果,以及 id = 13id = 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 提供