网站内容本地搜索的JavaScript实现
前几年搭建上一个基于Gatsby.js的博客的时候,曾经研究过怎么样在网站上面加入全文内容搜索功能,那个时候看到的方案都不太完美。主要难点不在于没有合适的浏览器用的搜索库,而是Gatsby的产物是静态生成的,要支持搜索,就是在页面里面加载整个网站的文本内容,整体实现会比较丑陋。而且还会遇到技术上其它的一些挑战(参见后文)。既然新的日志改成了next.js的服务端渲染,那么这部分代码完全可以移动到服务端执行了。因此趁这个机会研究了一下可以在2024年使用的技术方案。
框架选择
如果我的博客是全英文的,那么选项会多很多,例如fuse.js
这样的轻量化的都能满足,但是既然选择写中文,那么就要考虑框架支持CJK搜索了——大部分情况下,框架的默认配置都是没有CJK的。具体来说,就是至少需要支持自定义分词器和停用词。大约看了一下,比较符合条件的是:
- minisearch
- lunr
- elasticlunr
- flexsearch
分词器
在前几年的话,中文分词的流行方案是结巴分词以及其衍生物。这个工具的缺点是有一个比较大的字典。万幸现在是2024年,我们JS终于也有了原生的分词:Intl.Segementer
,并且浏览器与Node.js环境都可用。测试下来整体效果还可以,虽然分得不算完美,但是毕竟不占生成物体积——虽然部署服务器端渲染也不用太考虑这方面得因素。
接下来,这个API的一个弱点在于,只能生成针对一种语言的分词器,虽然可能情况不会太多,但是说不准以后我也会在文章里面使用其它语言比如日语和法语,因此需要考虑如何检测语言。在浏览器环境,有i18n.detectLanguage
可用,它是基于Compact Language Detector(CLD)库的,但是Node.js侧目前还没有这个API,好在有好心人开发了一个Node.js二进制版本cld3-asm
。这个工具的测试结果不太理想,在文本很短的时候比较容易误判,比如“Hello world!”会被检测为ky吉尔吉斯语。这样就意味着用来给搜索关键字做分词的话效果会差一点。但是还好我只会写四种语言,不认识的语言直接用英语来分词就可以。甚至,说不定把几种语言分出来的结果去重,效果可能也差不多。
停用词
nodejs生态圈里面有人在维护中文stop words,看起来可以直接用。
简繁转化
虽然我没有计划写繁体中文,但是说不定哪一天心情会改变,所以文本可能需要先经手一遍opencc统一处理,或者使用框架自带的等效词的功能。OpenCC也是有人移植到了JavaScript,看起来可以直接用。
其它选择
大体上看,技术方案是挺完整了,但是搜索毕竟是一门大生意,靠个人在一个简单的框架上手搓还是敌不过正规的方案的。Google搜索,或者algolia之类的付费方案都是有其价值的。如果不仅限于JavaScript,也有Pagefind这样的工具可以用,虽然我不太确定它的效果,毕竟只有索引是Rust的代码,搜索部分的代码依旧还是依赖JavaScript。