如何获取并填充 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 网站需要两个关键阶段:
- 在 Algolia 平台上创建索引:你将在此处获取必要的凭证(
App ID
,API Key
,Index Name
)。 - 用你的内容填充索引:你需要一种机制将你网站的内容(即你的
.md
文件)送入 Algolia 索引。
本指南将引导你完成这两个阶段,并推荐文档网站的最佳实践。
步骤 1:在 Algolia 网站创建索引并获取凭证
此初始设置在 Algolia 平台上完成。
- 注册 Algolia 账户:访问 Algolia 官网 并注册一个免费账户。
- 创建应用程序:登录后,你将被引导创建一个新应用程序。每个应用程序都有一个唯一的
App ID
。你可以在仪表盘的"API Keys"部分找到它。 - 创建索引:在你的应用程序中,导航到"Search" -> "Indices"菜单,然后点击"Create Index"。为你的索引命名,例如
python-tutorial
。这个名称就是你的Index Name
。 - 获取 API 密钥:在"API Keys"页面,你会看到多个密钥。对于你的 VitePress 配置,你需要:
- Search-Only API Key:此密钥是公开的,可以安全地放在你的前端代码中。将其用于
.vitepress/config.ts
文件中的apiKey
字段。 - Admin API Key:此密钥是机密的,绝不能暴露在前端! 你需要妥善保管此密钥,因为它用于填充索引。
- Search-Only API Key:此密钥是公开的,可以安全地放在你的前端代码中。将其用于
完成此步骤后,你将拥有替换配置文件中占位符所需的所有三个凭证。但是,此时你的索引仍然是空的。
步骤 2:选择一种方式为你的索引填充内容
这是最关键的一步。你需要选择一种方法来读取你网站的所有内容,并将其作为记录上传到 Algolia。你有以下三个主要选择:
方案一 (推荐):使用 DocSearch 服务
DocSearch 是 Algolia 提供的一项免费服务,专为开源项目和技术文档而设。它运行一个爬虫,自动并定期地抓取你的网站,从所有页面中提取内容(如标题和段落),然后自动将这些内容结构化并同步到你的 Algolia 索引中。
如何申请和使用 DocSearch
- 检查资格并申请:你的项目是一个关于技术对比的公开文档网站,完全符合 DocSearch 的申请条件。请访问 DocSearch 申请页面 并提交你的网站信息。
- 等待批准:DocSearch 团队将审核你的申请。
- 接收你的配置:一旦获得批准,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
文件,内容如下:
# 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
中:
// ... 在 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 客户端添加到你的项目开发依赖中。
npm install algoliasearch --save-dev
2. 创建索引脚本
在你的项目根目录下创建一个脚本文件,例如 scripts/algolia-indexer.js
。
3. 编写脚本逻辑
以下是一个完整的脚本示例,它会读取你所有的文档,并按一级标题 (#
) 和二级标题 (##
) 将它们分割成小记录,然后上传到 Algolia。
// 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
来跨平台地设置环境变量。
npm install cross-env --save-dev
然后更新 scripts
部分:
// 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)环境中设置它们。
总结你的后续步骤
- 在 Algolia 网站上注册,熟悉创建索引和查看 API 密钥的流程。
- 评估你的需求,在以下三个方案中做出选择:
- DocSearch (推荐):如果你的网站是公开的开源或技术文档,请立即前往 DocSearch 申请页面 提交申请。
- Algolia Crawler:如果你的网站是私有的,或需要高度自定义的爬取,请在你的 Algolia 仪表盘中配置一个付费的 Crawler。
- 自定义脚本:如果你需要完全的、代码级别的控制,并且不介意自行维护,请参考上文编写并部署你自己的索引脚本。
- 获取到凭证后(无论是来自 DocSearch 还是你自己创建的),替换掉
.vitepress/config.ts
文件中的占位符。
通过这些步骤,你的网站将拥有一个强大的搜索引擎,其内容会自动保持最新。
进阶功能:查询建议 (Query Suggestions)
在你成功配置好基础搜索后,可以考虑启用 Algolia 一个非常强大的进阶功能:查询建议 (Query Suggestions)。
它是什么?
简单来说,查询建议就是当用户在搜索框中开始输入时,系统会根据已有的数据智能地预测用户可能想要搜索的完整查询,并以下拉列表的形式展示出来。这与你在 Google 或 Amazon 上看到的搜索自动补全功能非常相似。
它是如何工作的?
Algolia 的查询建议主要通过两种方式生成高质量的建议:
基于分析数据 (Analytics-based): 这是最主要也是最有效的方式。Algolia 会分析你的用户真实产生的热门搜索数据。如果大量用户都搜索了"python list append",那么当一个新用户输入"python li"时,系统就会优先建议"python list append"。这确保了建议是基于真实用户行为的,相关性极高。
基于索引内容 (Index-based): 在你的网站刚上线,还没有足够的用户搜索数据时,这种方式非常有用。Algolia 可以分析你现有索引的内容(例如,你的文档标题、标签等),并从中生成建议。例如,它可以将你的文档标题"Python vs. JavaScript: Functions Comparison"直接作为一条建议。
如何启用?
启用查询建议通常需要在 Algolia 的仪表盘中进行额外配置:
- 导航到你 Algolia 应用的 Query Suggestions 页面。
- 创建一个新的 Query Suggestions 索引。这个索引是独立于你的主内容索引的,专门用来存放生成的建议词条。
- 将这个新的建议索引连接到你的主内容索引上作为其数据源 (Source Index)。
- 配置数据源的类型(是基于分析数据,还是基于索引内容)。
配置完成后,Algolia 会自动生成并更新你的建议索引。前端的搜索框组件(如 DocSearch)通常能够自动检测并使用这个建议索引,无需额外的前端代码改动。
它能带来什么好处?
- 提升搜索效率:用户只需输入几个字符就能快速选择完整的查询,节省时间。
- 减少拼写错误:通过选择建议,可以有效避免用户因拼写错误而无法找到结果。
- 引导用户发现:可以将热门或重要的查询展示给用户,引导他们发现网站上的关键内容。
疑难解答:为什么有记录却搜不到结果?
这是一个非常常见的问题。如果你在 Algolia 仪表盘的 Browse
标签页中能看到你的记录,但在网站上搜索不到任何结果,问题几乎总是出在 Algolia 索引的配置上。
请按照以下步骤进行排查:
1. 检查你的 recordExtractor
配置
首先,请确保你的爬虫配置中使用了正确的提取器。对于文档网站,helpers.docsearch()
是最佳选择,而不是 helpers.page()
。
为什么这很重要?
helpers.page()
将整个页面视为一个单独的记录。helpers.docsearch()
会智能地将你的页面按标题(H1, H2, ...)切分成多个记录,并创建一个名为hierarchy
的层级化对象。VitePress 的搜索界面正是依赖这个hierarchy
对象来展示结果的。
检查你的爬虫配置,确保 recordExtractor
的设置与我们在前面指南中推荐的类似:
// ...
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 在搜索时会自动根据当前语言(如 en
或 zh-CN
)添加一个过滤条件。如果 Algolia 不知道 lang
属性可以用于过滤,那么所有搜索都将返回零结果。
- 在同一 Configuration 页面,找到并点击 Faceting。
- 在 "Attributes for faceting" 输入框中,点击 Add an attribute。
- 输入
lang
并保存。
4. 验证索引数据
最后,确认你的爬虫确实成功提取了所需的数据。
- 在 Algolia 仪表盘中,进入 Browse 标签页。
- 随机选择一条记录进行查看。
- 确认记录中存在
hierarchy
对象,并且其中包含lvl0
,lvl1
等键。 - 确认记录中存在
lang
属性,并且其值是正确的(例如en
或zh-CN
)。
完成以上配置并保存后,等待几分钟让设置生效,然后回到你的网站上再次尝试搜索。99% 的情况下,问题都会得到解决。