跳转至

站内搜索:原理与配置#

本页记录本站搜索是怎么工作的——机制、产物、限制与配置位置。 一句话:搜索完全在浏览器里跑,没有服务端查询;索引在构建时生成, 由浏览器下载后现场建立。

一、为什么中文要特别处理#

本站搜索引擎是 lunr.js(由 MkDocs Material 提供)。lunr 是英文分词思路: 按空白与标点切词,再对词做词干化。中文没有空格,因此如果直接把中文丢给它, 整个句子会被当成一个词,搜索基本失效。

Material 的做法不是换引擎,而是在构建期把中文预先切好,再用一个特殊字符 把词与词标记开——这个字符就是零宽空格 U+200B(下称 \u200b,肉眼不可见)。

二、构建时发生了什么(jieba 切词 + 零宽空格)#

本项目使用 MkDocs Material 9.7.7,其 search 插件内置 jieba 中文分词 (material/plugins/search/plugin.py;配置项 jieba_dict、jieba_dict_user)。 构建每个页面段落时,它会把连续的汉字串交给 jieba.cut(),再用 U+200B 连接回去:

原文:  关于成立一个编程论坛的想法
索引里:关于\u200b成立\u200b一个\u200b编程\u200b论坛\u200b的\u200b想法

同时,索引的 config.separator 被设成包含 \u200b 的字符类:

[\s\u200b\u3000\-、。,.?!;]+

即:空白、零宽空格、全角空格、中日标点都是分隔符。于是 jieba 切出来的词 就成了真正的检索单位,标点也自然分词(这也是「简单、自洽、利他」能被拆成三个词的 原因)。

在 search_index.json 里该正则是以转义形式存放的(写作 [\\s\\u200b…]+), 上面给出的是它被解析后的形态。

所以 mkdocs.yml 里那句注释「jieba is installed → Chinese segmentation works」 对这个版本是准确的:jieba 由 search 插件自己调用,不是靠外部钩子。

三、索引产物#

项 值(2026-09-18)
文件 site/search/search_index.json
大小 约 738 KB(未压缩)
粒度 按标题切分的段落,不是整页
条目 274 段 / 27 页
预构建索引 index: null → 没有,由浏览器现场构建
文件名指纹 无(每次构建同名覆盖)

结构是两部分:config(lang、separator、pipeline、fields)+ docs(每段的 location / title / text)。location 里的中文是百分号编码的,例如 wiki/pages/%E4%B8%89%E5%85%AC%E7%90%86%E6%BA%AF%E6%BA%90/。

四、浏览器里发生了什么#

  1. 打开任意页面,Material 异步请求 search/search_index.json;
  2. 首次搜索时,用 lunr.js 依 config 现场建立索引——这就是搜索框里 「正在初始化搜索引擎」的含义;
  3. 输入查询 → 查询串只按 separator 切分(浏览器端没有 jieba);
  4. 命中按段落返回,支持结果高亮(search.highlight)与分享链接 (search.share,形如 ?q=关键词)。

五、由此推出的实际限制(重要)#

  • 查询要是一个「词」,而不是一句话。 索引里存的是 jieba 切出来的词;浏览器端 只按分隔符切查询,所以粘一整句没有分隔的中文进去,它仍然只是一个 token, 必须与索引中的某个词完全一致才命中。
  • 通常能命中:卡尔维诺、自洽、公理、荣耀经、利他
  • 多半搜不到:把整句原文粘进去
  • 专有名词可能被 jieba 切错。 人名、书名、曲名、自造词(如「三公理」「肾后」 「老彼得」)若切错,就搜不到。补救办法是给分词器提供词典:
plugins:
  - search:
      jieba_dict:  tools/jieba_dict.txt        # 替换主词典(谨慎)
      jieba_dict_user: tools/jieba_user.txt    # 追加用户词(推荐)
  • config.lang 目前解析为 ['en'](只装英文词干),与主题的 language: zh 并不同步。对汉字 token 无害,但若要做更地道的中日韩处理, 可显式配置 search 的 lang(Material 随包提供 lunr.zh.min.js)。
  • prebuild_index 默认关闭。 打开(prebuild_index: python|node)能把 lunr 索引预构建进 JSON、省下客户端 CPU,但文件会更大——在带宽受限时反而更慢, 需按实际链路权衡。
  • 搜索是纯客户端的:没有服务端检索,整包索引都会下发给访客。

六、运维注意#

  • search_index.json 不带文件名指纹,所以它必须可被重新校验;若服务器不发 Cache-Control,浏览器可能复用旧索引(见 DEPLOY.md 的缓存策略一节)。
  • 索引体积随页面数增长;中文经 jieba + 零宽空格处理后偏大,因为每个词都要多付 3 字节的 U+200B。
  • 排障口诀:搜索框一直停在「正在初始化搜索引擎」= 索引没有下载完, 而不是「搜索坏了」。先在浏览器控制台看该请求的状态与耗时。

七、配置位置一览#

# mkdocs.yml
theme:
  language: zh            # 界面语言
  features:
    - search.highlight    # 结果高亮
    - search.share        # 生成 ?q= 分享链接

plugins:
  - search                # 可选项:lang / separator / jieba_dict /
                          # jieba_dict_user / prebuild_index / indexing / fields

搜索索引由 tools/build_site.py 每次构建时重新生成,不是手工维护的文件, 也不在 wiki/ 手工层内。

参见#

  • 维客索引 — 全站主题页清单(搜索之外的另一条入口)
  • 维护规范 — wiki 结构约定与 ingest / query / lint 流程
  • 操作日志 — 本页的成文与后续改动记录

本页创建于 2026-09-18,最后更新于 2026-09-19。