Node.js用FlexSearch给Hexo添加极速全站搜索

全站检索,全站内容搜索之自定义flexsearch实现【已上线,V0.0.2测试中】

引用拓展:大文本,多文本,多文件系统级文本字符低资源消耗高效急速搜索

2024年3月25日 优化docker-compose.yml文件实现:免去docker重启后需要登录docker执行命令的步骤

有什么用

许多网站或博主都苦于站内搜索的功能缺陷,使用特定搜索引擎的搜索就得要忍耐特定搜索引擎收录文章的速度(搜索内容受局限于特定的旧版本),自带的搜索消耗太多资源,速度慢,html的静态站点无法使用数据库的搜索…..

介绍flexsearch(这个可以轻易从加速浏览器中搜索获取最新版信息)不是本文的重点,Node.js的入门在我上一篇文章是重点:Node.js开发环境及应用实例

本文主要分享Node.js + FlexSearch 实际应用在本站(hexo)中全文搜索实现,用于我在Carl Notes 博客园中同步发布文章时“相关内容”章节的实现(iframe方法,由于博客园禁止了在文章中添加javascript代码,导致ajax跨域去cdn站点里面获取相关内容失败,这是一个巨坑,坑了我好2-3天)

使用FlexSearch对于内容的搜索是很有帮助是它的强项,Hexo对于相关内容的实现也不错:用插件中的算法去比对我目前300多篇文章彼此之间的联系,关键字,权重等等。详细见 Hello hexo中关于相关文章 related_posts 如何增加到10条?的章节介绍

但hexo(我目前的版本)在全站搜索的实现是潦草了,不知道最新版有没有改进‘全站检索‘

1
2
3
4
hexo: 6.3.0                                                                                                
hexo-cli: 4.3.1
# 我的hexo待更新啊。。。懒。。。新版吸引力不足。。。
node: 20.10.0

目前版本的hexo是通过ajax请求把search.xml,比如这样的 search.xml下载到内存中,然后通过js来在本地浏览器搜索和匹配(优点:UI界面很好看)可是,可是我的文件截止目前为止已经3M了!

可是这对于WEB太大了,我用最快的CDN线路,最快也要花费350ms+才能下载到本地,一般的cdn都要1s+这对于用户是很要命的。这是在方案级别上的缺陷。

怎么用

使用了目前(2023年12月中旬)是最新版本的FlexSearch

最简单的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//申明三种情况的应用,从简单到复杂Index,Document和Worker;先理解最简单的Index
const {Index, Document, Worker} = require("flexsearch");

//指定Index索引的参数,可以很复杂,先从默认最简单的开始
const index = new Index({
tokenize: "reverse",
depth: 2,
minlength: 3
});

//添加源内容,比如多个文件,数据库。。。
index.add(post.title, post.title + post.source + post.content)

//搜索,参数限定了11个结果
var searchRes = index.search(req.query.q, 11);

相关内容

实现方法/过程

基础学习建议实践走一下流程:https://expressjs.com/en/starter/installing.html
(在试一试列子https://expressjs.com/en/starter/examples.html)

Express, jade相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#主程序文件app.js,文件内容节选:
var appSearchRouter = require('./routes/appsearch');
#这里引用的新建的文件appserach.js

app.use('/appsearch', appSearchRouter); #这个注册appsearch可以按需修改名称

#其他的文件内容都按默认的列子https://expressjs.com/en/starter/examples.html
#生成的默认的文件列表即可。文档很清晰,友好。




#appsearch.js文件内容Express, jade相关:
var express = require('express');
var router = express.Router();


res.render('searchTemplate', { searchResults: arrSearchRes });
#对应下面的searchTemplate.jade文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#然后在views目录下面添加searchTemplate.jade文件,文件内容:
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
.popup.search-popup
.search-result-container
.search-stats 找到 #{searchResults.length} 个搜索结果
hr
ul.search-result-list
each result in searchResults
li
a(href=result.doc.url, class="search-result-title", data-pjax-state="")
mark.search-keyword= result.doc.title
a(data-pjax-state="" href=result.doc.url)
p.search-result= result.doc.subcontent

FlexSearch相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#appsearch.js文件内容中FlexSearch相关:
const {Index, Document, Worker} = require("flexsearch");


//const document
const index = new Document({
tokenize: "reverse",
minlength: 3,
document: {
id: "id",
index: [
// { 没必要用title在搜索一遍,即便效率极高
// field: "title",
// tokenize: "forward"
// },
{
field: "content",
context: {
// depth: 2,
// resolution: 3
},
tokenize: "reverse"
}],
store: ["title", "updated", "date", "source", "content"]
}
});

// load posts from a file 加载hexo的数据库文件
var pages = JSON.parse(require('fs').readFileSync(path.join(__dirname, '../../blog/db.json')).toString()).models.Post;

// add posts to the index
pages.forEach(post => {
var idx = 1;
// index.add(post.title, post.title + post.source + post.content)
index.add({
id: post.title,
title: post.title,
"title": post.title,
"updated": post.updated,
"date": post.date,
"source" : post.source,
"related_posts": post.related_posts,
content: post._content
})
idx ++;
});


var arrSearchRes = index.search(req.query.q, {
limit: 11,
enrich: true
});

注意避坑事项1

1
2
3
// create the index
// var FlexSearch = require("flexsearch");
// var index = FlexSearch.create();

这是flexsearch的旧版本语法,Google了一圈,国内的(中文内容)很多例子都是这个旧版本的,旧版本是无法运行的,各种报错,连ChatGpt3.5都不会解释,问题出现在哪里了。(这告诉我们版本是多么重要的)

注意避坑事项2

jade模版也是很好用的,缩紧语法。很简易地将HTML生成。制作过程中,也能很容易地使用转换工具,将现成的HTML转化为jade(或者Pug)的模版,比如:https://tool.fiaox.com/template-html-pug/

模版转化,模版转换Html to Pug

添加CORS跨站支持

1
2
3
4
5
res.setHeader("Access-Control-Allow-Origin","*");
res.setHeader(
"Access-Control-Allow-Methods",
"PUT, GET, POST, DELETE, HEAD, PATCH"
);

Docker中Production发布

Docker中发布是我首选项,高效简单易管理。

发布文件列表:

​ 复制所有文件夹除了node_modules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#进入nodejs容器,启动bash(用于执行node程序们)
> docker exec -it nodejs bash

#直接运行node程序:
> node /home/app/blogsearch/bin/www
#或者进入目录在运行
> cd home/app/blogsearch/
> node ./bin/www



#【首次安装node app】不需要重新 npm init, 但是需要添加express库
> npm install express

> node ./bin/www
#成功:
http://192.168.6.116:3001/appsearch?q=android

如何常驻在后台运行?


#NPM反代出来
https://query.carlzeng.top:3/appsearch?q=android
https://query.carlzeng.top:3/appsearch?q=iptv

#添加matomo至模版
## 在模版中添加JS即可

#[版本更新] docker中如何关闭某个node进程,重启,重新发布新版本
> ps -falx | head -1; ps -falx | grep 'npm\|node'
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
4 0 173 134 20 0 3324 1536 pipe_r S+ pts/2 0:00 \_ grep npm\|node
0 0 69 8 20 0 1145980 203676 do_epo Sl+ pts/1 7:32 \_ node /home/app/blogsearch/bin/www

#查到ppid为8,杀死进程;然后重启
> kill -9 8
> node /home/app/blogsearch/bin/www

2024年3月25日 优化docker-compose.yml文件实现:免去docker重启后需要登录docker执行命令的步骤。具体优化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3"
services:
node:
stdin_open: true
tty: true
container_name: nodejs
restart: always
volumes:
- './app:/home/app'
ports:
- 3001:3000
image: node
command: node /home/app/blogsearch/bin/www
#上面这一行command,可以理解为docker的entrypoint,docker启动后,会自动先执行这个shell,如果需要执行多条命令,就重复添加多个command即可

所以现在就简单了,不用那么复杂进docker去kill然后重新执行node;

直接docker restart nodejs

Sample for node.js refrence

见上方:相关内容

URL:https://query.carlzeng.top:3/appsearch?q=node.js

再比如: 最终替换掉hexo的全站检索功能(在配置文件中关闭 local_search: false),展示如下:

## CDN 发布

如上的地址不够优雅,用CDN回源地址加端口的方式可以让地址变优雅一点

BlogCDN中新建一个网站,然后把CNAME指向到‘新网站’自动获取的CNAME(类似:@@@@.cdn.blogcdn.net),源站设置中,配置好自定的端口;这样地址就能变成下面这样

比如:https://jp.carlzeng.top/appsearch?q=adsl

版本更新Release Notes

V0.0.3

  • 更新了模版,style.css updated
  • 增加暗黑模式,added darkmode
  • 把隐藏或加密的文章挪出搜索结果

V0.0.2

  • 更新了模版,添加[首页]及连接
  • 把内容缩略的部分,做更精准的首次match,展示首个包含关键字的特定内容范围

下一步

还需要写一个trigger,在每次hexo g 或者 hexo d的时候,将.db文件sync到服务器中,供flexsearch作为最新的搜索数据源;目前先手动方式 :-)

手动的步骤(2步即可):

  1. BT打开到目录:/www/server/panel/data/compose/nodejs/app/blogsearch/routes

  2. 重启docker nodejs

    或者使用其他详细方案 小结内容详细见:Node.js的Docker Deployment步骤

    1
    2
    3
    4
    5
    6
    7
    ssh 192.168.6.116
    docker exec -it nodejs bash
    ps -falx | head -1; ps -falx | grep 'npm|node'

    kill -9 「node进程id,PPID值」

    node /home/app/blogsearch/bin/www &

感想

Node.js 真的爱了,好简洁的框架,可以定制做很多实际需求,对我来说,很容易上手。据听说服务的效率很高占用资源很低,还高并发。我将持续期待并关注这个app的实际表现…

相关内容跨站接口

服务器后端nodejs FlexSearch实现

灵感来源

Adding full text Search via FlexSearch to a Blog