Observability:如何运用 Elastic App Search 快速建立出色的 React 搜索体验

本文详细介绍如何使用Elastic的开源SearchUI库,结合React构建功能丰富且高效的搜索体验。涵盖数据建模、底层逻辑设计、用户体验优化等内容,指导开发者避开常见陷阱,实现30分钟内快速搭建搜索功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

建立搜索体验是一项艰苦的工作。 乍一看似乎很容易:建立一个搜索栏,将数据放入数据库,然后让用户输入对该数据库的查询。 但是,在数据建模,底层逻辑以及(当然)总体设计和用户体验方面,有很多事情要考虑。

我们将逐步介绍如何使用 Elastic 的开源 Search UI 库构建出色的基于 React 的搜索体验。 大约需要30分钟,然后你便可以将搜索带到需要它的任何应用程序中。

但是首先,是什么使创建搜索如此具有挑战性?

搜索是很难创建的

开发人员在搜索开发中采用许多错误的假设。比如许多相信的假设:

  • “知道他们要寻找的客户将按照你期望的方式进行搜索。”
  • “你可以编写一个查询解析器,该解析器将始终成功解析查询。”
  • “一旦设置,下周搜索将以相同的方式进行。”
  • “同义词很容易。”
  • ...

得出的结论是,搜索面临许多挑战--而且这些挑战并不简单。 你需要考虑如何管理状态,构建用于过滤,构面,排序,分页,同义词,语言处理等等的组件,等等。 但是,总而言之:

建立出色的搜索需要两个复杂的部分:

(1)搜索引擎,它提供用于增强搜索功能的 API

(2)搜索库,它描绘了搜索体验。

对于搜索引擎,我们将查看 Elastic App Search。

为了获得搜索体验,我们将介绍一个操作系统搜索库:Search UI。

完成后,将如下所示。你也可以在地址上进行在线体验。

搜索引擎: Elastic App Search

App Search 可作为付费托管服务或免费的自助托管发行版提供。 我们将在本教程中使用托管服务,但是请记住,如果你自己托管,你的团队可以免费使用带有基本许可的 Search UI 和 App Search

计划:将代表有史以来最好的视频游戏的文档编入搜索引擎,然后设计和优化搜索体验以对其进行搜索。

首先,注册14天的试用期-无需信用卡。

创建一个引擎。 你可以选择13种不同的语言。

我们将其命名为 video-games,并将语言设置为英语。

下载最佳视频游戏数据集。如果你由于一些原因不能下载这个数据文件,请到地址 https://github.com/liu-xiao-guo/elastic_app_search 进行下载。

然后使用导入程序将其上传到 App Search。

接下来,单击进入引擎,然后选择 “Credentials” 选项卡。

使用仅对 video-games 引擎具有 Limited Engine Access 的方式创建新的 Public Search Key。

我们可以记下我们刚创建的 Public Search Key 及 Host Indentifier 以便下面之用。

尽管看起来我们目前做的并不多,但我们现在拥有功能全面的搜索引擎,可以使用完善的搜索API来搜索我们的视频游戏数据。

到目前为止,这是我们所做的:

  • 创建了一个搜索引擎
  • 建立了索引文档
  • 创建一个默认的索引 schema
  • 创建了一个有限的可以用于外界访问的凭证(credential)

让我们开始使用 “Search UI” 来建立我们的搜索体验。

搜索库:Search UI

我们将使用 create-react-app 脚手架实用程序创建一个 React 应用:

npm install -g create-react-app
create-react-app video-game-search --use-npm
cd video-game-search

在此基础上,我们将安装 Search UI 和 App Search 连接器:

npm install --save @elastic/react-search-ui @elastic/search-ui-app-search-connector

并以开发模式启动该应用程序:

npm start

在你喜欢的文本编辑器中打开 src/App.js

我们将从一些样板代码开始,注意评论部分!

// Step #1, import statements
import React from "react";
import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";
import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";
import { Layout } from "@elastic/react-search-ui-views";
import "@elastic/react-search-ui-views/lib/styles/styles.css";
// Step #2, The connector
const connector = new AppSearchAPIConnector({
  searchKey: "[YOUR_SEARCH_KEY]",
  engineName: "video-games",
  hostIdentifier: "[YOUR_HOST_IDENTIFIER]"
});

// Step #3: Configuration options
const configurationOptions = {
  apiConnector: connector
  // Let's fill this in together.
};

// Step #4, SearchProvider: The finishing touches
export default function App() {
  return (
    <SearchProvider config={configurationOptions}>
      <div className="App">
        <Layout
        // Let's fill this in together.
        />
      </div>
    </SearchProvider>
  );
}

Step 1: 导入声明

我们需要导入我们的 Search UI 依赖关系和 React。

核心组件,连接器和视图组件包含在三个不同的程序包中:

  1. @elastic/search-ui-app-search-connector
  2. @elastic/react-search-ui
  3. @elastic/react-search-ui-views

继续进行时,我们将详细了解它们

import React from "react";
import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";
import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";
import { Layout } from "@elastic/react-search-ui-views";

我们还将为该项目导入默认样式表,这将使我们拥有良好的外观,而无需编写我们自己的 CSS 行:

import "@elastic/react-search-ui-views/lib/styles/styles.css";

Step 2:  连接器

我们有来自 App Search 的 Public Search Key 和 Host Identifier。

是时候让他们工作了!

Search UI 中的连接器对象使用 credentials 连接到 App Search 和超级搜索:

const connector = new AppSearchAPIConnector({
  searchKey: "[YOUR_SEARCH_KEY]",
  engineName: "video-games",
  hostIdentifier: "[YOUR_HOST_IDENTIFIER]"
});

搜索用户界面可与任何搜索 API 配合使用。 但是通过连接器可以使搜索 API 正常工作,而无需进行任何更深入的配置。

Step 3: configurationOptions
 

在深入探讨 configurationOptions 之前,让我们花点时间进行反思。

我们将一组数据导入了搜索引擎。 但是,它是什么样的数据?

我们对数据了解的越多,我们就会越了解如何将数据呈现给搜索者。 这样一来,你便可以了解如何配置搜索体验。

我们来看一个对象,这是该数据集中所有对象中的一个:

{ 
  "id":"final-fantasy-vii-ps-1997",
  "name":"Final Fantasy VII",
  "year":1997,
  "platform":"PS",
  "genre":"Role-Playing",
  "publisher":"Sony Computer Entertainment",
  "global_sales":9.72,
  "critic_score":92,
  "user_score":9,
  "developer":"SquareSoft",
  "image_url":"https://r.hswstatic.com/w_907/gif/finalfantasyvii-MAIN.jpg"
}

我们看到它有几个文本字段,例如 name,year,platform 等等,还有一些数字字段,例如 critic_score,global_sales 和 user_score。

如果我们提出三个关键问题,我们将足够了解,以提供扎实的搜索体验:

  • 大多数人将如何搜索? 以视频游戏的名称命名。
  • 大多数人想要看到的结果是什么? 视频游戏的名称,类型,发行商,得分和平台。
  • 大多数人将如何过滤,排序和构面? 按得分,体裁,发布者和平台分类。

然后,我们可以将这些答案转换为我们的 configurationOptions:

const configurationOptions = {
  apiConnector: connector,
  searchQuery: {
    search_fields: {
      // 1. Search by name of video game.
      name: {}
    },
    // 2. Results: name, genre, publisher, scores, and platform.
    result_fields: {
      name: {
        // A snippet means that matching search terms will be wrapped in <em> tags.
        snippet: {
          size: 75, // Limit the snippet to 75 characters.
          fallback: true // Fallback to a "raw" result.
        }
      },
      genre: {
        snippet: {
          size: 50,
          fallback: true
        }
      },
      publisher: {
        snippet: {
          size: 50,
          fallback: true
        }
      },
      critic_score: {
        // Scores are numeric, so we won't snippet.
        raw: {}
      },
      user_score: {
        raw: {}
      },
      platform: {
        snippet: {
          size: 50,
          fallback: true
        }
      },
      image_url: {
        raw: {}
      }
    },
    // 3. Facet by scores, genre, publisher, and platform, which we'll use to build filters later.
    facets: {
      user_score: {
        type: "range",
        ranges: [
          { from: 0, to: 5, name: "Not good" },
          { from: 5, to: 7, name: "Not bad" },
          { from: 7, to: 9, name: "Pretty good" },
          { from: 9, to: 10, name: "Must play!" }
        ]
      },
      critic_score: {
        type: "range",
        ranges: [
          { from: 0, to: 50, name: "Not good" },
          { from: 50, to: 70, name: "Not bad" },
          { from: 70, to: 90, name: "Pretty good" },
          { from: 90, to: 100, name: "Must play!" }
        ]
      },
      genre: { type: "value", size: 100 },
      publisher: { type: "value", size: 100 },
      platform: { type: "value", size: 100 }
    }
  }
};

我们已经将 Search UI 连接到我们的搜索引擎,现在我们有一些选项可以控制我们如何搜索数据,显示结果并探索这些结果。 但是我们需要一些东西来将所有内容绑定到 Search UI 的动态前端组件。

Step 4: SearchProvider

这是统治所有对象的对象。 SearchProvider 是所有其他组件嵌套的地方。

Search UI 提供了一个 Layout 组件,用于绘制典型的搜索布局。 有很深的自定义选项,但我们不会在本教程中介绍。

我们将做两件事:

  1. 将 configurationOptions 传递给 SearchProvider。
  2. 将一些结构性构建基块放入 Layout 中,并添加两个基本组件:SearchBox 和 Results。
export default function App() {
  return (
    <SearchProvider config={configurationOptions}>
      <div className="App">
        <Layout
          header={<SearchBox />}
          // titleField is the most prominent field within a result: the result header.
          bodyContent={<Results titleField="name" urlField="image_url" />}
        />
      </div>
    </SearchProvider>
  );
}

至此,我们已经在前端建立了基础。 在运行此后端之前,还有一些其他细节需要在后端解决。 我们还应该研究相关性模型,以便针对该项目的独特需求微调搜索。

重新进入搜索平台

App Search 具有强大且完善的搜索引擎功能。 它使曾经复杂的调优变得更加有趣。 只需单击几下,我们便可以进行细粒度的相关性调整和无缝的模式更改。

我们将首先调整 schema 以使其实际运行。

登录到 App Search,输入 video-games 引擎,然后单击 “Manage” 部分下的 “Schema”。

索引的 schema 将被展示出来。 默认情况下,这11个字段中的每一个均被视为文本。

在 configurationOptions 对象中,我们定义了两个范围构面来帮助我们搜索数字:user_score 和 critic_score。 为了使 range facet 按预期工作,字段类型必须为数字(number)。

单击每个字段旁边的下拉菜单,将其更改为数字,然后单击 “Update Types”。引擎会即时重新更新索引。 然后,当我们将构面(facet)组件添加到布局中时,范围过滤器将按预期运行。 现在,进入真正的漂亮东西。

下面的部分是高度相关的

具有三个关键的相关功能:Synonyms,Curations 和 Relevance Tuning。

在边栏中的 “Search Settings” 部分下选择每个功能:

Synonyms

世界各地的人们使用不同的词来形容事物。 同义词可帮助你创建被视为一个或一组相同的术语集。

就 video game 搜索引擎而言,我们知道人们会希望找到 Final Fantasy。 但是也许他们会改用FF。

单击进入同义词,然后选择创建同义词集并输入术语:

单击 Save。 你可以根据需要添加任意多个同义词集。

现在,搜索 FF 与搜索 Final Fantasy 的权重相同。

Curations

Curations 是最最让人喜欢的。 如果有人搜索 Final Fantasy 或 FF,该怎么办? 系列赛中有很多游戏-他们会得到哪些?

默认情况下,前五个结果如下所示:

  1. Final Fantasy VIII
  2. Final Fantasy X
  3. Final Fantasy Tactics
  4. Final Fantasy IX
  5. Final Fantasy XIII

这似乎不正确……Final Fantasy VII 是所有游戏中最好的 Final Fantasy 游戏。 而且 Final Fantasy XIII 不是很好! 😜

我们可以做到这一点,以便搜索 Final Fantasy 的人会收到 Final Fantasy VII 作为第一结果吗? 我们可以从搜索结果中删除 Final Fnatasy XIII 吗?

我们可以!

单击 “Curations”,然后输入查询:“Final Fantasy”。

接下来,通过抓住表格最左侧的把手将 “Final FantasyVII” 文档拖到 “Promoted Documents” 部分。 然后单击 “Final Fantasy XIII” 文档上的 “Hide Result” 按钮(那个有一条线穿过眼睛的图标,下图列表中第三个图标):

现在,执行 “Final Fantasy” 或 “FF” 搜索的任何人都将首先看到 “Final Fantasy VII”。

他们根本看不到 Final Fantasy XIII。 哈!

我们可以升级和隐藏许多文档。 我们甚至可以对升级后的文档进行排序,因此我们可以完全控制每个查询顶部显示的内容。

Relevance tuning

单击边栏中的 “Relevance Tuning”。

我们搜索一个文本字段:name 字段。 但是,如果我们有多个文本字段可供人们搜索,例如 name 字段和 description 字段,该怎么办? 我们正在使用的 video game 数据集不包含 description 字段,因此我们假想一些文档以进行仔细考虑。

说我们的文档看起来像这样:

{ 
  "name":"Magical Quest",
  "description": "A dangerous journey through caves and such." 
},
{ 
  "name":"Dangerous Quest",
  "description": "A magical journey filled with magical magic. Highly magic." 
}

如果有人想找到游戏 Magical Quest,他们会输入该内容作为查询。 但是第一个结果将是 Dangerous Quest:

为什么? 因为在 “Dangerous” 的 description 中 “Magical” 一词出现了3次,所以搜索引擎不会知道一个字段比另一个字段更重要。 然后,它将使 “Dangerous Quest” 的排名更高。 这就是为什么存在相关性调整的难题。

我们可以选择一个字段,除其他外,还可以增加其相关性的权重:

我们看到,当我们增加权重时,正确的项目 “Magical Quest” 上升到顶部,因为 name 字段变得更重要。 我们需要做的就是将滑块拖动到更高的值,然后单击 “Save”。

现在,我们已经使用 App Search 实现了如下的任务:

  • 调整 schema,并将 user_score 和 critic_score 更改为数字字段。
  • 微调关联(relevance)模型。

这样就总结出了精美的“仪表板”功能-每个功能都有一个匹配的 API 端点,如果你不是 GUI 的用户,则可以使用它们使程序以编程方式工作。

现在,让我们结束 UI。

最后加工

此时,你的 UI 应该可以正常工作了。 尝试一些查询。 首先要说的是,我们缺少探索结果的工具,例如过滤,分面(facet),排序等,但是搜索有效。 我们需要完善用户界面。

在初始的 src/App.js 文件中,我们导入了三个基本组件:

import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";

根据我们为配置选项定义的内容,让我们添加更多内容。

导入以下组件将启用UI中缺少的功能:

  • PagingInfo:在当前页面上显示信息。
  • ResultsPerPage:配置每页上显示多少个结果。
  • Paging:浏览不同的页面。
  • Facet:以数据类型独有的方式过滤和浏览数据。
  • Sort:重新定向给定字段的结果。
import {
  PagingInfo,
  ResultsPerPage,
  Paging,
  Facet,
  SearchProvider,
  Results,
  SearchBox,
  Sorting
} from "@elastic/react-search-ui";

导入后,可以将组件放置到布局中。

布局组件将页面分为多个部分,可以通过 prop 将组件放置在这些部分中。

它包含以下部分:

  • header:搜索框/栏
  • bodyContent:结果容器
  • sideContent:侧边栏,其中包含构面和排序选项
  • bodyHeader:围绕结果的“包装器”,其中包含上下文丰富的信息,例如当前页面和每页结果数
  • bodyFooter:用于在页面之间快速导航的分页选项

组件呈现数据。根据我们在 configurationOptions 中提供的搜索设置获取数据。现在,我们将每个组件放置在适当的布局部分中。

例如,我们在 configurationOptions 中描述了五个方面的维度,因此我们将创建五个方面的组件。每个 Facet 组件都将使用“字段”属性作为返回数据的键。

我们将它们与我们的 Sorting 组件一起放在 sideContent 部分中,然后将 Paging,PagingInfo 和 ResultsPerPage 组件放在最适合它们的部分中:

<Layout
  header={<SearchBox />}
  bodyContent={<Results titleField="name" urlField="image_url" />}
  sideContent={
    <div>
      <Sorting
        label={"Sort by"}
        sortOptions={[
          {
            name: "Relevance",
            value: "",
            direction: ""
          },
          {
            name: "Name",
            value: "name",
            direction: "asc"
          }
        ]}
      />
      <Facet field="user_score" label="User Score" />
      <Facet field="critic_score" label="Critic Score" />
      <Facet field="genre" label="Genre" />
      <Facet field="publisher" label="Publisher" isFilterable={true} />
      <Facet field="platform" label="Platform" />
    </div>
  }
  bodyHeader={
    <>
      <PagingInfo />
      <ResultsPerPage />
    </>
  }
  bodyFooter={<Paging />}
/>

现在,让我们看一下本地开发环境中的搜索体验。

好多了! 我们提供了丰富的选项来探索搜索结果。

我们引入了一些额外的好处,例如多种排序选项,并且通过添加单个标志使发布者的面可过滤。 尝试使用空白查询进行搜索并浏览所有选项。

最后,让我们看一下搜索体验的最后一项功能。 这是一个受欢迎的...

自动完成 (Autocomplete)

搜索者喜欢自动完成功能,因为它可以提供即时反馈。 它的建议有两种形式:结果和查询。 取决于哪种口味,搜索者将收到相关结果或可能导致结果的潜在查询。

我们将重点关注自动填充作为一种查询建议形式。

这需要两个快速更改。

首先,我们需要将自动完成功能添加到 configurationOptions 对象中:

const configurationOptions = {
  autocompleteQuery: {
    suggestions: {
      types: {
        documents: {
          // Which fields to search for suggestions
          fields: ["name"]
        }
      },
      // How many suggestions appear
      size: 5
    }
  },
  ...
};

其次,我们需要根据 SearchBox 启用自动填充功能:

...
        <Layout
          ...
          header={<SearchBox autocompleteSuggestions={true} />}
/>
...

是的,就是这样。

尝试搜索-键入时,将显示自动完成查询建议。

总结

现在,我们拥有美观的功能性搜索体验。 而且,我们避免了人们在尝试实施搜索时经常会遇到的一堆陷阱。 30分钟还不错,你不是说吗?你可以在地址进行一个完美的体验。

如果你想进一步动态生成数据集,请参阅文章https://swiftype.com/documentation/app-search/api/documents#create

你可以在如下地址找到这个项目的源码:https://github.com/liu-xiao-guo/swiftype-video-game-search

参考:

【1】How to Build Great React Search Experiences Quickly

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值