Skip to content

如何获取并填充 Algolia 索引

在深入步骤之前,我们先澄清一个关键概念,这能帮助你更好地理解所有选项。


核心概念澄清:Algolia vs. DocSearch

  • Algolia:是一个强大的搜索引擎服务。它提供底层的搜索 API、索引管理和数据分析工具。你可以把它想象成一个性能极高的、专门用于搜索的"数据库"。
  • DocSearch:是 Algolia 公司专门为开源和技术文档提供的一项免费内容填充服务。它本质上是一个免费的、预先配置好的 Algolia Crawler,负责自动读取你的网站内容,并将其放入你的 Algolia 索引中。

结论:你在 VitePress 中使用的始终是 Algolia 的搜索功能。而 DocSearch通用版 Crawler你自己的脚本,都只是把你的网站内容"喂"给 Algolia 的不同方式。

前端 UI (docsearch.js) vs. 后端服务 (DocSearch Service)

你可能还会注意到,从浏览器发出的搜索请求头中包含了 docsearch 的字样。这是否意味着 VitePress 只能使用 DocSearch 服务呢?

答案是否定的。这揭示了一个更深层次的区别:

  • 前端界面:VitePress 的搜索框 UI 是基于 Algolia 开源的 docsearch.js 库构建的。这是一个预先封装好的、用户体验极佳的前端组件。
  • 后端服务:而我们之前讨论的 DocSearch 服务、通用版 Crawler 或自定义脚本,都属于后端数据填充的范畴。

VitePress 的做法是,采用成熟的 docsearch.js 作为前端界面,同时允许你灵活选择任何一种后端方式来为这个界面提供数据。只要你的数据结构是 docsearch.js 期望的格式(即包含 hierarchy 对象的记录),无论数据源是哪个,前端界面都能正常工作。这也是为什么我们在爬虫和脚本中都强调使用 helpers.docsearch() 来生成记录的原因。


将 Algolia 搜索集成到你的 VitePress 网站需要两个关键阶段:

  1. 在 Algolia 平台上创建索引:你将在此处获取必要的凭证(App ID, API Key, Index Name)。
  2. 用你的内容填充索引:你需要一种机制将你网站的内容(即你的 .md 文件)送入 Algolia 索引。

本指南将引导你完成这两个阶段,并推荐文档网站的最佳实践。


步骤 1:在 Algolia 网站创建索引并获取凭证

此初始设置在 Algolia 平台上完成。

  1. 注册 Algolia 账户:访问 Algolia 官网 并注册一个免费账户。
  2. 创建应用程序:登录后,你将被引导创建一个新应用程序。每个应用程序都有一个唯一的 App ID。你可以在仪表盘的"API Keys"部分找到它。
  3. 创建索引:在你的应用程序中,导航到"Search" -> "Indices"菜单,然后点击"Create Index"。为你的索引命名,例如 python-tutorial。这个名称就是你的 Index Name
  4. 获取 API 密钥:在"API Keys"页面,你会看到多个密钥。对于你的 VitePress 配置,你需要:
    • Search-Only API Key:此密钥是公开的,可以安全地放在你的前端代码中。将其用于 .vitepress/config.ts 文件中的 apiKey 字段。
    • Admin API Key此密钥是机密的,绝不能暴露在前端! 你需要妥善保管此密钥,因为它用于填充索引。

完成此步骤后,你将拥有替换配置文件中占位符所需的所有三个凭证。但是,此时你的索引仍然是空的。


步骤 2:选择一种方式为你的索引填充内容

这是最关键的一步。你需要选择一种方法来读取你网站的所有内容,并将其作为记录上传到 Algolia。你有以下三个主要选择:

方案一 (推荐):使用 DocSearch 服务

DocSearch 是 Algolia 提供的一项免费服务,专为开源项目和技术文档而设。它运行一个爬虫,自动并定期地抓取你的网站,从所有页面中提取内容(如标题和段落),然后自动将这些内容结构化并同步到你的 Algolia 索引中。

如何申请和使用 DocSearch

  1. 检查资格并申请:你的项目是一个关于技术对比的公开文档网站,完全符合 DocSearch 的申请条件。请访问 DocSearch 申请页面 并提交你的网站信息。
  2. 等待批准:DocSearch 团队将审核你的申请。
  3. 接收你的配置:一旦获得批准,DocSearch 团队将为你配置好一切,并提供一套专用的 appId, apiKey, 和 indexName你应该使用 DocSearch 提供的凭证,而不是你自己创建的凭证,因为他们的爬虫会自动将内容更新到这个指定的索引中。

为什么 DocSearch 是最佳选择?

  • 自动化:你无需编写任何代码来提取和上传内容。DocSearch 会自动处理。
  • 免费:对于符合条件的开源项目完全免费。
  • 优化:DocSearch 爬虫经过专门优化,能够很好地理解文档网站的结构(如标题层次结构),从而创建出高质量的搜索索引。

方案二:使用通用的 Algolia Crawler

你提到的 Algolia Crawler 是一个非常关键的概念。实际上,DocSearch 就是构建在 Algolia Crawler 之上的一个免费、为开源文档优化的版本。如果你需要比 DocSearch 更强大的功能,或者你的网站不符合 DocSearch 的免费条件,那么使用通用的 Algolia Crawler 就是你的最佳选择。

何时选择通用版 Crawler?

  • 私有或商业网站:当你的文档网站不在公网上,或者是一个商业产品的一部分时。
  • 需要高度自定义:你需要完全控制爬取规则、数据提取逻辑和索引计划(例如,每小时更新一次)。
  • 爬取受保护内容:你的网站需要登录才能访问。

与 DocSearch 的主要区别

  • 灵活性与控制:通用版 Crawler 提供了一个强大的可视化界面,让你能够精确定义要抓取哪些内容、如何构造记录、如何处理元数据等。
  • 成本:与免费的 DocSearch 不同,通用版 Crawler 是一个付费产品,其价格取决于你的使用量。
  • 设置:你需要自己登录 Algolia 仪表盘,从头开始配置一个新的 Crawler,而不是通过申请表来完成。

总的来说,通用版 Crawler 将 DocSearch 的"自动挡"体验升级为了"手动挡",提供了更强的动力和操控性,但需要付出相应的成本和配置精力。

如何使用通用版 Crawler

配置通用版 Algolia Crawler 是一个迭代的过程:你将配置爬虫,运行爬取检查结果,然后根据需要返回调整配置,直到满意为止。

以下是这个流程中的核心步骤。

前置步骤:验证网站所有权 (可选但推荐)

在开始配置爬虫之前,一个好习惯是向 Algolia 验证你对网站的所有权。这可以通过在你的网站根目录添加一个 robots.txt 文件来完成,其中包含 Algolia 提供的验证密钥。

根据 VitePress 的约定,你应该在 public 目录下创建 robots.txt 文件,内容如下:

txt:public/robots.txt
# Algolia-Crawler-Verif: E710EB89DAF291F9
User-agent: *
Allow: /

1. 在 Algolia 仪表盘创建 Crawler

  • 登录你的 Algolia 账户。
  • 在左侧菜单中,找到并点击 Search,然后选择 Crawler
  • 点击 Create Crawler 按钮,然后输入你的网站 URL。Algolia 会进行一个初步的测试抓取并生成基础配置。

2. 配置核心爬取规则

  • 创建后,进入 Crawler 的 Editor 视图,这里是所有配置的核心。
  • sitemaps: (推荐) 这是告诉爬虫从哪里开始的最佳方式。如果你的网站有站点地图(VitePress 默认会生成 sitemap.xml),应在此处列出。爬虫会智能地使用它来发现所有页面。
  • startUrls: 如果没有站点地图,你可以使用此项手动指定爬虫的入口 URL。
  • pathsToMatch: 定义哪些 URL 模式下的页面内容是需要被提取的。例如,https://your-site.com/guide/** 表示所有 guide 路径下的页面都应该被索引。
  • exclusionPatterns: 定义哪些 URL 模式应该被忽略,例如 https://your-site.com/archive/****/private/**

3. 配置数据提取 (actions) 这是最关键的部分。actions 是一个数组,允许你为不同类型的页面定义不同的提取规则。

以下是一个适用于类似 VitePress 文档网站的 recordExtractor 配置示例,它被包裹在一个 action 中:

javascript
// ... 在 Crawler 配置编辑器中 ...
"actions": [
  {
    // 此 action 应用于所有匹配 pathsToMatch 的页面
    "indexName": "python_tutorial_pages",
    "pathsToMatch": ["https://your-site.com/**"],
    "recordExtractor": ({ $, helpers }) => {
      // helpers.docsearch 是一个官方提供的辅助函数,极大地简化了文档网站的记录创建
      return helpers.docsearch({
        recordProps: {
          // lvl0, lvl1, ..., lvl6 对应于你的内容层级
          lvl0: {
            selectors: ".sidebar .active", // 假设当前活动的侧边栏项是最高层级
            defaultValue: "Documentation"
          },
          lvl1: ".content h1",
          lvl2: ".content h2",
          lvl3: ".content h3",
          lvl4: ".content h4",
          content: ".content p, .content li" // 定义哪些元素是正文内容
        },
        indexHeadings: true // 自动为标题建立索引层级
      });
    }
  }
]

配置要点:

  • 你需要根据你网站的实际 HTML 结构来调整 .content h1 这样的 CSS 选择器。
  • recordExtractor 使用类似 Cheerio (服务器端的 jQuery) 的语法来操作页面 DOM。

4. 配置安全检查 (重要) 为防止意外,配置安全检查是最佳实践。

  • maxLostRecordsPercentage: 如果本次爬取到的记录数比上次少了超过这个百分比(默认为 10%),爬虫将停止并发出警告,而不是直接用不完整的数据覆盖生产索引。

5. 运行与监控

  • 保存你的配置。
  • 在 Crawler 的 Overview 页面,点击 Restart crawling 来启动一次完整的爬取。
  • 你可以在 Monitoring 标签页下查看爬取的状态、已发现的 URL 数量、成功创建的记录数以及任何可能出现的错误。根据这些反馈,回到 Editor 视图继续优化你的配置。

完成以上步骤并成功运行后,你的 Algolia 索引就会被填充上来自你网站的结构化数据,可以被前端的搜索框使用了。

方案三:手动上传内容:自定义脚本方案

如果你无法使用 DocSearch(例如,网站是私有的)或者你需要对索引内容进行更精细的控制,你可以编写一个自定义脚本来手动推送数据。

这个流程的核心是在每次网站构建后,运行一个 Node.js 脚本来读取你的 Markdown 文件,将其转换为合适的 JSON 记录,然后使用 Algolia API 上传它们。

1. 安装 Algolia API 客户端

首先,将官方的 JavaScript 客户端添加到你的项目开发依赖中。

bash
npm install algoliasearch --save-dev

2. 创建索引脚本

在你的项目根目录下创建一个脚本文件,例如 scripts/algolia-indexer.js

3. 编写脚本逻辑

以下是一个完整的脚本示例,它会读取你所有的文档,并按一级标题 (#) 和二级标题 (##) 将它们分割成小记录,然后上传到 Algolia。

javascript
// scripts/algolia-indexer.js
import algoliasearch from 'algoliasearch';
import fs from 'fs/promises';
import path from 'path';
import matter from 'gray-matter';

// 递归地查找指定目录下的所有 Markdown 文件
async function getMarkdownFiles(dir) {
  let files = [];
  const items = await fs.readdir(dir, { withFileTypes: true });
  for (const item of items) {
    const fullPath = path.join(dir, item.name);
    if (item.isDirectory()) {
      files = files.concat(await getMarkdownFiles(fullPath));
    } else if (item.name.endsWith('.md')) {
      files.push(fullPath);
    }
  }
  return files;
}

// 将 Markdown 内容分割成可搜索的记录
function splitContentToRecords(content, frontmatter) {
    const records = [];
    const sections = content.split(/\n(?=##? )/); // 按 H1 或 H2 标题分割

    let currentH1 = frontmatter.title || '';
    
    sections.forEach(section => {
        const h1Match = section.match(/^# (.+)\n/);
        const h2Match = section.match(/^## (.+)\n/);

        let title = '';
        let content = section;

        if (h1Match) {
            currentH1 = h1Match[1];
            title = currentH1;
            content = section.substring(h1Match[0].length);
        } else if (h2Match) {
            title = `${currentH1} > ${h2Match[1]}`;
            content = section.substring(h2Match[0].length);
        }

        records.push({
            objectID: `${frontmatter.path}#${title.replace(/ /g, '-').toLowerCase()}`,
            title: title,
            content: content.replace(/<\/?[^>]+(>|$)/g, "").trim(), // 移除 HTML 标签
            path: frontmatter.path,
        });
    });
    return records;
}


async function main() {
  // 确保在环境变量中设置了你的密钥
  // 这非常重要,避免将 Admin API Key 提交到 Git
  if (!process.env.ALGOLIA_ADMIN_KEY) {
    throw new Error('ALGOLIA_ADMIN_KEY is not set');
  }

  // 初始化 Algolia 客户端
  const client = algoliasearch('YOUR_APP_ID', process.env.ALGOLIA_ADMIN_KEY);
  const index = client.initIndex('YOUR_INDEX_NAME');

  // 获取所有 Markdown 文件
  const files = await getMarkdownFiles(path.resolve(process.cwd(), 'guide'));
  const zhFiles = await getMarkdownFiles(path.resolve(process.cwd(), 'zh/guide'));
  const allFiles = [...files, ...zhFiles];

  let records = [];
  for (const file of allFiles) {
    const fileContent = await fs.readFile(file, 'utf-8');
    const { data: frontmatter, content } = matter(fileContent);
    const relativePath = path.relative(process.cwd(), file).replace(/\\/g, '/');
    
    // 添加 frontmatter 信息以便在记录中使用
    frontmatter.path = relativePath.replace(/\.md$/, '');

    const fileRecords = splitContentToRecords(content, frontmatter);
    records = records.concat(fileRecords);
  }

  // 清空现有索引并上传新记录
  try {
    await index.clearObjects();
    console.log('Index cleared.');
    await index.saveObjects(records);
    console.log(`Indexed ${records.length} records successfully.`);
  } catch (error) {
    console.error('Error uploading to Algolia:', error);
  }
}

main().catch(console.error);

4. 添加到构建流程

最后,修改你的 package.json 文件,在 build 命令执行完毕后运行这个索引脚本。你可能需要安装 cross-env 来跨平台地设置环境变量。

bash
npm install cross-env --save-dev

然后更新 scripts 部分:

json
// package.json
"scripts": {
  "dev": "vitepress dev .",
  "build": "vitepress build .",
  "preview": "vitepress preview .",
  "index": "cross-env ALGOLIA_ADMIN_KEY=你的私密AdminKey node ./scripts/algolia-indexer.js"
},

安全警告: 直接在 package.json 中写入密钥仍然是不安全的。最佳实践是使用 .env 文件(并将其加入 .gitignore)和 dotenv 包来加载环境变量,或者在你的持续集成(CI/CD)环境中设置它们。


总结你的后续步骤

  1. 在 Algolia 网站上注册,熟悉创建索引和查看 API 密钥的流程。
  2. 评估你的需求,在以下三个方案中做出选择:
    • DocSearch (推荐):如果你的网站是公开的开源或技术文档,请立即前往 DocSearch 申请页面 提交申请。
    • Algolia Crawler:如果你的网站是私有的,或需要高度自定义的爬取,请在你的 Algolia 仪表盘中配置一个付费的 Crawler。
    • 自定义脚本:如果你需要完全的、代码级别的控制,并且不介意自行维护,请参考上文编写并部署你自己的索引脚本。
  3. 获取到凭证后(无论是来自 DocSearch 还是你自己创建的),替换掉 .vitepress/config.ts 文件中的占位符。

通过这些步骤,你的网站将拥有一个强大的搜索引擎,其内容会自动保持最新。


进阶功能:查询建议 (Query Suggestions)

在你成功配置好基础搜索后,可以考虑启用 Algolia 一个非常强大的进阶功能:查询建议 (Query Suggestions)

它是什么?

简单来说,查询建议就是当用户在搜索框中开始输入时,系统会根据已有的数据智能地预测用户可能想要搜索的完整查询,并以下拉列表的形式展示出来。这与你在 Google 或 Amazon 上看到的搜索自动补全功能非常相似。

它是如何工作的?

Algolia 的查询建议主要通过两种方式生成高质量的建议:

  1. 基于分析数据 (Analytics-based): 这是最主要也是最有效的方式。Algolia 会分析你的用户真实产生的热门搜索数据。如果大量用户都搜索了"python list append",那么当一个新用户输入"python li"时,系统就会优先建议"python list append"。这确保了建议是基于真实用户行为的,相关性极高。

  2. 基于索引内容 (Index-based): 在你的网站刚上线,还没有足够的用户搜索数据时,这种方式非常有用。Algolia 可以分析你现有索引的内容(例如,你的文档标题、标签等),并从中生成建议。例如,它可以将你的文档标题"Python vs. JavaScript: Functions Comparison"直接作为一条建议。

如何启用?

启用查询建议通常需要在 Algolia 的仪表盘中进行额外配置:

  1. 导航到你 Algolia 应用的 Query Suggestions 页面。
  2. 创建一个新的 Query Suggestions 索引。这个索引是独立于你的主内容索引的,专门用来存放生成的建议词条。
  3. 将这个新的建议索引连接到你的主内容索引上作为其数据源 (Source Index)
  4. 配置数据源的类型(是基于分析数据,还是基于索引内容)。

配置完成后,Algolia 会自动生成并更新你的建议索引。前端的搜索框组件(如 DocSearch)通常能够自动检测并使用这个建议索引,无需额外的前端代码改动。

它能带来什么好处?

  • 提升搜索效率:用户只需输入几个字符就能快速选择完整的查询,节省时间。
  • 减少拼写错误:通过选择建议,可以有效避免用户因拼写错误而无法找到结果。
  • 引导用户发现:可以将热门或重要的查询展示给用户,引导他们发现网站上的关键内容。

疑难解答:为什么有记录却搜不到结果?

这是一个非常常见的问题。如果你在 Algolia 仪表盘的 Browse 标签页中能看到你的记录,但在网站上搜索不到任何结果,问题几乎总是出在 Algolia 索引的配置上。

请按照以下步骤进行排查:

1. 检查你的 recordExtractor 配置

首先,请确保你的爬虫配置中使用了正确的提取器。对于文档网站,helpers.docsearch() 是最佳选择,而不是 helpers.page()

为什么这很重要?

  • helpers.page() 将整个页面视为一个单独的记录。
  • helpers.docsearch() 会智能地将你的页面按标题(H1, H2, ...)切分成多个记录,并创建一个名为 hierarchy 的层级化对象。VitePress 的搜索界面正是依赖这个 hierarchy 对象来展示结果的。

检查你的爬虫配置,确保 recordExtractor 的设置与我们在前面指南中推荐的类似:

javascript
// ...
recordExtractor: ({ $, helpers }) => {
  return helpers.docsearch({
    recordProps: {
      lvl0: { selectors: ".sidebar .active", defaultValue: "Documentation" },
      lvl1: ".content h1",
      // ... 其他层级
      content: ".content p, .content li"
    },
    // ...
  });
},
//...

2. 配置可搜索属性 (Searchable Attributes)

这是最可能的原因。你必须明确告诉 Algolia 应该在哪些数据字段中进行搜索。

  • 登录 Algolia 仪表盘,进入你的索引。
  • 点击 Configuration 标签页。
  • 在侧边栏中选择 Searchable Attributes
  • 点击 Add a Searchable Attribute,然后逐一添加以下所有属性。这些属性对应了 helpers.docsearch 生成的 hierarchy 对象:
    • unordered(hierarchy.lvl0)
    • unordered(hierarchy.lvl1)
    • unordered(hierarchy.lvl2)
    • unordered(hierarchy.lvl3)
    • unordered(hierarchy.lvl4)
    • unordered(hierarchy.lvl5)
    • unordered(hierarchy.lvl6)
    • content

unordered() 表示 Algolia 会将这些属性中的词语视为无序的,这对于匹配标题中的关键词非常重要。

3. 配置用于过滤的属性 (Attributes for Faceting)

你的网站是多语言的,VitePress 在搜索时会自动根据当前语言(如 enzh-CN)添加一个过滤条件。如果 Algolia 不知道 lang 属性可以用于过滤,那么所有搜索都将返回零结果。

  • 在同一 Configuration 页面,找到并点击 Faceting
  • 在 "Attributes for faceting" 输入框中,点击 Add an attribute
  • 输入 lang 并保存。

4. 验证索引数据

最后,确认你的爬虫确实成功提取了所需的数据。

  • 在 Algolia 仪表盘中,进入 Browse 标签页。
  • 随机选择一条记录进行查看。
  • 确认记录中存在 hierarchy 对象,并且其中包含 lvl0, lvl1 等键。
  • 确认记录中存在 lang 属性,并且其值是正确的(例如 enzh-CN)。

完成以上配置并保存后,等待几分钟让设置生效,然后回到你的网站上再次尝试搜索。99% 的情况下,问题都会得到解决。

Last updated: