关于一套独立的Galgame Wiki系统开发日志

Web
139 编辑于

一、写在最前:为什么第一份日志就要写“架构”?

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 一把梭,而是分层设计:

  1. 精确哈希层(Redis):将高频别名作为 Key,实体 ID 作为 Value 存入 Redis。用户输入“高岛”直接 O(1) 命中。这解决了 80% 的短尾搜索。
  2. AC 自动机层(Aho–Corasick):将全部别名(约 12 万条初始种子)构建成多模式匹配树,在用户输入的同时进行流式匹配,实现“输入即提示”。
  3. 模糊检索层(Elasticsearch 边缘 N-gram):当精确匹配失败时(例如用户输错了字),利用 ES 的 edge_ngram 和拼音插件进行容错。

重点实例:
用户输入“高岛” → Redis 命中别名表(alias_name='高岛')→ 映射到实体“高島拓也”(《樱花大战》角色)→ 直接跳转。用户不需要知道标准词条名是“高島拓也”,也不需要输入 [高岛] 这种反人类的标记。

4.2 别名自动化挖掘(爬虫+NLU)

为了让别名表不依赖人工录入,我们开发了自动挖掘脚本:

  • 爬取各大汉化组发布页的标题备注(如“【某某汉化组】[高岛] 最新作”)。

  • 利用日文维基和中文维基的跨语言链接,自动对齐中、日、英标题。

  • 针对粉丝圈常见的“X神”“X社”进行规则生成(如“Key社”→“Key”)。



五、全自动内链系统:从“纯文本”到“带蓝链的富文本”

这是整个系统最复杂、踩坑最多的模块。传统思路是在保存时做正则替换 preg_replace,但会遇到三大灾难:交叉重叠冲突(例如“Key”和“Key社”同时匹配时选谁)、循环嵌套(链接套链接)、错杀普通词汇(将正文里的“石头”误认为“石头门”)。

5.1 最长词优先 + 双向最大匹配(Bi-MM)

我们的策略是这样的:

  1. 将正文按标点符号(句号、逗号、换行)分割为若干个短句块

  2. 对每一个短句块,使用 AC 自动机找出所有潜在命中区间 [start, end]

  3. 采用 “贪心最长匹配 + 区间冲突解决” 算法:

    • 如果区间 A 完全包含区间 B(如 [1, 10] 匹配到“Key社”,[3, 6] 匹配到“Key”),则丢弃短词,保留长词

    • 如果区间交叉但不包含(如 [1,5][4,8]),则根据实体热度(PV)和别名优先级决定谁胜出,剔除另一个。

  4. 对于剩下的唯一区间,生成 <a href="/entity/{id}">{matched_text}</a>

5.2 上下文消歧(基于共现词袋)

同名不同实体是最大痛点。例如“樱”可能指《Fate》的间桐樱,也可能指《樱花大战》的真宫寺樱。我们的策略并非深奥的 NLP 语义分析(太慢),而是利用词袋共现

  • 当命中“樱”且别名优先级相同时,向上、向下各扫描 50 个字符。

  • 如果这 100 个字符中出现“圣杯”“魔术师”“远坂”,则倾向指向“间桐樱”。

  • 如果出现“帝国华击团”“太正时代”“光武”,则倾向指向“真宫寺樱”。

  • 若仍无法判断,则生成消歧气泡(即弹出选择框),而不是武断链接。

5.3 性能优化(异步渲染)

我们并未在 PHP/Python 后端保存时硬写死 HTML 链接,因为那样会导致正文存储臃肿且难以回退。我们的做法是 “动态注入”

  • 数据库仅存储纯净的 Markdown/文本。

  • 页面请求时,经过渲染管线:

    1. 先渲染 Markdown 为 HTML。
    2. 将 HTML 中的纯文本节点提取出来,运行上述自动链接算法。
    3. 使用 DOM 解析器将匹配到的文本包裹上 <a> 标签。
    4. 存入 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 算法) 全站“关系补全”建议队列

九、已知的硬骨头与反思(写给后来的维护者)

  1. 消歧的终极困境:虽然我们的共现词袋解决了 85% 的情况,但对于极短词(如“翼”),依然容易出现误链。目前妥协方案是:低于 2 个字符且优先级低于阈值的别名,默认不生成自动内链,只保留搜索直达功能。
  2. 编辑冲突与缓存雪崩:当一个热门实体(如“麻枝准”)的别名被修改时,所有包含该名称的数千篇页面缓存都要失效。我们引入了 “依赖追踪表”,记录每一个实体被哪些页面引用,更新时精准失效,避免全局清空 Redis。
  3. 多语种混合分词:Galgame 文本中经常出现“夜明け前より瑠璃色な”这种纯日文标题,与中文混杂。我们放弃了通用的 jieba 分词,改为 “自定义词典优先 + 日文形态素(Kuromoji)后备” 的策略

本文版权遵循 CC BY-NC 协议 本站版权政策

1 条回复

鲲

9686

#1
发布于

其实 鲲 Galgame 的上游 wiki 数据来自于 nextmoe,nextmoe 已经实现这些主要部分了,还在内测,只是还没有应用到 kungal

你可以到我们开发群 @KUNForum 玩

(。>︿<。) 已经一滴回复都不剩了哦~