前言
去年开始,我开始维护持续集成和持续部署工具 Drone的中文文档站Drone.cool,有将网站内容集成到Algolia的诉求,但现网HugowithAlgolia的资料非常有限,经搜索,留存有数篇文章,按照内容操作,部分能梳理生成索引的JSON,但不支持Algolia命令行上传。于是把Hugo到Algolia的集成重新梳理了一遍。
KickOff! Algolia
Algolia 是一款快速、可靠和易于使用的搜索服务,旨在提高应用程序和网站的搜索体验,具有先进的搜索和过滤功能,可提供高质量的搜索结果和排名。此外,Algolia 提供了一系列工具和 API,使得搜索和查询数据变得更加容易和灵活。
注册 Algolia
官网地址:https://www.algolia.com,注册帐号后,通过https://www.algolia.com/account/plan/create?from=dashboard创建一个「Free Application」。
随后,选择存储的数据中心,可以根据实际地理位置就近进行选择,Algolia 也会根据网络情况推荐选用哪个数据中心,便于提供更快的搜索服务。
中国大陆的网站一般建议选择新加坡 (Singapore) 或香港 (Hong-Kong) 的数据中心。但目前这几个节点都需要联系官方开通,嫌麻烦的话随机选一个就好,实测网络也没有多差。
点击「Review」后进入确认界面,接受使用条款并创建即可。一般情况下小网站 Algolia 提供的免费额度是充足的,无需太过担心不够用的问题。
获取 Application ID 和 API Key
在 Dashboard 上点击应用,可以获得应用的 Application ID,点击「Search」导航栏后,进入搜索配置,创建一个索引。
点击左下角「Setting」菜单,在设置中找到「Team and Access」->「API Keys」,可以获取到对应应用的四个 Key,分别如下:
Application ID
:应用唯一标识符。它用于在使用 Algolia 的 API 时识别我们的网站。Search-Only API Key
:用于在应用或前端代码中使用搜索服务时调用的公共 API 密钥。只可用于搜索查询和向 Insights API 发送数据。Admin API Key
:用于创建、更新和删除索引数据。也可以用它来管理 API 密钥。Usage API Key
:用于访问 API 用量和日志 Endpoint。
API Keys
安装 Algolia CLI,配置本地 Profile
Algolia CLI 安装参考官网文档中GET STARTED,我使用的 macOS,操作如下:
brew install algolia/algolia-cli/algolia
在本地添加 Profile 配置并设置为默认
➜ algolia profile add
? Name: example-profile
? Application ID: CW7TR03BVW
? Admin API Key: a57581****************0a74b8
? Set as default profile? No
✓ Profile 'example-profile' (CW7TR03BVW) added successfully.
至此,可以使用 Algolia CLI 进行上传操作了。此外,在 Web 上,你还可以对索引 (Index)、查询建议 (Query Suggestions)、查询分类 (Query Categorization) 等功能进行配置,这有利于在后期搜索中充分展示文档,优化搜索结果,具体细节可以自行在 Web 上进行摸索。
Hugo集成 Algolia
在Hugo中集成 Algolia,有多种方式,现网的实践一般是用Hugo的模板引擎生成一个JSON,或使用第三方开发的小工具如hugo-algolia
进行集成。由于第三方工具维护问题,多数工具已经跑不起来了,所以还是选择了原厂支持——用 Hugo 的模板引擎生成一个JSON,然后用 Algolia CLI 上传。
使用模板引擎生成 algolia.json
在 Hugo 的配置文件中添加如下模板渲染输出配置(一般是config.toml
,我使用的 YAML 所以是config.yaml
)
outputs:
home:
- Algolia
outputFormats:
Algolia:
baseName: "algolia"
isPlainText: true
mediaType: "application/json"
notAlternative: true
在themes/default/layouts/_default/
下创建一个名为list.algolia.json
的文件,文件内容如下:
{{- $.Scratch.Add "index" slice -}}
{{- $section := $.Site.GetPage "section" .Section }}
{{- /* range .Site.AllPages */ -}}
{{- range where .Site.Pages "IsPage" true -}}
{{- if and (ne .Type "page") (eq .Kind "page") -}}
{{- $.Scratch.Add
"index" (
dict "objectID" .RelPermalink
"hierarchy" (
dict "lvl0" .Type
"lvl1" .Description
)
"type" "content"
"tags" .Params.Tags
"content" .Title
"url" .RelPermalink
"Weight" .Weight
)
}}
{{- end -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}
执行hugo
命令后会在public
目录下生成algolia.json
文件,内容为被压缩后的单行有效JSON。其数据结构可以根据网站自身需要进行自定义,通过后期 Index 配置实现多样化的数据结构和展示,在这里贴出的结构是一个比较适合多数网站的结构。
将索引上传至 Algolia
生成algolia.json
后,你可以将JSON文件内容通过 Algolia Web 通过文件或手动粘贴 JSON 上传,均可完成索引导入。但使用 Algolia-CLI 时,官方使用了ndjson
格式而非json
格式,这也是现网除了官方文档,没有一篇 Hugo 相关的文章提及到的坑。
使用algolia objects import
命令上传索引,以下是命令参数的释义:
➜ algolia objects import --help
Import objects to the specified index from a file / the standard input.
The file must contains one single JSON object per line (newline delimited JSON objects - ndjson format: https://ndjson.org/).
Usage:
algolia objects import <index> -F <file> [flags]
Flags:
--auto-generate-object-id-if-not-exist 如果不存在,自动生成对象ID
-b, --batch-size int 指定上传批量大小(默认为1000)。
-F, --file file 从文件中读取要导入的记录(使用"-"从标准输入读取)。
-h, --help 帮助
Inherited Flags
--admin-api-key string Admin API key,
--application-id string Application ID
-p, --profile string 本地配置好的 Profile
--search-hosts strings The list of search hosts as CSV
Examples
...
这里有几处要注意:
- 考虑 JSON 文件后期会越来越大,指定
-b
参数时可以设置大一点。 --application-id
、--admin-api-key
、--profile
可以不指定,会默认调用本地默认的Profile
,上文已设置过。
JSON?ndJSON?
在执行algolia objects import example-index -F public/algolia.json -b 9999999
后,会出现形如Importing records ⡿could not send intermediate batch: Algolia API error [400] body of batch should be an object and not an array near line:1 column:43的错误提示。
查阅 Algolia API 文档https://www.algolia.com/doc/api-reference/api-methods/save-objects/?client=CLI#examples,发现使用了ndjson
格式而非json
(笔者注:其实上文在 Object Import 时 shell 的帮助提及到过)。
ndJSON知识补漏
[ndJSON](https://normalcoder.com/blog/ndjson/)
(New-line Delimited JSON)是一个比较新的标准,本身结构简单,在一个.ndjson
后缀文件中,逐行存储一个传统 JSON 对象,使用换行符(0x0A)分隔。其 MIME 类型是application/x-ndjson
。
以下是一个JSON
数组,包含了若干个人的信息,每个人的信息都是一个对象。每个对象都有相同的属性,包括姓名、年龄、婚姻状况、爱好和住址。
[
{
"name": "John Smith",
"age": 30,
"isMarried": true,
"hobbies": ["reading", "swimming", "traveling"],
"address": {
"street": "123 Main St",
"city": "New York",
"state": "NY",
"zip": "10001"
}
},
{
"name": "Jane Doe",
"age": 25,
"isMarried": false,
"hobbies": ["hiking", "cooking", "painting"],
"address": {
"street": "456 Park Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001"
}
}
]
相同的数据,使用[ndJSON](https://normalcoder.com/blog/ndjson/)
进行存储,其数据表现为:
{"name": "John Smith", "age": 30, "isMarried": true, "hobbies": ["reading", "swimming", "traveling"], "address": {"street": "123 Main St", "city": "New York", "state": "NY", "zip": "10001"}}
{"name": "Jane Doe", "age": 25, "isMarried": false, "hobbies": ["hiking", "cooking", "painting"], "address": {"street": "456 Park Ave", "city": "Los Angeles", "state": "CA", "zip": "90001"}}
JSON 和 nsJSON 相对比,两者区别于数据存储方式不同。JSON
通常用于存储单个数据对象,而[ndJSON](https://normalcoder.com/blog/ndjson/)
则用于存储多个数据对象,每个对象都以单独的行表示。在处理大量数据时,nsJSON
的优势更为明显,因为它可以逐行读取数据,并且不需要将整个文件读入内存中。
将 JSON 转为 ndJSON
无奈,ndjson
现网的工具也是少之又少……,几经周折,找到一个 NPM 包json-to-ndjson
,可以将JSON
转为ndJSON
。于是:
npm install -g json-to-ndjson
转换格式
json-to-ndjson public/algolia.json -o public/algolia.ndjson
在此尝试导入索引对象:
➜ algolia objects import example-index -F public/algolia.ndjson -b 999999
✓ Successfully imported 819 objects to drone_cool in 1.827148884s
最后,将上述的过程整理一下,使用一行命令完成 Hugo 网站编译、索引生成、转换和上传,最后将public
目录丢到网站部署即可。
rm -fr public/ && hugo && json-to-ndjson public/algolia.json -o public/algolia.ndjson && algolia objects import drone_cool -F public/algolia.ndjson -b 9999999 && rm public/algolia.*
完事儿,收工!
后续
在倒腾集成的过程,想过直接使用模板引擎渲染的方式,直接输出 ndJSON 格式的文件。但 Hugo 的outputFormats
中mediaType
不支持 “application/x-ndjson”,无奈作罢。
生命的意义在于折腾不止,既然知道了 Hugo 不支持ndJSON
渲染,可以给 Hugo 提个 Issue Pull Request 了…