给博客做个站内全文搜索:纯前端离线倒排索引的设计与取舍

给博客做个站内全文搜索:纯前端离线倒排索引的设计与取舍

💡 原文中文,约5200字,阅读约需13分钟。
📝

内容提要

作者为Hugo博客开发了纯前端离线全文搜索方案:构建时用Python和jieba生成倒排索引,拆分为核心与正文两层并按需异步加载;倒排表采用Base-32差值编码压缩,并处理技术词汇、繁简转换与拼写容错;索引使用固定URL加条件请求,避免全站页面缓存失效。该方案无需服务端,部署即可搜索,适合中小型静态博客。

🔎

延伸解读

纯前端搜索的适用边界

文章明确提到,这套方案适合文章不到两千篇的中小型静态博客。索引总体积约1.4 MB,Brotli压缩后约573 KB,加载时间在MacBook Pro上从38 ms优化到13 ms。但如果数据量再大几个数量级,或者需要按权限过滤结果,作者建议还是应该使用Typesense或Elasticsearch。这说明纯前端方案在数据规模和功能需求上有明确上限。

索引压缩的工程权衡

作者将倒排表从JSON数组改为Base-32差值编码字符串,主要收获不是压缩体积,而是加载时间从38 ms降到13 ms。因为Brotli本身擅长压缩重复数字,压缩后体积仅从527 KB降到432 KB。同时,search.js不再为每个词条预先建立数组,而是查询命中时才解码。这提示我们,在浏览器端处理大规模索引时,解析开销往往比传输体积更值得关注。

固定URL与缓存策略的取舍

使用fingerprint哈希文件名会导致每次索引变化时全站HTML中的URL都改变,进而触发全站重新压缩和缓存失效。作者改为固定URL,并配置Cache-Control: no-cache,让浏览器通过ETag或If-Modified-Since进行条件请求。这样只有索引文件本身需要重新验证,其他页面缓存不受影响。代价是每次打开搜索框会多两个小请求,但作者认为可以接受。

中文搜索的额外处理

针对技术博客,作者用jieba分词,并对C++、C#、.NET等带符号词汇特殊处理,代码块中的标识符在_和-处断开。繁简统一通过OpenCC字符表在分词前折叠,查询时同样处理,保证繁简都能搜到。拼写容错方面,4个字符以上的英文单词会查找编辑距离为1的词条,中文则支持子串查找。这些细节提升了搜索的实用性,但也增加了索引构建的复杂度。

❓

Q&A

为什么这个博客不直接用Google站内搜索?

主要因为隐私和外部依赖问题,引入第三方服务意味着读者要向第三方提供数据,有些读者不愿意或无法访问;另外搜索引擎索引有滞后,文章发布后一段时间内搜不到,而构建时生成的索引上线就能搜到。

为什么不用Typesense或Meilisearch这类服务端搜索?

它们需要在服务器上常驻进程,而本站是FreeBSD上的nginx直接提供静态文件,除了评论系统外没有动态后端,为了搜索多跑一个服务不划算;而且文章不到两千篇,数据量小,把索引发给浏览器本地匹配完全可行。

这个纯前端搜索的索引是怎么生成和加载的?

构建前用Python脚本和jieba分词,对每篇文章的标题、标签、分类和正文建立倒排表,输出JSON。索引拆成核心索引和正文索引两层:打开搜索框先加载核心索引(约400 KB,Brotli后约112 KB),之后用requestIdleCallback在浏览器空闲时异步加载正文索引(约992 KB,Brotli后约461 KB),加载完合并并重跑当前查询。

倒排表为什么要用Base-32差值编码压缩?

原始JSON倒排表在词条多时会产生大量小数组,且文档ID用十进制数字占空间大。改成两个长字符串(terms和postings),postings内部用差值编码,每个字符表示6位(低5位数值,第6位表示是否继续),差值不超过31时一个字符就能表示一篇文档。实测正文索引从1.8 MB降到0.9 MB,加载时间从38 ms降到13 ms。

搜索如何处理技术词汇、繁简中文和拼写错误?

带符号的技术词汇如C++、C#、.NET作为完整词条保留;代码块内容也会索引,但标识符在_和-处断开。繁简统一用OpenCC字符表在分词前把繁体折叠成简体,查询时同样折叠。拼写容错方面,4个字符以上的英文单词若不在索引中也不是某词条开头,会找相差一次编辑的词条并以较低权重计入;中文则支持词条内部子串查找。

为什么索引文件从带哈希的文件名改回固定URL?

带内容哈希的文件名会导致每次改文章索引哈希变化,全站近两千个HTML文件中的搜索框URL都变,从而全站HTML重新压缩和传输,缓存全部失效。改回固定URL后,只有变化的文章、列表页、feed和两个索引文件需要更新,索引缓存交给HTTP条件请求(Cache-Control: no-cache配合ETag/If-None-Match)处理。

🏷️

标签

➡️

继续阅读