# 为文档站点添加搜索功能

Algolia 的优秀团队构建并开源了 DocSearch (opens new window),这是一套专门用于索引文档站点数据并快速添加搜索栏的工具集。

本文将展示如何使用与 Typesense 兼容的定制版 DocSearch。事实上,Typesense 官方文档站点上的搜索栏就是使用这个定制版本构建的。

Typesense 的定制版 DocSearch 由两个组件组成:

  1. typesense-docsearch-scraper (opens new window) - 一个网络爬虫,用于扫描您的文档站点并将内容索引到 Typesense 中。
  2. typesense-docsearch.js (opens new window) - 一个 JavaScript 库,用于在您的文档站点上添加搜索栏。当终端用户开始在搜索栏中输入时,它会查询由 DocSearch 爬虫构建的内容索引。

提示:在非文档站点上的使用

尽管 DocSearch 最初是为文档站点构建的,但它实际上可以用于任何具有结构化、层次化且页面间 HTML 标记一致的网站。

# 第一步:设置 DocSearch 爬虫

首先,我们需要设置爬虫指向您的文档站点。运行爬虫将为网站上的每个单词生成索引,然后将其上传到您的 Typesense 服务器。这正是使您的网站可搜索的关键!

# 创建 DocSearch 爬虫配置文件

根据以下模板之一创建您自己的 config.json 文件,指向您的文档站点:

这里是官方 DocSearch 爬虫文档 (opens new window),描述了所有可用的配置选项。

注意

Algolia 的 DocSearch 仓库已被归档,因为 Algolia 在 2022 年 2 月迁移到了其专有的闭源爬虫。因此,他们不再维护开源版本。

鉴于此,Typesense 计划维护并开发 一个分支 (opens new window)。因此,您可以安全地忽略其文档中的弃用警告。

长期来看,我们计划将所有文档更新到 Typesense 的仓库以避免混淆。

# 修改 DocSearch Scraper 配置文件

使用模板文件后,您需要修改配置中的几个字段:

  • index_name - 应为一个唯一字符串,用于标识您的网站。这对应下文前端配置中的 typesenseCollectionName
  • start_urls - 对应您网站的 URL。
  • stop_urls - 需要忽略的 URL 数组。例如,如果网站上有变更日志,您可能希望忽略它,以免干扰实际内容的搜索结果。
  • sitemap_urls - (仅限 Docusaurus) 您需要修改此 URL 以匹配,就像修改 start_urls 一样。(此 XML 文件由 Docusaurus 在构建过程中自动生成。)
  • lvl1 - (仅限 Docusaurus) 将 header h1 改为 article h1, header h1

提示:抓取运行在 localhost 上的站点

如果您在 localhost 上运行 Typesense,并且使用 Docker 运行爬虫,则需要在 config.json 文件中修改一些内容。

start_urlssitemap_urls 中,您需要将目标 URL 改为 host.docker.internal,以确保爬虫能在主机上找到正确的站点,而不是在容器内部寻找。

您需要在 :80 端口运行站点,因为如果站点托管在其他端口,爬虫可能会出现意外行为。

TIP

请注意 index_name(爬虫配置中)和 typesenseCollectionName(前端配置中)的命名差异。这是因为 Algolia 将文档集合称为 "index",而 Typesense 将其称为 collection。该爬虫最初是从 Algolia 分叉而来,为了保持与生态系统的向后兼容性,我们刻意保留了这一命名。

TIP

如果您查看 Typesense 实例的日志,可能会发现它报告的索引/集合名称类似于 foo_1675838072 而不是 foo。这是因为每次爬虫运行时:

  • 它会创建一个名为 foo_<当前unix时间戳> 的新集合
  • 创建/更新一个名为 foo 的别名,指向 foo_<当前unix时间戳>
  • 删除之前抓取的文档版本(存储在 foo_<之前的时间戳> 中)

因此,在配置前端搜索引擎时,您应该将索引/集合名称指定为 foo 而不是 foo_<unix时间戳>

# 添加 DocSearch 元标签(可选)

爬虫会自动从 DocSearch 元标签中提取信息,并将 content 值附加到该页面上提取的所有记录中。这是按自定义属性筛选搜索结果的绝佳方式。

<meta name="docsearch:{$NAME}_tag" content="{$CONTENT}" />

示例:如果您在一组特定页面上有以下标记:

<meta name="docsearch:language_tag" content="en" />
<meta name="docsearch:version_tag" content="1.2.4" />

这些页面上提取的所有记录都会包含值为 enlanguage_tag 属性,以及值为 1.24version_tag 属性。您可以在 filter_by 中使用这些属性来限制搜索范围到特定的记录集。

TIP

必须在 $NAME 变量末尾添加 _tag 后缀,该属性才会被保存到 schema 中。

# 运行爬虫程序

运行爬虫最简单的方式是使用 Docker。

  1. 安装 Docker (opens new window)

  2. 安装 jq 工具 (opens new window)

  3. 确保您的 Typesense 服务器正常运行

  4. 创建 .env 文件并填入以下内容,请根据您的实际情况替换对应值:

    TYPESENSE_API_KEY=xyz
    TYPESENSE_HOST=xxx.a1.typesense.net
    TYPESENSE_PORT=443
    TYPESENSE_PROTOCOL=https
    

    TIP

    如果您是自托管 Typesense,通常可以在 /etc/typesense/typesense-server.ini 文件中找到 API 密钥和端口号。

    主机名应填写您服务器的 FQDN 或 IP 地址。

    默认情况下,自托管的 Typesense 使用 HTTP 协议,因此您可能需要将 https 改为 http(除非您在 ini 文件中指定了 ssl-certificatessl-certificate-key)。

    TIP

    如果您在 localhost 上运行 Typesense 并使用 Docker 运行爬虫,使用 TYPESENSE_HOST=localhost 将无法工作,因为此处的 localhost 指的是容器内的本地主机。您需要让 Docker 容器内的爬虫能够连接到宿主机上运行的 Typesense。请按照此处说明 (opens new window)使用适当的主机名来指向您的 Docker 宿主机。例如在 macOS 上,您应该使用:TYPESENSE_HOST=host.docker.internal

  5. 运行爬虫:

    docker run -it --env-file=/path/to/your/.env -e "CONFIG=$(cat config.json | jq -r tostring)" typesense/docsearch-scraper:0.11.0
    

这将抓取您的文档站点并将其索引到 Typesense 中。

TIP

上述 Docker 命令将以交互模式运行爬虫,并将日志输出到 stdout。

如果需要,您可以通过在命令末尾添加 | tee scraper-output.txt 来同时将输出发送到 stdout 和文件。这很有帮助,因为输出可能会非常冗长。

您也可以通过将 -it 标志替换为 -d 来以守护进程模式 (opens new window)运行爬虫。

# 常见挑战与复杂用例的处理技巧

以下是在 Docker 容器内运行爬虫时应对常见挑战的一些技巧:

# 传递配置文件路径而非配置字符串

上述示例中使用了 jq 工具将配置文件解析为 JSON 字符串,然后作为 CONFIG 环境变量传递。

如果您没有 jq 工具,可以了解另一种方式:将配置文件路径传递给 CONFIG 变量,这样系统会从该路径读取文件内容。

只需确保配置文件在容器内可访问。换句话说,您需要通过卷挂载方式提供该文件,如下例所示:

docker run -it \
  -v "/path/to/config/dir/on/your/machine:/tmp/search" \
  -e "CONFIG=/tmp/search/typesense.json" \
  typesense/docsearch-scraper:0.11.0

# 信任内部 CA 颁发的证书

如果您要爬取的网站使用了内部 CA(常见于企业内网等场景)颁发的证书,您需要让容器信任该 CA。为此,您可以挂载包含受信任 CA 的文件,并将其作为命令行参数传递。

以下示例中,当前目录下的 ca-chain.crt 文件将被添加到受信任的 CA 列表中:

docker run -it \
  --mount type=bind,source="$(pwd)/ca-chain.crt",target=/etc/ssl/certs/ca-certificates.crt \
  --env "REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt" \
  --env-file=/path/to/your/.env  \
  -e "CONFIG=$(cat config.json | jq -r tostring)" \
  typesense/docsearch-scraper:0.11.0

# 在命令行设置环境变量而非使用 .env 文件

如果您不想使用 .env 文件或在当前环境中无法使用,也可以通过命令行传递所有变量:

docker run -it \
  -e "TYPESENSE_API_KEY=xyz" \
  -e "TYPESENSE_HOST=xxx.a1.typesense.net" \
  -e "TYPESENSE_PORT=443" \
  -e "TYPESENSE_PROTOCOL=https" \
  -e "CONFIG=$(cat config.json | jq -r tostring)" \
  typesense/docsearch-scraper:0.11.0

# 主机解析

如果您的爬虫依赖容器内不可用的主机解析,可以通过命令行添加主机条目:

docker run -it \
  --add-host intranet.company.com:10.1.2.3 \
  --env-file=/path/to/your/.env  \
  -e "CONFIG=$(cat config.json | jq -r tostring)" \
  typesense/docsearch-scraper:0.11.0

# 自定义请求头

您可以通过在配置文件中添加 headers 对象来为爬虫的请求添加自定义 HTTP 头。这在需要添加特定认证头或其他用途时非常有用。

带自定义头的配置示例:

{
  "index_name": "your_docs",
  "start_urls": ["https://your-site.com"],
  "headers": {
    "Authorization": "Bearer your-token-here",
    "Custom-Header": "custom-value"
  },
  // ... 其余配置
}

您指定的请求头将会应用于爬虫发出的所有请求。

# 认证方式

如果您需要爬取需要认证的内容,以下方式已内置支持:

# 基础 HTTP 认证

使用此认证方式需设置以下环境变量:

  • DOCSEARCH_BASICAUTH_USERNAME
  • DOCSEARCH_BASICAUTH_PASSWORD
# Cloudflare Zero Trust (CF) 认证

使用此认证方式需设置以下环境变量:

  • CF_ACCESS_CLIENT_ID
  • CF_ACCESS_CLIENT_SECRET
# Google Identity-Aware Proxy (IAP) 身份感知代理

要使用此认证方式,请设置以下环境变量:

  • IAP_AUTH_CLIENT_ID
  • IAP_AUTH_SERVICE_ACCOUNT_JSON
# Keycloak (KC) 身份认证

要使用此认证方式,请设置以下环境变量:

  • KC_URL
  • KC_REALM
  • KC_CLIENT_ID
  • KC_CLIENT_SECRET

# 集成到 CI / 部署到服务器

如果您是首次设置 Typesense,请直接跳转到下一节。但一旦确认爬虫可以正常工作且网站能生成连贯的搜索结果后,您应该建立持续爬取网站内容的机制。

TIP

Typesense Cloud (opens new window) 中,我们仅为您托管 Typesense 集群。您仍需在 CI 流水线/基础设施中运行爬虫来更新索引。

爬虫 Docker 容器是无状态的,因此可以在任何支持运行无状态 Docker 容器的平台上运行,例如:

以及更多平台。我们建议在 CI 中运行爬虫,这样您的搜索索引将始终保持最新(而不是使用例如每天运行一次的 cron 任务)。

# Docusaurus 站点的前提条件

为 Docusaurus 站点设置 DocSearch 爬虫时,请注意:

  1. 必须提供 sitemap.xml 文件才能正常使用爬虫。生成方法如下:

    • 安装站点地图插件:
      npm install @docusaurus/plugin-sitemap --save
      
    • 添加到 docusaurus.config.js 中:
      plugins: [
        [
          '@docusaurus/plugin-sitemap',
          {
            // 插件选项
          },
        ],
      ],
      
    • 生成站点地图:
      npm run build
      
  2. 本地测试时,请先启动站点服务:

    npm run serve
    
  3. 运行爬虫前请确保 Typesense 服务器已启动

# 第二步:为文档站点添加搜索栏

# 选项 A:Docusaurus 驱动的站点

如果使用 Docusaurus (opens new window) 作为文档框架,可以通过 docusaurus-theme-search-typesense (opens new window) 插件为站点添加搜索栏。

$ npm install docusaurus-theme-search-typesense@next --save


# 

$ yarn add docusaurus-theme-search-typesense@next

#

$ pnpm add docusaurus-theme-search-typesense@next


将以下配置添加到您的 `docusaurus.config.js` 文件中:

```javascript
{
  themes: ['docusaurus-theme-search-typesense'],
  themeConfig: {
    typesense: {
      // 替换为您索引/集合的名称
      // 应与爬虫配置文件中 "config.json" 的 "index_name" 字段匹配
      typesenseCollectionName: 'docusaurus-2',

      typesenseServerConfig: {
        nodes: [
          {
            host: 'xxx-1.a1.typesense.net',
            port: 443,
            protocol: 'https',
          },
          {
            host: 'xxx-2.a1.typesense.net',
            port: 443,
            protocol: 'https',
          },
          {
            host: 'xxx-3.a1.typesense.net',
            port: 443,
            protocol: 'https',
          },
        ],
        apiKey: 'xyz',
      },

      // 可选:Typesense 搜索参数:https://typesense.org/docs/0.24.0/api/search.html#search-parameters
      typesenseSearchParameters: {},

      // 可选
      contextualSearch: true,
    },
  }
}

按照这些说明 (opens new window)为搜索组件添加样式。

# 选项 B:基于 Vuepress 的站点

如果您使用 Vuepress (opens new window) 作为文档框架(例如 Typesense 自己的文档站点),这里有一个 Vue 组件 (opens new window)可供使用。

将该组件复制到 .vuepress/components/TypesenseSearchBox.vue 并根据需要进行修改。

然后在您的 .vuepress/config.js 文件中添加一个名为 typesenseDocsearch 的键,内容如下:

{
  themeConfig: {
    typesenseDocsearch: {
      typesenseServerConfig: {
        nearestNode: {
          host: 'xxx.a1.typesense.net',
          port: 443,
          protocol: 'https',
        },
        nodes: [
          {
            host: 'xxx-1.a1.typesense.net',
            port: 443,
            protocol: 'https',
          },
          {
            host: 'xxx-2.a1.typesense.net',
            port: 443,
            protocol: 'https',
          },
          {
            host: 'xxx-3.a1.typesense.net',
            port: 443,
            protocol: 'https',
          },
        ],
        apiKey: '<your-search-only-api-key>',
      },
      typesenseCollectionName: 'docs', // 应与您在爬虫配置中使用的集合名称匹配
      typesenseSearchParams: {
        num_typos: 1,
        drop_tokens_threshold: 3,
        typo_tokens_threshold: 1,
        per_page: 6,
      },
    },
  }
}

参考

这是我们为 Typesense 自己的 Vuepress 文档站点使用的 docsearch-scraper 配置 (opens new window)

# 选项 C:使用 DocSearch.js v3(模态布局)的自定义文档框架

将以下 DocSearch.JS 代码片段添加到所有文档页面中:

<!-- 在文档站点的导航栏某处 -->
<div id="searchbar"></div>

<!-- 在 head 标签结束前 -->
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/typesense-docsearch-css@0.3.0"
/>

<!-- 在 body 标签结束前 -->
<script src="https://cdn.jsdelivr.net/npm/typesense-docsearch.js@3.4"></script>

<script>
  docsearch({
    container: '#searchbar',
    typesenseCollectionName: 'docs', // 应与 docsearch scraper config.js 中指定的集合名称匹配
    typesenseServerConfig: { 
      nodes: [{
        host: 'localhost', // 对于 Typesense Cloud 使用 xxx.a1.typesense.net
        port: '8108',      // 对于 Typesense Cloud 使用 443
        protocol: 'http'   // 对于 Typesense Cloud 使用 https
      }],
      apiKey: '<SEARCH_API_KEY>', // 使用仅具有搜索权限的 API 密钥
    },
    typesenseSearchParameters: { // 可选参数
      // filter_by: 'version_tag:=0.21.0' // 当你有版本化文档时很有用
    },
  });
</script>

# 参考:

# 选项 D:使用 DocSearch.js v2 的自定义文档框架(下拉布局)

将以下 DocSearch.JS 代码片段添加到所有文档页面:

<!-- 在文档站点的导航栏某处 -->
<input type="search" id="searchbar">

<!-- 在 head 标签结束前 -->
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/typesense-docsearch.js@1/dist/cdn/docsearch.min.css"
/>

<!-- 在 body 标签结束前 -->
<script src="https://cdn.jsdelivr.net/npm/typesense-docsearch.js@1/dist/cdn/docsearch.min.js"></script>

<script>
  docsearch({
    inputSelector: '#searchbar',
    typesenseCollectionName: 'docs', // 应与 docsearch scraper 配置文件 config.js 中指定的集合名称一致
    typesenseServerConfig: { 
      nodes: [{
        host: 'localhost', // 对于 Typesense Cloud 使用 xxx.a1.typesense.net
        port: '8108',      // 对于 Typesense Cloud 使用 443
        protocol: 'http'   // 对于 Typesense Cloud 使用 https
      }],
      apiKey: '<SEARCH_API_KEY>', // 使用仅具备搜索权限的 API 密钥
    },
    typesenseSearchParams: { // 可选参数
      // filter_by: 'version_tag:=0.21.0' // 当文档有版本控制时非常有用
    },
  });
</script>

# 参考:

# 样式定制

您可以根据需要覆盖以下样式:

.algolia-autocomplete .ds-dropdown-menu {
  width: 500px;
}

.algolia-autocomplete .typesense-docsearch-suggestion--category-header {
  color: darkgray;
  border: 1px solid gray;
}

.algolia-autocomplete .typesense-docsearch-suggestion--subcategory-column {
  color: gray;
}

.algolia-autocomplete .typesense-docsearch-suggestion--title {
  font-weight: bold;
  color: black;
}

.algolia-autocomplete .typesense-docsearch-suggestion--text {
  font-size: 0.8rem;
  color: gray;
}

.algolia-autocomplete .typesense-docsearch-suggestion--highlight {
  color: blue;
}

请注意,由于我们使用的是未经修改的 autocomplete.js (opens new window),所以仍需使用 .algolia-autocomplete 类名。但对于 docsearch 相关类名,使用的是 .typesense-docsearch-*,因为这是修改版的 DocSearch.js。

CSS 调试技巧

要在不关闭搜索栏的情况下检查调试 CSS(点击开发者工具面板时不会关闭搜索栏),可以在初始化 docsearch 库时使用 debug: true 选项!

# 选项 E: Sphinx 文档生成器

这里有一份由 Typesense 用户编写的指南 (opens new window),介绍如何将 Sphinx (opens new window) 与 Typesense DocSearch 集成。

# 语义搜索 New

从 Typesense Server v0.25.1 和 typesense-docsearch-scraper v0.9.1 开始,Typesense 内置支持语义搜索

语义搜索利用机器学习模型,即使用户搜索的关键词在您的文档站点中不存在,也能提供概念上相关的结果。

例如,如果用户搜索"hard disk"而您的文档中包含"hard drive",语义搜索仍然能够检索到这些结果。

步骤1: 要启用语义搜索(Semantic Search),首先更新您的爬虫配置文件,包含以下高亮部分:





 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


{
  "index_name": "your_docs",
  "start_urls": ["..."],
  "selectors": {},
  "custom_settings": {
    "field_definitions": [
      {"name": "anchor", "type": "string", "optional": true},
      {"name": "content", "type": "string", "optional": true},
      {"name": "url", "type": "string", "facet": true},
      {"name": "url_without_anchor", "type": "string", "facet": true, "optional": true},
      {"name": "version", "type": "string[]", "facet": true, "optional": true},
      {"name": "hierarchy.lvl0", "type": "string", "facet": true, "optional": true},
      {"name": "hierarchy.lvl1", "type": "string", "facet": true, "optional": true},
      {"name": "hierarchy.lvl2", "type": "string", "facet": true, "optional": true},
      {"name": "hierarchy.lvl3", "type": "string", "facet": true, "optional": true},
      {"name": "hierarchy.lvl4", "type": "string", "facet": true, "optional": true},
      {"name": "hierarchy.lvl5", "type": "string", "facet": true, "optional": true},
      {"name": "hierarchy.lvl6", "type": "string", "facet": true, "optional": true},
      {"name": "type", "type": "string", "facet": true, "optional": true},
      {"name": ".*_tag", "type": "string", "facet": true, "optional": true},
      {"name": "language", "type": "string", "facet": true, "optional": true},
      {"name": "tags", "type": "string[]", "facet": true, "optional": true},
      {"name": "item_priority", "type": "int64"},
      {
        "name": "embedding",
        "type": "float[]",
        "embed": {
          "from": [
            "content",
            "hierarchy.lvl0",
            "hierarchy.lvl1",
            "hierarchy.lvl2",
            "hierarchy.lvl3",
            "hierarchy.lvl4",
            "hierarchy.lvl5",
            "hierarchy.lvl6",
            "tags"
          ],
          "model_config": {
            "model_name": "ts/all-MiniLM-L12-v2"
          }
        }
      }
    ]
  }
}

这会指示Typesense自动生成一个 embedding字段, 使用contenthierarchy.*tags字段的内容。

如果您有自定义标签,可以编辑上面的schema,将这些自定义字段包含在embed.from中。

步骤 2: 现在,更新您前端中的 DocSearch 初始化代码,设置以下自定义 query_by 字段以包含 embedding 字段:

docsearch({
    //... 其他参数如上所述
    typesenseSearchParameters: { // 在某些 docsearch 插件中(见上文),该参数可能名为 `typesenseSearchParams`
      // ... 
      query_by:
        'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,hierarchy.lvl6,content,embedding',
      vector_query: 'embedding:([], k: 5, distance_threshold: 1.0, alpha: 0.2)' // 可选的向量搜索微调参数
    },
  });

这样就完成了!

您现在已启用支持语义搜索的 DocSearch。

提示:机器学习模型选项

上述示例使用了 Typesense 内置的 ML 模型,但您也可以使用 OpenAI、PaLM API 或任何其他内置 ML 模型,具体方法参见此处

注意:CPU 使用情况

内置的机器学习模型计算密集度高。

因此根据文档站点的大小,当您启用语义搜索并使用内置 ML 模型时,即使是几千条记录也可能需要花费数十分钟来生成嵌入向量并建立索引。

如需加速此过程,您可以在 Typesense 中启用GPU 加速

当您在 Typesense 中使用 OpenAI 等远程嵌入服务时,则不需要 GPU,因为模型运行在 OpenAI 的服务器上。