前阵子整理游戏库的时候我算是彻底绷不住了。电视柜底下堆了十几张实体盘,外置硬盘里躺着上百个数字版游戏,加上会员每个月塞进来的会免游戏,我压根说不清楚自己到底买了多少游戏、哪些打通了、哪些烂尾了。为了治这个毛病,我自己写了一个小工具,代号叫 AnyPS5——意思是不管你手里是标准版、薄版还是Pro版,都能用同一套办法把你的游戏资产管明白。这个项目不破解主机、不碰灰色地带、不需要root权限,就是一个纯靠手动录入、本地部署、数据自己说了算的游戏库管理系统。如果你是那种游戏买了不少但永远搞不清库存的人,或者你想给自己做个私人游戏账本,这篇文章值得你看下去。
1. 需求从哪来:游戏越买越多之后的"账本焦虑"
1.1 会免、折扣、实体盘混在一起的乱账
我大概是PS5发售之后两年才开始认真积累游戏库的。那时候觉得无所谓,买一张玩一张,无所谓管理。后来情况变了:商店大促一年来个七八次,会员每个月都往库里塞游戏,实体盘看到便宜就想收一张,结果就变成了典型的"库存失控"状态。
最直接的导火索是某次想找一个游戏,翻了半天不知道是数字版还是实体盘,最后发现根本没买,只有下载版在另一台机器上。那一刻我意识到,游戏资产和理财一样,不做台账就永远是糊涂账。于是我在电脑上开了一个Excel,想记录"游戏名、版本、入手渠道、价格、通关状态、游玩时长"。这个思路没错,但坚持了两个月就放弃了——每次打开表格手工更新太反人性,尤其打完一个游戏还要记得去改状态,没过多久表格就烂尾了。
这就是 AnyPS5 的起点:我要的不是一个单纯填表工具,而是一个能让我以最低成本维护、打开就能看到全貌的游戏资产库。
1.2 市面方案为什么不够用
在动手之前,我认真研究过市面上已有的游戏库管理方式。结论是:没有一个完全满足我的需求。
第一类是主机系统自带的游戏库界面。它当然能显示你买过什么,但问题是它只分"已安装/未安装",没有通关状态、没有价格记录、没有实体盘管理,也没有跨设备的统一视图。第二类是第三方奖杯统计和收藏网站。这类网站信息全,但数据要手动同步,有些还依赖玩家社区的人肉标注,中小众游戏的信息大概率是残缺的。更重要的是,这类网站大多要求注册账号、上传你的游戏数据,我不太想把整个游戏库暴露在一个不透明的平台上。
还有一个非常现实的问题:隐私和可持续性。我不想依赖任何一个哪天可能关停的第三方服务。所以 AnyPS5 从设计第一天就定了"本地优先、数据自理"的路线——所有数据存在我自己机器上,备份就是把数据库文件拷走,服务挂了重启就行,没有任何厂商能收回这个工具。
2. 整体设计:把"数据自理"当成第一原则
2.1 AnyPS5到底做了什么
一句话描述:AnyPS5 是一个面向PS5玩家的自托管游戏库管理平台,它把散落在大脑、Excel、收纳盒里的游戏信息统一收进一个本地数据库,并提供录入、查询、统计、导出四类能力。
它不做什么,可能比做什么更重要。它不读取主机内部数据,不做系统破解,不自动同步账户信息。这些能力不是做不到,而是会触碰厂商的服务条款和灰色地带,没必要为了一个记账工具把自己搞到风险里去。AnyPS5 的数据入口只有两个:你手动填写的表单,以及你按模板整理好的 CSV 文件。听起来很原始,但正是这种"原始"让整个项目变得异常干净和安全。
整个项目跑起来之后就是一个局域网内可访问的网页。你在电脑上录入,在平板上查看报表,手机也可以访问。所有数据落在一个 SQLite 数据库文件里,想换机器直接把目录拷走。
2.2 数据模型设计
一开始我在设计数据库表的时候犯了选择困难症。游戏信息看起来很直观,但实际建模时有几个绕不开的问题:一个游戏可能同时有实体版和数字版,一个游戏名在中文和英文下可能完全对不上,一个游戏可能有多个通关状态(比如普通通关了但 DLC 没玩),价格是入手时的信息而不是当前的售价。
经过两轮重构,最后的核心表结构长这样:
CREATE TABLE games ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, name_cn TEXT, publisher TEXT, genres TEXT, release_year INTEGER, cover_url TEXT ); CREATE TABLE licenses ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL REFERENCES games(id), license_type TEXT CHECK(license_type IN ('digital', 'disc')), acquired_date TEXT, price REAL, region TEXT, store TEXT, notes TEXT ); CREATE TABLE playthroughs ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL REFERENCES games(id), status TEXT CHECK(status IN ('not_started', 'playing', 'completed', 'platinum', 'dropped')), hours_played REAL, started_date TEXT, completed_date TEXT, rating INTEGER );这个设计的核心思路是"游戏"和"持有凭证"分离。同一个游戏,你既买了数字版又买了实体版,在 games 表里只有一条记录,在 licenses 表里则有两条。这样统计"我有多少个游戏"和"我一共持有了多少份游戏"就是两件不同的事,不会重复计算。playthroughs 表单独存放游玩进度,它和游戏是一对多的关系——同一款游戏你可以开多个周目,每个周目独立记录。
这个模型让我第一次体会到:"买过什么"和"玩过什么"是两个维度。前者是资产账,后者是体验账,任何独立的个人游戏库管理工具都应该把它们分开。
2.3 技术选型:轻量但够用
技术选型上,我刻意避开了复杂的分布式架构。这是个人工具,不是高并发的互联网产品,追求的是"一个人用一辈子不折腾"。
最终选型如下:
| 层次 | 选型 | 理由 |
|---|---|---|
| 前端 | Vue3 + Vite + Tailwind CSS | 开发效率高,组件生态成熟 |
| 后端 | FastAPI (Python) | 代码量少,自带 API 文档,适合单机工具 |
| 数据库 | SQLite | 零配置文件,单个文件即数据库,备份和迁移成本极低 |
| 可视化 | ECharts | 图表类型全,中文文档友好 |
| 部署 | Docker Compose | 一条命令起服务,换机器不折腾 |
后端之所以选 FastAPI 而不是 Django,理由很简单:这个项目根本没有复杂的权限体系,也没有后台管理站点,我要的只是几十个 API 接口和几行挂载静态页面的代码。FastAPI 写起来快,参数校验还自带文档,比 Django 轻太多了。
前端没有用任何重型状态管理库。Vue3 自带的 reactive 已经足够,所有状态都在当前页面内流转,不需要全局共享。为了减少用户等待,页面加载时一次性从后端拉取全部游戏数据(个人工具的数据量撑死几百条记录),后续的筛选和排序全在内存里完成。
3. 核心模块实现:从清单录入到可视化报表
3.1 游戏清单的双通道录入
这是整个工具里最关键的交互模块,也是最容易劝退用户的地方。如果录入一个游戏要填十多个字段、点五次按钮,那用不了几天就弃用了。我的目标是"录一款游戏的时间控制在30秒以内"。
通道一:手动表单。表单字段被我最简化为七项:游戏名、中文名、版本类型(数字/实体)、入手日期、入手价格、通关状态、游玩时长。其余字段如发行商、类型、年份全部留空,等有空的时候再补。空字段不影响统计——系统优先展示有数据的字段,没有的就不显示,绝不给用户"没填完就难受"的感觉。
通道二:批量导入。CSV 模板只设置三列必填:name、license_type、acquired_date。其他字段可空。后端解析 CSV 时做了三层校验:必填项是否缺失、license_type 是否是合法值、日期格式是否能被解析。任何一条出错都会在界面上明确提示具体行号和错误原因,而不是整个文件导入失败。
这里有一个细节:为避免用户在录入时看不到已有数据导致重复录入,表单顶部做了一个实时搜索框,输入一个字就立刻列出库内所有名称包含该字样的游戏。重复判断不是简单的字符串相等,而是"规范化之后相同才算相同"——空格、全角半角括号、大小写都会先被统一处理。
3.2 状态追踪与时长统计
通关状态是我使用频率最高的一个维度。我把它设计成一个五态模型:未开始、进行中、已通关、白金、弃坑。每种状态有对应的标记逻辑:进行中会自动累计一个"最近玩过"标签;已通关会建议你填通关日期;白金则附带一个醒目的庆祝标识;弃坑要求填一个弃坑原因,方便以后回看自己为什么没玩下去。
游戏时长这个字段,官方主机系统其实有记录,但没有任何开放接口能让人直接导出。这事我也研究过,最终确认在主机的官方应用里能看到时长数字,想自动拿进来要么靠手工抄,要么靠脚本模拟点击——后者是妥妥的违规操作。所以我干脆把手动录入设计成了"游戏结束时补一条记录"的方式:每小时大概记一个数就行,不需要计时器。这种"事后回忆"的误差对于个人游戏账本来说完全够用,它要的是趋势和总量,不是秒级精度。
3.3 可视化报表
报表页是我投入时间最多的地方,也是朋友来参观项目时最喜欢看的部分。表面上它只是几张图,其实每一张背后都对应一个"决策问题"。
最核心的是游戏库总览仪表盘:顶部显示总游戏数、已通关数、白金数、总游玩时长四个大数字;下面是一张按年份分组的入手数量柱状图;再往下是类型分布饼图。
然后是"投入产出分析"报表。它统计的是价格和时长的关系,横轴是入手价格,纵轴是游玩时长,每个点是一张游戏,点越大代表通关率越高。这样你一眼就能看出来哪些游戏是高性价比、哪些是买来吃灰的。这个报表对我影响很大——它让我在面对新折扣时,会不自觉地想"我大概率能玩多久"。
最后是月度回顾:每个月自动生成一张图,展示本月完成了哪些游戏、新购入了多少、游玩时间多少小时。我每次点开都会有一种"这个月没有白过"的实感,这大概是记账工具最具情绪价值的时刻。
4. 数据获取绕不开的三个坑
4.1 商店元数据自动抓取:我最后放弃了
理论上,每个游戏的封面、类型、发行商、发行年份都可以从商店网页上拿到。我也确实搭过一个抓取原型,通过商店搜索页取数据。踩坑之后我果断放弃了这条路线,原因有三。
第一,商店页面的搜索结果是通过动态请求加载的,直接请求普通网页拿不到有用的内容,必须逆向分析接口。这本身还能接受,但接口对请求频率和请求头非常敏感,稍微频繁一点就返回异常。第二,我意识到这属于绕过网页常规交互去抓取资料的灰色操作,虽然有"个人学习研究"的挡箭牌,但常年跑着一个每天戳商店接口几百次的程序,我心理上过不去。第三,个人项目最怕的是"维护成本失控"。商店页面改版一次,我的解析逻辑就要重写一次,这会让一个记账工具的维护成本变得比记账本身还高。
最后我选择了折中方案:录入页面内置了一个手动辅助填充功能,你输入英文名后,系统自动从公开的搜索建议接口拉取候选列表,你在列表里挑一个,系统把封面和年份带回来。这个交互是一个"人工确认"的环节,不是批量抓取,频率极低,稳定性和合规性都可控。实测下来,它解决了90%的封面补全需求,而剩下的10%哪怕空着也不影响统计。
4.2 中英文版本名对不上
你可能会觉得"游戏名不就是游戏名吗",实际操作后会发现完全不是。同一个游戏在商店里的中文名、英文名、玩家俗称、甚至不同地区的分级标题都可能是完全不同的字符串。比如某游戏的英文名是一个样子,中文版加了一个完全不相干的副标题,你要是按英文名去搜索就会一头雾水。
我的解决方案是在数据库里给 games 表增加了 name_cn 字段,并且把它当作一等公民来对待——录入时两个字段都有独立输入框,搜索时两个字段都会被检索。此外,我还加了一个简单的同义词映射表:当两个游戏名称被手动标记为"同一个游戏"时,系统会把这条映射关系记下来,之后搜索任一个名字都能找到对应的那条游戏记录。这个映射表是我用了一个多月后逐渐手动补全的,目前覆盖了几十个常见的一名多译案例。
4.3 实体盘数据录入:从扫码到手工
最开始我想得很美:手机扫一下实体盘背面的条形码,系统自动把游戏信息带出来。实际试了之后发现这条路也走不通。一是家用手头的数据库基本覆盖的是国外发行版,港版封面背面的条形码扫出来经常匹配到别的东西;二是本地 OCR 库对封面上的艺术字几乎无能为力,读取成功率太低。
试错三轮之后,我选择了一个"笨但稳"的办法:实体盘在录入面板里绑定一条数字版的数据。你只需要在添加许可证时选择已有游戏记录,如果库里还没有就先在表单里快速创建一条,再把实体盘信息挂上去。换句话说,实体盘和数字版共用同一条游戏记录,只是许可证类型不同。这个做法的额外收益是统计报表非常干净——同一款游戏的实体版和数字版不会被显示成两个游戏。
5. 实测效果:用了三个月之后的真实体感
5.1 数据完整度与维护成本
这个项目从上线到稳定使用,我大概用了三个月。第一期我先导入了全部会免和折扣数字版清单,大约120条游戏记录,然后又逐步补了30多条实体盘,现在库内稳定在150条记录左右。
维护成本比我预想的低很多。正常情况下,每周只需要花三五分钟:买了新游戏就录入,通关了就把状态改一下,顺手填一个时长。真正让我意外的是,这个"低维护成本"并不是一开始就有的,而是在我养成了几个固定习惯之后才出现的。比如我规定自己"拿到游戏当天必须入账",不然过两天一定会忘;再比如"通关后立即改状态,再顺手填小时数",整个操作不到十秒。
数据完整度方面,核心字段的覆盖率达到95%以上,只有封面和类型这类补充信息还有缺失。统计报表在数据完整的前提下,给出了不少让我惊讶的结论。
5.2 买游戏决策的变化
这个工具有没有一个直接改变我的地方?有,而且是发生在第四个月。
我翻看投入产出分析图的时候,发现有一款花了全价买的游戏,时长显示只有两小时。我当时买它是因为媒体评分高、题材我也喜欢,结果就是不对胃口。这张图上所有位于低性价比线的游戏,几乎都有一个共同特征:他们在购买时都处于"打折临时起意买"或者"跟风热门测评买"的状态。
后来我给自己立了一个规矩:凡是临时起意想买的游戏,先放进愿望单,记下看到的价格,强制过两周再决定。两周后如果还想要,再入手;如果已经忘了,说明它本来就不在我的真实需求里。AnyPS5 的愿望单模块其实就是一张"冷静期表格",配合价格折线图,我今年上半年乱买的游戏数量直接少了一大半。
6. 一些给后来者的建议
6.1 尽量早定"通关"的标准
如果你也想做类似的个人工具,我第一个建议是:尽早定义什么算"通关"。我的五态模型里,"已通关"和"白金"很容易混淆。一开始我以为白金肯定算通关,后来发现有些游戏白金难度极高,但主线剧情很短,一晚上就能通;有些游戏通关了但奖杯还差八成,你总不能说它没玩过。
我的做法是把两者的关系定义为并列而非包含:只要主线剧情走完就算"已通关";白金另算,是给愿意挑战的玩家的额外标签。这样统计"通关率"的时候不会因为白金而低估,统计"白金率"的时候也不会被普通通关污染。
6.2 本地优先不是保守,是省事
可能有人会觉得"本地部署、手动录入"这套方案太原始,远不如那些能自动同步的在线服务。但我想说,本地优先的第一优点其实不是隐私,而是省事——我不需要担心服务商关停,不需要研究接口变更,不需要被登录页和更新提醒骚扰,数据备份就是一个文件复制。对于一个要长期维护的个人工具,这个优点远比花哨的自动同步更珍贵。
6.3 可以扩展的方向
这个项目目前的边界很清楚:它只记录游戏和游玩数据,不做社区、不做社交、不做在线比对。但如果有人想基于同样的思路扩展,我觉得有几个方向值得尝试:给实体盘加一个"借出记录"模块;给游戏库生成一个纯静态的分享页面,方便在朋友圈晒战绩;加上一套简单的插件机制,让自定义报表不用改主程序。
我在写完这套系统之后最大的感受是:你以为你需要更多数据,其实你需要的是更好的记录习惯。AnyPS5 本质上没有帮我产生任何新数据,它只是把散落的记录收拢起来,让我能看到全貌。就冲这一点,我认为它是值得花时间做的项目。如果你也想给自己建一个类似的账本,别管技术栈是什么,先把"游戏"和"持有凭证"这两个表分开,剩下的都是细节。