一、写在最前:为什么第一份日志就要写“架构”?
Galgame Wiki 不同于普通维基。普通维基(如维基百科)的内容相对稳态,编辑者众,人工维护成本可以被摊薄。但 Galgame 领域具有极强的亚文化封闭性和术语爆炸性:一部游戏可能有官方译名、民间译名、日文原名、罗马音、粉丝爱称(如“万华镜”“艹猫”“脏翅膀”)。如果依赖人力去维护每一个重定向和内部链接,不出三个月,编辑组就会因枯燥的机械劳动而崩溃。
因此,我们的开发铁律是:凡是能通过算法自动化的,绝不留给人工。 本日志将详细记录我们第一个核心里程碑——“零感知检索与自动化链接系统”的完整开发过程、数据结构、算法取舍与性能压测,并附上下一阶段的详细预研计划。
简单来说:因为Wiki的核心是内链,比如我的Wiki因为口语化而输入了键子,藏翅膀,艹猫,sayo教,但是这些必须去指向正确的Wiki页面,然而,按照其他Wiki的思路去做映射表,最大的问题是,这需要多少人工成本?
况且,比如石头指向石头门,Key指向Key社?但是万一石头就是石头,Key就是密钥的意思,按照其他Wiki的思路,这就出现了严重的错误链接,而且,Galgame有多少部,很多因为都进行了简单的口语化,比如翼,按照正常的Wiki架构和开发,是没办法用在Galgame身上的,这也是我为什么要脱离fandom和其他已经有的Wiki框架,坚持独立开发的原因,因为那些框架压根不是为Galgame设计的,生搬硬套到Galgame身上,会出现严重的问题。
就拿很火的fandom举个例子,如果我用fandom去做Galgame的Wiki,会出现一下严重的问题:
内容审查与Galgame天然冲突
Fandom的“社区内容政策”日益收紧,尤其在“冒犯性语言和图像内容”方面,执行往往“过于激进,甚至影响到游戏内广告牌图片和角色语音片段”。
Galgame是什么?是一类以“具有魅力的女性为卖点”的游戏,其中大量作品涉及恋爱、性暗示乃至成人内容。Fandom的政策对这些内容的态度是什么?该平台明确“强烈反对”包含成人内容的维基,甚至有用户举报包含“色情、成人漫画和成人恶搞内容”的条目。这不是一个可以协商的边缘问题——它意味着Galgame Wiki的核心内容本身就可能随时面临被删除或限制的风险。
一个连《GTA》里的游戏台词和广告牌都容不下的平台,怎么可能容得下Galgame?
平台控制权完全不在社区手中
Fandom被私募股权公司TPG Capital收购后,社区编辑者逐渐失去对自身内容的主控权。平台会“未经同意”在维基页面上添加视频;会推出“糟糕且未经测试的功能”并坚持保留,无视社区的强烈反对;甚至连大规模保护页面以防恶意编辑都不被允许。
《空洞骑士》维基的编辑者直言:“我们容忍了这么久,因为最初他们还会倾听和反馈。但后来一切都变了”。一个社区的维基,最终却连自己的页面长什么样、有什么功能都无法决定——这对于倾注了大量心血的编辑者来说,是难以接受的。
编辑门槛过高
MediaWiki要求编辑者学习一套复杂的标记语言,尽管用户多次请求,至今没有成熟的所见即所得支持。对于一个以爱好者社区为核心的Galgame Wiki来说,这意味着贡献知识的门槛被人为抬高——不是每个人都能记住[[和{{}}的用法。
功能扩展的代价
萌娘百科在2025年升级MediaWiki版本时,因新版对第三方扩展和模板支持不佳,被迫删除了大量高使用的模板和扩展,导致大面积功能失效。这说明,在MediaWiki上构建深度定制功能,每次版本升级都是一场豪赌。而Galgame Wiki所需要的别名系统、自动内链、关系图谱等功能,恰恰需要深度的定制开发。
内容适应性差
有维基运营者指出,MediaWiki“对内容适应性略差”,某些复杂的内嵌模板代码可能导致解析失败,且“加载慢,个别极长页面可能崩溃”。Galgame词条往往包含大量的角色列表、路线分支、CG列表等结构化数据,这些内容在通用Wiki框架下要么难以呈现,要么性能堪忧。
Galgame不是一种游戏,是一个复杂的文化
Galgame的知识不是孤立的点,而是一张密集的网:
- 一个游戏→多个角色→各自声优→所属事务所
- 一个会社→多个作品→同一原画/剧本/音乐
- 一个系列→前作/续作/FanDisk→共享世界观
正如VNDB(视觉小说数据库)所展示的,视觉小说需要“关系图来详细说明作品之间的序列关系(续作、前传、FanDisk、扩展)和虚构宇宙的共性”。这种关系图谱不是通用Wiki的标配功能,而是需要从底层设计就开始支持的架构。
二、第一阶段的终极目标定义
在动工前,我们先给“好用”下了三个硬指标:
| 指标 | 具体描述 | 验收标准 |
|---|---|---|
| 无感搜索 | 用户不需要知道词条的标准名,凭记忆中的任何称呼都能直达 | 输入“高岛”无需加括号或前缀,直接命中“高島拓也”或对应人物 |
| 自动织网 | 用户编写的正文无需手动添加 [[]] 标记,系统自动识别实体并添加超链接 |
编辑器只写纯文本,保存后页面自动充满蓝链 |
| 毫秒响应 | 搜索建议与内链解析不能拖慢页面加载 | 搜索下拉响应 < 200ms,页面渲染后内链注入 < 500ms |
三、核心数据层设计(第一版 Schema)
一切智能都建立在数据结构的严谨性上。我们摒弃了传统 Wiki 平铺直叙的 page + redirect 简单模型,引入了实体-别名-关系的三层架构。
3.1 核心表结构(MySQL + Redis 缓存)
表 A:wiki_entities(实体主表)
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
id |
bigint | 自增主键 | 10086 |
title_cn |
varchar(255) | 中文标准标题 | 命运石之门 |
title_jp |
varchar(255) | 日文原名 | STEINS;GATE |
title_en |
varchar(255) | 罗马音/英文名 | Steins;Gate |
entity_type |
tinyint | 实体类型枚举 | 1=游戏, 2=角色, 3=会社, 4=人物(Staff), 5=术语 |
sort_key |
varchar(50) | 排序关键词(忽略前缀) | “命运” 或 “STEINS” |
summary |
text | 词条摘要(用于搜索权重提升) | 科幻冒险游戏…… |
content |
longtext | 词条正文(原始 Markdown/HTML) | …… |
view_count |
int | 浏览量 | 0 |
status |
tinyint | 审核状态 | 1=草稿, 2=已发布 |
表 B:wiki_aliases(别名映射表)—— 本次开发的重中之重
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
id |
bigint | 自增主键 | 50001 |
entity_id |
bigint | 指向实体主表 | 10086 |
alias_name |
varchar(255) | 别名文本(带语言标记) | “石头门” / “シュタゲ” / “SG” |
lang |
tinyint | 语种 | 1=中文, 2=日文, 3=英文, 4=数字/符号梗 |
source_type |
tinyint | 来源 | 1=官方, 2=民间汉化组, 3=粉丝爱称, 4=OCR/自动抽取 |
priority |
int | 优先级(解决同名冲突) | 数字越大越优先匹配 |
is_abbr |
boolean | 是否为缩写(影响分词策略) | true/false |
表 C:wiki_entity_relations(关系预存表 —— 为下一阶段图谱预热)
虽然当前阶段只做内链,但我们已经预留了关系表,并开始从信息框自动抽取基础关系,供后续图谱使用。
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
id |
bigint | 自增 | |
source_id |
bigint | 源实体 | 《CLANNAD》的ID |
target_id |
bigint | 目标实体 | “麻枝准”的ID |
relation_type |
varchar(50) | 关系谓词 | “剧本” / “原画” / “开发” |
weight |
float | 置信度/权重 | 0.98 |
四、别名系统的核心算法实战
4.1 三层匹配引擎(Pipeline)
我们并没有直接用 Elasticsearch 一把梭,而是分层设计:
- 精确哈希层(Redis):将高频别名作为 Key,实体 ID 作为 Value 存入 Redis。用户输入“高岛”直接 O(1) 命中。这解决了 80% 的短尾搜索。
- AC 自动机层(Aho–Corasick):将全部别名(约 12 万条初始种子)构建成多模式匹配树,在用户输入的同时进行流式匹配,实现“输入即提示”。
- 模糊检索层(Elasticsearch 边缘 N-gram):当精确匹配失败时(例如用户输错了字),利用 ES 的
edge_ngram和拼音插件进行容错。
重点实例:
用户输入“高岛” → Redis 命中别名表(alias_name='高岛')→ 映射到实体“高島拓也”(《樱花大战》角色)→ 直接跳转。用户不需要知道标准词条名是“高島拓也”,也不需要输入[高岛]这种反人类的标记。
4.2 别名自动化挖掘(爬虫+NLU)
为了让别名表不依赖人工录入,我们开发了自动挖掘脚本:
-
爬取各大汉化组发布页的标题备注(如“【某某汉化组】[高岛] 最新作”)。
-
利用日文维基和中文维基的跨语言链接,自动对齐中、日、英标题。
-
针对粉丝圈常见的“X神”“X社”进行规则生成(如“Key社”→“Key”)。
五、全自动内链系统:从“纯文本”到“带蓝链的富文本”
这是整个系统最复杂、踩坑最多的模块。传统思路是在保存时做正则替换 preg_replace,但会遇到三大灾难:交叉重叠冲突(例如“Key”和“Key社”同时匹配时选谁)、循环嵌套(链接套链接)、错杀普通词汇(将正文里的“石头”误认为“石头门”)。
5.1 最长词优先 + 双向最大匹配(Bi-MM)
我们的策略是这样的:
-
将正文按标点符号(句号、逗号、换行)分割为若干个短句块。
-
对每一个短句块,使用 AC 自动机找出所有潜在命中区间
[start, end]。 -
采用 “贪心最长匹配 + 区间冲突解决” 算法:
-
如果区间 A 完全包含区间 B(如
[1, 10]匹配到“Key社”,[3, 6]匹配到“Key”),则丢弃短词,保留长词。 -
如果区间交叉但不包含(如
[1,5]和[4,8]),则根据实体热度(PV)和别名优先级决定谁胜出,剔除另一个。
-
-
对于剩下的唯一区间,生成
<a href="/entity/{id}">{matched_text}</a>。
5.2 上下文消歧(基于共现词袋)
同名不同实体是最大痛点。例如“樱”可能指《Fate》的间桐樱,也可能指《樱花大战》的真宫寺樱。我们的策略并非深奥的 NLP 语义分析(太慢),而是利用词袋共现:
-
当命中“樱”且别名优先级相同时,向上、向下各扫描 50 个字符。
-
如果这 100 个字符中出现“圣杯”“魔术师”“远坂”,则倾向指向“间桐樱”。
-
如果出现“帝国华击团”“太正时代”“光武”,则倾向指向“真宫寺樱”。
-
若仍无法判断,则生成消歧气泡(即弹出选择框),而不是武断链接。
5.3 性能优化(异步渲染)
我们并未在 PHP/Python 后端保存时硬写死 HTML 链接,因为那样会导致正文存储臃肿且难以回退。我们的做法是 “动态注入”:
-
数据库仅存储纯净的 Markdown/文本。
-
页面请求时,经过渲染管线:
- 先渲染 Markdown 为 HTML。
- 将 HTML 中的纯文本节点提取出来,运行上述自动链接算法。
- 使用 DOM 解析器将匹配到的文本包裹上
<a>标签。 - 存入 Redis 页面缓存(有效期 24 小时,实体更新时主动失效)。
这套管线使得首次渲染耗时约 300ms,缓存命中后响应 < 50ms。
六、真实用户路径演示(端到端测试)
假设一位新用户刚刚打开 Galgame Wiki:
| 步骤 | 用户行为 | 系统内部动作 | 前端反馈 |
|---|---|---|---|
| 1 | 在首页搜索框输入“脏翅膀” | Redis 精确命中别名(脏翅膀 → 秽翼的尤斯蒂娅) | 搜索下拉立即显示“秽翼的尤斯蒂娅”,并带有“热门游戏”角标 |
| 2 | 用户点击进入词条 | 后端从 MySQL 取出纯净正文,启动 AC 自动机扫描 | 页面加载后,正文中的“八城十八”、“吉里吉里”全部自动变为蓝色超链接 |
| 3 | 用户看到“吉里吉里”蓝链,点击跳转 | 搜索系统再次启动,精确命中“吉里吉里(KiriKiri)”引擎词条 | 平滑跳转至引擎介绍页,并在面包屑导航显示“回到 秽翼的尤斯蒂娅” |
| 4 | 用户返回编辑模式,尝试新增一个词条,正文输入“由宇是神” | 保存时后端提取“由宇”(别名指向《秋之回忆》某角色)和“神”(通过上下文判定此处为形容词,忽略不生成链接) | 保存成功后,前端展示正文仅“由宇”带链接,“是神”保持纯文本 |
七、压力测试数据(第一版上线前压测)
我们模拟了 1000 篇长文(平均每篇 5000 字)同时提交并触发自动内链解析:
| 测试项 | 并发数 | 平均解析耗时 | CPU 峰值 | 内存占用 |
|---|---|---|---|---|
| 纯文本扫描(AC 自动机) | 50 | 320ms | 65% | 1.2GB |
| 带消歧扫描(共现词袋) | 50 | 580ms | 89% | 2.1GB |
| Redis 缓存命中加载 | 500 QPS | 12ms | 12% | 800MB |
结论:AC 自动机树内存占用约 150MB(含 12 万节点),完全可控;消歧逻辑在极端高并发下存在瓶颈,后续需引入协程异步处理。
八、下一步开发蓝图:关系图谱与知识发现(2026.07 - 2026.12)
当前系统虽然解决了“检索”和“链接”,但用户依然处于被动接收信息的状态。下一阶段,我们要从 “点” 走向 “网”。
8.1 为什么 Galgame 极度依赖图谱?
因为一个 Galgame 玩家最典型的求知路径是:“我玩过《千恋万花》,觉得原画很棒,我想看看这位画师还画过哪些游戏,这些游戏又是哪个会社出的,该会社最近有什么新作。” 这在传统 Wiki 中需要手动跳转 5~6 次,而在图谱中,只是一个圈层展开的距离。
8.2 核心实体关系矩阵(详细定义)
我们在下一阶段将正式上线图数据库(Neo4j),并迁移预存的关系数据。以下是完整的 3 大类 16 小类关系定义:
| 关系类别 | 关系名 | 源 → 目标 | 典型实例 |
|---|---|---|---|
| 创作关联 | 开发(Developed) | 游戏 → 会社 | 《Summer Pockets》→ Key |
| 发行(Publish) | 游戏 → 会社 | 《Fate/stay night》→ TYPE-MOON (发行) | |
| 原画(Art) | 游戏 → 人物 | 《千恋万花》→ こもわた遙華 | |
| 剧本(Script) | 游戏 → 人物 | 《CLANNAD》→ 麻枝准 | |
| 音乐(Music) | 游戏 → 人物 | 《秽翼》→ Active Planets | |
| 配音(VA) | 游戏角色 → 声优 | 古河渚 → 中原麻衣 | |
| 导演(Directed) | 游戏 → 人物 | 《Muv-Luv》→ 吉田博彦 | |
| 作品系谱 | 正统续作(Sequel) | 游戏 → 游戏 | 《Muv-Luv Alternative》→《Muv-Luv》 |
| 前传(Prequel) | 游戏 → 游戏 | 《Fate/Zero》→《Fate/stay night》 | |
| FanDisk | 游戏 → 游戏 | 《CLANNAD Side Stories》→《CLANNAD》 | |
| 相同世界观(Shared Universe) | 游戏 → 游戏 | 《CANAAN》↔《428 被封锁的涉谷》 | |
| 衍生作(Spinoff) | 游戏 → 游戏 | 《魔法少女伊莉雅》→《Fate/stay night》 | |
| 业界生态 | 母公司(Parent) | 会社 → 会社 | Key → Visual Arts |
| 品牌/分社(Brand) | 会社 → 会社 | 戯画 → TGL | |
| 合作(Collaboration) | 会社 ↔ 会社 | 型月 ↔ 光荣特库摩(Fate/Samurai Remnant) | |
| 前雇员(Former Staff) | 人物 → 会社 | 麻枝准 (前 Key,现转型) |
8.3 图谱应用场景详细预演
场景 A:词条页底部的“知识星空”
在《千恋万花》页面底部,我们不再展示干巴巴的“相关条目”,而是渲染一个可交互的力导向图:
-
中心节点:千恋万花(高亮)
-
1 度邻居:柚子社、こもわた遙華、天宫心(角色)、声优等。
-
用户操作:拖动 1 度邻居“柚子社”至画布中央,图谱自动加载第二层关系——柚子社开发的其他作品(《DRACU-RIOT!》《RIDDLE JOKER》等)。
场景 B:搜索“麻枝准”的侧边栏速览
搜索结果页面右侧固定显示一个迷你雷达图,直观展示该人物在业界的“连接度”:
-
剧本作品:CLANNAD, Air, Kanon, Angel Beats!
-
音乐作品:同上 + 智代After
-
所属变迁:前 Key 社 → 现 VISUAL ARTS 旗下
-
用户可一键导出此关系图为图片分享。
场景 C:“猜你喜欢”与补全推荐
基于图遍历算法(最短路径),当系统发现《Summer Pockets》和《LOOPERS》拥有共同的实体节点“麻枝准”(原案)且没有任何关联标记时,后台任务队列会自动生成一个审核提示:
💡 系统发现潜在关联:
《Summer Pockets》与《LOOPERS》共享人物麻枝准(担任角色:原案)。建议建立“关联作品 (Related)”关系。是否一键采纳?
8.4 技术架构迭代(Q3-Q4 路线图)
| 阶段 | 时间 | 开发任务 | 交付物/产出 |
|---|---|---|---|
| Phase 1 | 8月 - 9月 | Neo4j 集群部署,将现有 MySQL 预存关系(约 8000 条)迁移至图库,编写 Cypher 查询封装层 | 图数据库 Schema v1.0,基础 CRUD API |
| Phase 2 | 10月 | 前端图谱渲染器开发(基于 vis.js 改造),实现点击展开、收缩、高亮最短路径 | 词条页内嵌交互图谱组件 |
| Phase 3 | 11月 | 批量关系导入工具 + 后台审核面板(编辑者可手动拖拽画布添加新关系) | 运营后台 v2.0 |
| Phase 4 | 12月 | 智能补全推荐算法上线(基于 Jaccard 相似度 + 社区发现 Louvain 算法) | 全站“关系补全”建议队列 |
九、已知的硬骨头与反思(写给后来的维护者)
- 消歧的终极困境:虽然我们的共现词袋解决了 85% 的情况,但对于极短词(如“翼”),依然容易出现误链。目前妥协方案是:低于 2 个字符且优先级低于阈值的别名,默认不生成自动内链,只保留搜索直达功能。
- 编辑冲突与缓存雪崩:当一个热门实体(如“麻枝准”)的别名被修改时,所有包含该名称的数千篇页面缓存都要失效。我们引入了 “依赖追踪表”,记录每一个实体被哪些页面引用,更新时精准失效,避免全局清空 Redis。
- 多语种混合分词:Galgame 文本中经常出现“夜明け前より瑠璃色な”这种纯日文标题,与中文混杂。我们放弃了通用的 jieba 分词,改为 “自定义词典优先 + 日文形态素(Kuromoji)后备” 的策略


1 条回复