前往主页Meilisearch 标志
返回文章
2023年9月14日

使用 InstantSearch 通过 ID 优化分面

了解如何在 UI 中使用 InstantSearch 显示分面名称,同时通过其唯一标识符优化分面。

Carolina Ferreira
Carolina FerreiraMeilisearch 开发者布道师@CarolainFG
Refining facets by ID with InstantSearch

在本指南中,我们将深入探讨 Meilisearch 中的分面(faceting)概念,以及如何使用它来显示分面名称,同时通过 ID 进行筛选。

什么是分面?

分面是搜索引擎中一种用于将搜索结果分类为多个类别或“分面”的技术。这些分面可以是类别、标签、价格范围,甚至颜色。这使用户更容易导航和筛选结果,提供更精细和高效的搜索体验。

为什么使用 ID 作为分面筛选器?

对于大多数应用程序和用户而言,通过分面名称(例如电影类型)进行筛选通常是足够且直观的。分面名称是人类可读的,并能清晰地说明筛选器的作用。然而,在某些用例中,出于几个关键原因,更倾向于通过 ID 进行筛选:

  • 不易出错:ID 通常更简单且标准化,因此不易出现分面名称中可能存在的拼写错误或不一致。
  • 唯一标识符:在某些数据库中,分面名称可能会重复,但特性略有不同,而 ID 始终是唯一的。当您有名称相似或相同但属性不同的项目时,这尤其有用。

通过使用 ID 进行筛选,同时向用户显示相应的分面名称,您可以兼得效率和可用性。这样,您就利用了 ID 和名称的优势,使您的应用程序既健壮又用户友好。

ID 到名称的挑战

在 Meilisearch 中,分面是过滤器的一种特殊用例。本质上,您可以将添加到 filterableAttributes 列表中的任何属性用作分面。当您向搜索查询添加分面参数时,Meilisearch 将返回一个 [facetDistribution](https://meilisearch.org.cn/docs/reference/api/search#facetdistribution) 对象。此对象提供给定分面值之间匹配文档的数量分布。

 "facetDistribution":{
    "genres":{
      "Classics":6,
      "Comedy":1,
      "Coming-of-Age":1,
      "Fantasy":2,
      "Fiction":8,

    }
  }

但是,如果您已添加到 filterableAttributes 列表中的字段是 ID,则 facetDistribution 对象将返回这些 ID。

 "facetDistribution":{
    "genres":{
      "5":6,
      "6":1,
      "8":1,
      "13":2,
      "16":8,

    }
  }

尽管 ID 对于后端操作非常有用,但它们在前端不一定用户友好或有意义。这就是为什么您在用户界面中显示这些 ID 时,可能需要将它们映射回其相应的分面名称。

前端中的 ID 到名称映射

假设您有一个电影数据集,结构如下:

{
    "id": 5,
    "title": "Four Rooms",
    "overview": "It's Ted the Bellhop's first night on the job....",
    "genres": [
        {
            "id": 7,
            "name": "Crime"
        },
        {
            "id": 6,
            "name": "Comedy"
        },
    ],
    "release_date": 818467200,
}

您希望基于电影类型进行分面搜索。因此,您将 `genres.id` 添加到 可筛选属性 列表。当然,在 UI 中您希望显示 `genres.name`,这样用户在 UI 上看到的是“Crime”而不是“7”。

让我们深入了解如何使用 InstantSearch 和 instant-meilisearch 来实现这一点。

使用 InstantSearch 和 instant-meilisearch

InstantSearch 是一个用于构建搜索 UI 的开源前端库。Instant-meilisearch 是将 InstantSearch 与 Meilisearch 集成的首选搜索客户端。

要将 instant-meilisearch 与 InstantSearch 结合使用,您需要:

1. 导入所需的模块,包括 instantMeiliSearch

import { instantMeiliSearch } from '@meilisearch/instant-meilisearch'
import instantsearch from 'instantsearch.js'
import { searchBox, infiniteHits,refinementList } from 'instantsearch.js/es/widgets'

2. 使用您的 Meilisearch 主机和搜索 API 密钥建立 Meilisearch 客户端

const searchClient = instantMeiliSearch(
  'https://ms-7053a8dd7c09-72.lon.meilisearch.io',
  'meilisearchApiKey'
)

3. 使用您的 Meilisearch 索引名称和搜索客户端设置 instantsearch

const searchIndex = instantsearch({
  indexName: 'movies',
  searchClient
})

在本指南中,我们将使用 InstantSearch 的 refinementList 小部件将 ID 映射到用户友好的名称。

此小部件带有一个可选参数,名为 transformItems。此函数接收 items(或分面),并允许您在它们显示在 UI 上之前对其进行转换。它的参数中还包括完整的返回结果数据。

每个项目(或分面)都包含以下属性:

  • count:分面在结果集中出现的次数
  • value:用于优化的值(在本例中为 genres.id)
  • label:要显示的标签
  • highlighted:高亮显示的标签。此值在默认模板中显示

如您所见,highlightedrefinementlList 小部件中默认使用的标签。

有了这些信息,我们可以使用 transformItems 函数来显示 genres.name 而不是 genres.id

transformItems(items, { results }) {
    // The 'results' parameter contains the full results data
    return items.map(item => {
      // Initialize genreName with the existing highlighted label
      let genreName = item.highlighted;
      
      // Loop through results.hits to find a matching genre
      for (let hit of results.hits) {
        const matchedGenre = hit.genres.find(genre => genre.id.toString() === item.value);
        
        // Update genreName if a match is found
        if (matchedGenre) {
          genreName = matchedGenre.name;
          break;
        }
      }
      
      // Return the updated item with the new tagName as the highlighted value
      return {
        ...item,
        highlighted: genreName
      };
    });
  }

通过此配置,您可以高效地将 ID 映射到更用户友好的名称,从而提升用户的搜索体验。

完整的代码应如下所示:

import { instantMeiliSearch } from '@meilisearch/instant-meilisearch'
import instantsearch from 'instantsearch.js'
import { searchBox, infiniteHits,refinementList } from 'instantsearch.js/es/widgets'

const searchClient = instantMeiliSearch(
  'https://ms-7053a8dd7c09-72.lon.meilisearch.io',
  'meilisearchApiKey'
)
  
const searchIndex = instantsearch({
  indexName: 'movies',
  searchClient
})

const searchBox = instantsearch.widgets.searchBox({
  // ...
});

const hits = instantsearch.widgets.hits({
  // ...
});

const refinementList = instantsearch.widgets.refinementList({
  container: '#facets',
  attribute: 'genres.id',
  transformItems(items, { results }) {
    return items.map(item => {
      let genreName = item.highlighted; 
      for (let hit of results.hits) {
        const matchedGenre = hit.genres.find(genre => genre.id.toString() === item.value);
        if (matchedGenre) {
          genreName = matchedGenre.name;
          break;
        }
      }
      return {
        ...item,
        highlighted: genreName
      };
    });
  }
})

searchIndex.addWidgets([searchBox, infiniteHits, refinementList]);

searchIndex.start()

此示例展示了如何使用 原生 JavaScript 实现 ID 到名称的映射,但您也可以使用您偏好的前端框架实现类似的效果。请查阅 ReactVue 的相关文档。

如需了解更多关于 Meilisearch 的信息,您可以订阅我们的新闻通讯。您可以通过查看路线图并参与我们的产品讨论来了解更多关于我们产品的信息。

对于其他任何事情,请在 Discord 上加入我们的开发者社区。

Meilisearch indexes embeddings 7x faster with binary quantization

Meilisearch 使用二值量化将嵌入索引速度提高 7 倍

通过在向量存储 Arroy 中实现二值量化,已显著减少了大型嵌入的磁盘空间使用和索引时间,同时保持了搜索的相关性和效率。

Tamo
Tamo2024年11月29日
How to add AI-powered search to a React app

如何将 AI 驱动的搜索添加到 React 应用中

使用 Meilisearch 的 AI 驱动搜索构建一个 React 电影搜索与推荐应用。

Carolina Ferreira
Carolina Ferreira2024年9月24日
Meilisearch is too slow

Meilisearch 太慢了

在这篇博文中,我们将探讨 Meilisearch 文档索引器所需的增强功能。我们将讨论当前的索引引擎、其缺点以及优化性能的新技术。

Clément Renault
Clément Renault2024年8月20日