其实我很早就想把这里的搜索给重写一下。
- 旧方案:Lunr
- 初步了解Pagefind
- 我的使用步骤
- 搜索页面的api调用
- 一些问题和调整
- 限定搜索的标签范围
- pagefind_extended的中文支持
- Github Action和pagefind的整合
旧方案:Lunr
之前使用的是静态生成一个search_data.json,大概全站生成的格式是这样的:
{
"blog-post-107": {
"post_id" : "/blog/post/107",
"title": "算术题生成器",
"url": "/blog/post/107/",
"date": "2024-08-23 08:00:00",
"category": "技术",
"tags": ["javascript"]
}
,
"blog-post-106": {
"post_id" : "/blog/post/106",
"title": "从MP4里面提取音频",
"url": "/blog/post/106/",
"date": "2024-07-30 08:00:00",
"category": "技术",
"tags": ["python","ffmpeg"]
}
,
.............
我并没有把全文给放进去,所以搜索起来有很大的限制。
然后再利用Lunr来进行检索。其中我还用了lunr-language进行了一下分词。虽然不明白当时为什么用了jp的库而不是zh的,很久远的事情了,实在记不得了。
用下来搜索的效果大概是这样的
说白了就是只能用标题、标签和分类来搜索。
虽然说Lunr其实很强大,如果我把全文都塞进去不见得搜不出来,但是我实在不高兴折腾了。
备份下当时的代码
window.idx = lunr(function () {
this.use(lunr.jp);
this.field('post_id');
this.field('title', { boost: 10 });
this.field('category');
this.field('tags');
});
window.data = $.getJSON('/search_data.json');
window.data.then(function(loaded_data){
$.each(loaded_data, function(index, value){
window.idx.add(
$.extend({ "id": index }, value)
);
});
var results = window.idx.search(decodeURIComponent(params['string']));
});
之后我还搜索过一些其他方案,比如Algolia,bootstrapt网站就用的这个,看上去特别酷炫,但是要收钱……所有需要收钱的方案在我这里基本都被干掉了
初步了解Pagefind
最近又没事搜索了一下,搜到了这篇文章基于Pagefind实现静态博客站内搜索,非常感兴趣。
于是就上手试了一下,感觉非常不错。
首先Pagefind是先扫描站点生成index,然后提供默认的ui或者api进行检索。
对于我的基于Jekyll,部署在Github Page的白嫖式网站非常友好。
我的使用步骤
按照https://pagefind.app/docs/的步骤,比较简单,但是我之后微调之后有很多不一样的地方。
首先我在上文《基于Pagefind实现静态博客站内搜索》里面发现搜索框和结果框可以分开,但是现在我拿到的版本并不能分开(或者我没有找到分开设置的方法),这造成了default UI和我现有页面的嵌入配合成了灾难。虽然说我可以把result部分设成一个悬浮的div,但是调整了很久的CSS,感觉还是非常丑。我思考了一下还是用原来的比较古板/傻的search.html页面,用调用api的方式在特定页面显示搜索的结果。
生成索引
我自己并不是很熟悉npm和npx,所以直接从它Release的里面拉了个已经编译好的win版本来索引。
我的blog的本地目录大概是这样的,真正应该被检索的文章其实是在编译之后以全静态的格式放在_site下面的blog/post下面,其他都是一些照片/分类/标签/索引之类的杂七杂八的没有搜索价值的东西。
├─_data
├─_includes
├─_layouts
├─_posts
└─_site
├─blog
│ ├─index
│ └─post
├─photo
├─photos
├─search
├─tag
└─type
所以我的本地生成index的命令是这样的
pagefind.exe --site C:\codes\JiYouMCC.github.io\_site --glob blog/post/**/*.html
在生成index之后,会在_site下面生成一个pagefind文件夹,所有的索引/界面/js东西都在里面。放在_site里面并不会真正生效,所以我需要拷到根目录下(C:\codes\JiYouMCC.github.io\pagefind),让jekyll视为一堆静态库来引用。
搜索页面的api调用
大体上也是跟着文档走,稍微做了一些细节上的配置。
var pagefind = await import("/pagefind/pagefind.js");
await pagefind.options({
showSubResults: false,
excerptLength: 15
});
pagefind.init();
var search = await pagefind.search(searchString);
var results = await Promise.all(search.results.map(r => r.data()));
其实我还想加一些什么根据日期/相关性排序之类的东西,这个稍后再说。
一些问题和调整
然后我就发现了一些问题,相应做了一些调整。
限定搜索的标签范围
首先,我发现搜索结果的meta的图片总是用了我link里面欧拉项目的图片,这八竿子打不着啊。根据文档image will contain the src of the first img that follows the h1
,所以我需要把这个图片丢出搜索的范围。参考了一下Configuring what content is indexed,我把blog的right部分全用<aside data-pagefind-ignore="all">
扔了出去,以防止图片的干扰,用<main data-pagefind-body>
包裹了一下所有post的真正的文章部分,然后就生效了。这样也防止了我搜索“评论”结果把所有的文章都搜索出来的窘境。
然后,在UI上做了一些调整,还是用jquery写了一推的append,处理了一下图片的格式,大概是这样的效果
感觉好像大功告成了(并不是!)
pagefind_extended的中文支持
然后我就发现了一个很麻烦的问题,中文分词非常糟糕。
同样是上面那个搜索,明明有一句话说的是“根据像素点的灰色深度”blablablab,我就搜索“像素”,结果什么都搜不出来。
这个问题看上去已经非常致命了,我的感觉是,肯定有什么我不知道的配置还没搞定。
不过除此以外,搜索标题、标签、分类已经不是问题了,体验还比原来的好很多,所以先将就着用一下。
后来我在Installing and running Pagefind上面终于找到了蛛丝马迹。
./pagefind --site "public"
# or
./pagefind_extended --site "public"
Pagefind publishes two releases, pagefind and pagefind_extended. The extended release is a larger binary, but includes specialized support for indexing Chinese and Japanese pages.
The extended release is a larger binary, but includes specialized support for indexing Chinese and Japanese pages.
居然为中文和日文特地搞了个版本!解决了分词的问题!
相应的使用的现成可执行文件就是pagefind_extended-v1.1.0-x86_64-pc-windows-msvc.tar.gz,命令行更新为
pagefind_extended.exe --site C:\codes\JiYouMCC.github.io\_site --glob blog/post/**/*.html
Github Action和pagefind的整合
另一个小问题就是,每次jekyll有新的文章,都需要jekyll serve出_site,然后进行重新索引。从DevOps的角度来说,理论上可以在部署的时候加入这个index+拷贝文件夹的步骤,但是我有点懒,先不折腾了。什么时候我高兴了研究下Github Action,和jekyll的编译部署都整合起来,说不定就成了。
(第二天)我就研究下了Github Action然后也搞定了!
期间也有一些小波折,稍微记录一下!
之前部署Jekyll的时候用的是Github自己默认的。我自己本身没有太关注Github的更新,就是(工作中)突然发现有一天它出了Action的功能,又突然有一天发现Jekyll的部署就是走的Action。
不管怎么说,反正现在可以自定义。
Github Action自带的deploy jekyll的workflow 来自于jekyll-build-pages。从原理上来说,我只要在它上传artifacts之前把pagefind索引的步骤塞进去就行。
然后先碰到了一个奇奇怪怪的问题。
......
[Building search indexes]
Total:
Indexed 1 language
Indexed 101 pages
Indexed 7937 words
Indexed 0 filters
Indexed 0 sorts
thread 'main' panicked at /home/runner/work/pagefind/pagefind/pagefind/src/output/mod.rs:350:46:
called `Result::unwrap()` on an `Err` value: Os { code: 13, kind: PermissionDenied, message: "Permission denied" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: Process completed with exit code 101.
一开始我还以为是pagefind的可执行文件权限不对,来了一波chmod 755
之类的,一点儿都不管用。然后我ll
了一下,才发现
-rw-r--r-- 1 runner docker 3265 Aug 29 01:56 404.html
-rw-r--r-- 1 runner docker 209 Aug 29 01:56 Gemfile
-rw-r--r-- 1 runner docker 244 Aug 29 01:56 README.md
-rw-r--r-- 1 runner docker 684 Aug 29 01:56 _config.yml
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 _data
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 _includes
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 _layouts
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 _posts
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 _sass
drwxr-xr-x 19 root root 4096 Aug 29 01:56 _site
-rw-r--r-- 1 runner docker 2202 Aug 29 01:56 archive.html
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 ascii_art
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 css
-rw-r--r-- 1 runner docker 12862 Aug 29 01:56 favicon.ico
-rw-r--r-- 1 runner docker 1352 Aug 29 01:56 feed.xml
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 fonts
-rw-r--r-- 1 runner docker 5657 Aug 29 01:56 guestbook.html
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 img
-rw-r--r-- 1 runner docker 4466 Aug 29 01:56 index.html
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 js
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 lib
-rw-r--r-- 1 runner docker 1948 Aug 29 01:56 lot.html
-rwxr-xr-x 1 runner docker 66446392 Apr 2 19:25 pagefind_extended
-rw-r--r-- 1 runner docker 48695886 Apr 2 19:37 pagefind_extended-v1.1.0-x86_64-unknown-linux-musl.tar.gz
drwxr-xr-x 15 runner docker 4096 Aug 29 01:56 photo
-rw-r--r-- 1 runner docker 950 Aug 29 01:56 photos.html
drwxr-xr-x 2 runner docker 4096 Aug 29 01:56 play
-rw-r--r-- 1 runner docker 97 Aug 29 01:56 robots.txt
-rw-r--r-- 1 runner docker 336 Aug 29 01:56 search.html
-rw-r--r-- 1 runner docker 1748 Aug 29 01:56 tag.html
-rw-r--r-- 1 runner docker 2733 Aug 29 01:56 type.html
中间这_site的root:root怎么就那么刺眼呢。原来jekyll build在docker里面出来的文件夹本身就root了。
接着我就折腾了一波什么chown -R runner:docker
之类的,各种不管用,也没找到actions/jekyll-build-pages@v1
有什么配置可以设置docker的arg的地方,只有输出文件夹的地址啥的。
最后搜了一圈,找到了Reset Workspace Ownership Action,终于好用了
最后我的workflow大致是这样:
name: Build Jekyll Site with Pagefind
on:
push:
branches: ["master"]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Build
uses: actions/jekyll-build-pages@v1
- name: Get Actions user id
id: get_uid
run: |
actions_user_id=`id -u $USER`
echo $actions_user_id
echo "uid=$actions_user_id" >> $GITHUB_OUTPUT
- name: Correct Ownership in GITHUB_WORKSPACE directory
uses: peter-murray/reset-workspace-ownership-action@v1
with:
user_id: ${{ steps.get_uid.outputs.uid }}
- name: Index pagefind
run: |
wget https://github.com/CloudCannon/pagefind/releases/download/${{ vars.PAGEFIND_VERSION }}/${{ vars.PAGEFIND_PACKAGE_NAME }}
tar -xvzf ${{ vars.PAGEFIND_PACKAGE_NAME }}
./pagefind_extended --site ${{ github.workspace }}/_site --glob blog/post/**/*.html
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
另带2个环境变量,想着方便以后维护吧,毕竟是从别人Release现成拉下来的。
PAGEFIND_PACKAGE_NAME=pagefind_extended-v1.1.0-x86_64-unknown-linux-musl.tar.gz
PAGEFIND_VERSION=v1.1.0
接着就愉快地把repo里面的pagefind文件夹给干掉了!我可以在线编辑markdown不用管编译这种乱糟糟的事情了!
感觉Github Action真心强大,不知道能不能把我的评论系统之每次新建post都要手动建Issue这个问题也给解决了呢?
现在还有一个小问题,就是搜索的结果里面会把emoji作为图片放在meta里,觉得怪怪的。我试了ignore却并不好用,回头想想怎么整。
在Pagefind的Github发了相关的Issue: exclude-selectors does not work,虽然说结果并不能解决我的问题(反而得到了不太理想的解释),不过这个活跃的项目给我一种友好+富有活力的感觉,真的越来越喜欢它了!