☰
AnyPS5:用OCR和SQLite打造本地优先的PS5游戏数据管理工具
2026/10/8 20:35:06 网站建设 项目流程

PS5买回家三个月后,我发现自己陷入一种完全没预料到的状态:不是没游戏玩,而是被“管理”这件事逼疯了。数字版买了几十个、实体盘散在架子上,哪个游戏玩到哪一步、哪些白金了、哪些烂尾了,全凭记忆;存档只敢依赖PSN云端,会员一到期就心虚;想等打折入手的游戏,每天刷新商店页面也烦。于是我用业余时间写了一套本地工具,取名AnyPS5。这个名字没多玄乎,我的意思是“任何想整理PS5数据的时候,都有个顺手的东西能用”。

如果你也跟我一样,PS5的底座成了吃灰容器,游戏库越滚越大但越来越难找,存档和价格总得靠人肉记忆,那这篇文章应该对你有用。我会把这个项目的完整设计思路、技术选型、模块实现和踩坑过程全部拆开讲。它不涉及破解、不碰固件、不依赖任何私有接口,只是把一个普通玩家手里本就可以合法访问的数据,整理成自己能看懂的本地数据库。

1. 一开始为什么写AnyPS5:PS5玩家的零散需求清单

先说清楚,我不是专业的主机开发者,只是一个玩了十几年游戏、平时也写Python的普通玩家。买PS5之前我以为痛点只有一个:游戏贵。买之后才发现,真正的痛点是一堆琐碎需求没人管。

1.1 我遇到的四个高频场景

第一个场景是游戏库管理。数字版、会免、实体盘三种来源混在一起,主机自带的入库列表只能显示“已安装”和“已购买”,但没法回答一个问题:我到底有哪些游戏?哪个通关了,哪个烂尾了?我甚至出现过在商店里差点二次购买已拥有的游戏这种尴尬事。

第二个场景是存档备份。PS5把存档管理做得比PS4还保守,本地能直接导出的信息非常有限。我习惯把重要游戏通关前的存档单独备份到U盘,但经常忘记哪个游戏上次备份是什么时候。会员云存档虽然方便,但有一次续费空窗期让我意识到,把重要存档全押在订阅服务上不踏实。

第三个场景是价格监控。我想买的游戏通常有五六个,分布在港服、日服和欧美服,每个服打折时间不一样。手动刷新商店页面太原始,而现成的价格追踪网站要么延迟大半天,要么只覆盖少数区域商店。

第四个场景是外设兼容性记录。我手上有DS5手柄、旧DS4、方向盘、键鼠转换器,还有给娃用的副手柄。每台设备在不同游戏里的兼容表现不一样,比如某方向盘在GT7里是完美的,在《F1 23》里却按键错乱。这种记录用记事本太乱,放表格里又不方便查。

这四个需求单独看都不大,但凑在一起就是一团乱麻。我也试过用Excel硬扛,最后发现维护成本高到离谱。

1.2 市面已有工具的现状:数据都散着

其实市面上不是没有工具,问题在于它们是“碎片化”的。官方PS App能看在线状态、购买记录和奖杯,但没法本地存档备份提醒,也没法做价格历史追踪;第三方奖杯网站数据翔实,但只覆盖奖杯,不关心价格和外设;价格追踪APP又跟游戏库完全不打通。

我整理过一张对比表,越整理越觉得差距明显:

能力官方PS App第三方奖杯站价格追踪工具AnyPS5目标
游戏库全量管理部分部分否是
本地存档快照登记否否否是
多商店价格历史否否部分是
外设兼容记录否否否是
想看的维度自由组合否否否是

表格最能说明问题:我要的不是一个“奖杯神器”或者“打折雷达”,而是一份属于自己的、能互相联动的PS5数据底账。

1.3 工具定位:本地优先,一切数据归自己

想清楚需求之后,我给AnyPS5定了三条规则。第一,本地优先,所有数据放进本地SQLite库,不上传、不依赖云端账号;第二,不做破解扩展,不读取主机内部文件,只处理玩家自己通过U盘、截图、公开商店页面能拿到的信息;第三,模块插件化,游戏库、存档、价格、外设各自独立,互不影响。

这个定位让我写代码时非常舒服。我不需要跟任何私有协议搏斗,也不用担心工具被人拿去搞灰色用途。它本质上是一个“玩家自己的信息整理系统”,安全、干净、合法。

2. AnyPS5的总体设计与技术选型:一个本地优先的采集型工具集

定位清楚之后,剩下的就是搭骨架。AnyPS5不是一个单一功能的脚本,而是一个多模块的本地工具集,所以我花了比较多时间在项目结构上。

2.1 项目结构:单仓库多模块

项目叫AnyPS5,代码仓库里大概是这样的结构:

anyps5/ ├── app/ │ ├── main.py # FastAPI入口 │ ├── models.py # SQLAlchemy模型 │ ├── modules/ │ │ ├── library/ # 游戏库与奖杯截图OCR │ │ ├── backups/ # 存档备份登记 │ │ ├── prices/ # 商店价格监控 │ │ └── peripherals/ # 外设兼容记录 │ ├── static/ # 前端面板资源 │ └── templates/ # Jinja2页面 ├── scripts/ │ ├── ocr_pipeline.py # 截图OCR任务 │ ├── price_watch.py # 价格定时抓取 │ └── backup_reminder.py # 备份提醒 ├── data/ │ └── anyps5.db # SQLite数据库 └── requirements.txt

每个模块都可以单独跑:OCR管道是独立脚本,价格监控是独立定时任务,Web面板只是把结果统一展示出来。这样有一个明显好处:任何一个模块挂了,不影响另外几个。

2.2 技术栈选型:为什么是Python+FastAPI+SQLite

技术栈我几乎没有犹豫。Python处理OCR、爬虫、文本处理是舒适区;FastAPI用来起一个轻量Web面板很顺手;SQLite对于单用户的几千条记录完全够用,还不需要装数据库服务。

可能有人会问,为什么不用Electron做个桌面应用?我的理由是,Electron打包体积太重量级,而且我要的不是一个“软件”,是一个能随时加脚本的底座。做成Web面板的好处是,手机上通过局域网也能打开,PS5开着的时候顺手查一下价格、看备份提醒,体验很好。

SQLite在这个项目里其实被低估了,很多人觉得它只能做玩具,但搭配WAL模式和合理的表结构,完全能扛住价格表几万条记录。后面我会专门讲我踩过的锁库坑。

2.3 数据模型设计的核心:一切围绕“游戏”这个主语

整个数据集的核心是游戏表,其他模块都跟它关联。我建了几个主要表:

  • games:游戏基础信息(id, title, platform, source, status, first_seen_at)
  • save_backups:(id, game_id, backup_time, backup_path, note, md5)
  • price_records:(id, game_id, store, region, price, currency, fetched_at)
  • peripherals:(id, name, type, interface, compatibility, note, updated_at)

这样的好处是,价格记录可以追溯历史,存档快照可以关联到具体游戏,外设虽然跟游戏不是强关联,但我在表里加了一个“适用游戏列表”的字段,用JSON存。下面是建表的SQL简化版:

CREATE TABLE games ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, platform TEXT DEFAULT 'PS5', source TEXT DEFAULT 'digital', status TEXT DEFAULT 'backlog', first_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE save_backups ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL, backup_time TIMESTAMP NOT NULL, backup_path TEXT, note TEXT, md5 TEXT, FOREIGN KEY (game_id) REFERENCES games(id) );

所有数据的血缘都很清楚,后面做统计面板时可以直接JOIN,不用绕弯路。

3. 游戏库与奖杯数据整理:用OCR打通PS5截图这座孤岛

游戏库整理是整个项目里我最想说的模块,因为它的方案选择很能代表AnyPS5的思路:不硬啃私有接口,而是从玩家手头已有的截图上想办法。

3.1 核心思路:不依赖官方接口,从截图下手

PS5主机自带截图功能,奖杯解锁、游戏启动、游戏完成度这类画面,系统都会生成包含游戏名称、图标、进度信息的卡片式截图。把U盘插到PS5上,可以整批复制这些截图到电脑。这是索尼官方支持的导出方式,完全合法。

那有了截图之后怎么变成结构化数据?我的答案是OCR。用现成的开源OCR库把截图里的游戏名、奖杯类型、时间信息识别出来,再归一化进数据库。这比去逆向PSN接口成本低得多,而且不碰任何账号风险。

3.2 OCR管道的具体流程

整个管道分四步:截图拷贝、图片清洗、文本识别、结果归并。

截图拷贝没什么好说的。图片清洗我这里做了一件重要的事:先把截图统一缩放到合理分辨率,然后裁剪掉屏幕上下的无效区域,只保留中间的游戏信息卡片区域。这个裁剪规则我调了很久,不同版本的系统UI卡片位置略有差异,我干脆做了一个“版本号+坐标”的配置表。

识别部分我用的是PaddleOCR,因为中英文混合游戏名比较多,这一版引擎在中文识别上的表现比Tesseract好很多。核心代码并不复杂:

from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang='ch') def ocr_screenshot(img_path): result = ocr.ocr(str(img_path), cls=True) texts = [] for line in result: for item in line: texts.append(item[1][0]) return texts

识别出来的文本会进入一个归一化模块。这里的坑在于游戏名写法不统一:“God of War Ragnarok”可能被识别成“God of War Ragnarök”,日版游戏还带日文副标题。我在库里建了一张别名表,把“游戏规范全名”和“常见OCR变体/简称”映射起来,然后用编辑距离匹配兜底。

3.3 奖杯统计与游戏库标签体系

OCR识别出来的并不仅仅是游戏名。PS5的奖杯截图里通常包含“已获得奖杯数/总数”和奖杯等级信息。我把这些字段也提取出来,写成结构化的奖杯进度记录。

与此同时,我给游戏库设计了一套轻量标签体系:backlog(没开始)、playing(进行中)、completed(已通关)、platinum(已白金)、abandoned(烂尾)。每次OCR扫到某个游戏的新截图,就会自动更新它的状态。比如识别到奖杯进度100%,我会标记为platinum候选,再人工确认一次。

这个机制跑起来之后效果很直观。我第一次扫描自己U盘里的两千多张截图,居然识别出14个“我记忆中早通关但其实烂尾”的游戏,数据跟印象冲突的时候,感觉很有意思。

3.4 OCR落地时的头疼问题:误识别、时区和重复图片

误识别是OCR必然面对的问题。我遇到过把“FINAL FANTASY XVI”识别成“FINAL FANTASY XV1”的情况,还有把游戏内角色名的英文压到游戏名上面的问题。解决办法是,所有命中结果都先跟别名表比对,相似度低于阈值的进入“待确认队列”,不会直接污染主表。

第二个问题是时区。截图OCR识别出的是文本时间,但PS5系统时间是UTC还是北京时间取决于你的设置。我在导出截图时写了一个小脚本,优先读取图片的EXIF时间戳,而不是OCR文本里的时间,这样避免了反复调整配置。

第三个是重复图片。同一款游戏我可能截图几百次,如果不做去重,游戏库会变成灾难。我在管道的入口处先算每个图片的md5哈希,重复图片直接跳过。代价是哈希计算需要一点时间,但跟后续省下的手工清理成本比,完全值得。

问题我的最终方案
游戏名OCR变体别名表+编辑距离匹配
时间源不一致用EXIF时间戳,不用OCR文本
重复截图图片md5全局去重
不确定的记录进待确认队列,不自动入库

4. 存档备份提醒与本地快照管理:让数据安全变得可感知

存档备份这个模块,本质上不是“帮你备份”,而是“提醒你什么时候该备份,并记录你备份了什么”。原因很简单,PS5官方只允许通过系统设置的“已保存数据”把存档复制到U盘,任何绕过这个流程的第三方存档管理手段,都可能涉及越狱或私有协议,我不想碰。

4.1 云存档的隐患与本地备份的必要性

很多人觉得开两年PSN会员就能高枕无忧,但我算了笔账:会员到期后,云端存档只保留一段时间,超过期限可能被清掉;更麻烦的是,如果某天你在本地启动了一个旧版本游戏,PS5自动同步云存档时,可能把新进度覆盖掉。这种事情一旦发生,哭都来不及。

所以我坚持一个朴素的习惯:重要游戏通关前、更新大版本前、会员到期前,各做一次本地U盘备份。人脑记不住这些节点,于是AnyPS5接管了“记忆”的部分。

4.2 U盘备份流程与AnyPS5的登记机制

实际操作一点都不高科技:在PS5上进入“设定 > 已保存的数据和游戏/应用设定 > 已保存的数据(PS5) > 复制到USB驱动器”,选择游戏,复制。手动操作完毕之后,在AnyPS5的Web面板里点一下“登记备份”,填上游戏、时间、路径和备注,后台自动计算该游戏的备份数,并记录下来。

为了让登记成本降到最低,我在面板上预置了最近常玩的游戏列表,点两下就能完成登记。同时我会在这个模块里存一条md5,用来做备份文件的完整性校验。虽然U盘备份的文件结构没法直接逐文件比对,但我能确认每次备份的文件清单在体积和数量上是否合理,能提前发现某些游戏备份异常变小的情况。

这里有一个从实际操作中得来的经验:备份时建议把U盘插在主机背面接口,部分前接口在数据传输过程中受供电波动会影响写入。我遇到过两次备份完成后文件列表显示不全,后来固定使用后置接口,问题消失。

4.3 快照管理与恢复演练

只有备份没有恢复演练,等于白备份。我第一次做恢复测试是在某个游戏更新后突然无限报错,当时把U盘插回去,通过系统设置把存档复制回主机,问题立刻缓解。从那次起,我每两个月会挑一个不常玩的游戏做一次“备份-恢复-删除备份”演练,AnyPS5里记录每次演练的结果。

快照管理在数据库侧就是一套简单的版本列表:同一游戏的最新三条备份会被标记为“keep”,更早的可以选择清理。界面直接展示“当前备份数 / 最近备份时间 / 下次建议备份时间”,一眼看明白。

游戏最近备份备份数备注
Elden Ring2024-05-123通关前最终版
Baldur's Gate 32024-04-282二周目准备
GT72024-06-011版本更新前备份

这种不用猜的感觉,真的很踏实。

5. 游戏价格监控模块:自己写爬虫踩过的那些细节

价格监控可能是AnyPS5里最“现成工具很多”的领域,但我还是选择自己写,原因是现成工具都没法满足“多区域商店+历史价格趋势+自定义提醒阈值”这三个需求同时出现。

5.1 为什么不用现成价格站

市面上成熟的价格历史站,数据覆盖确实广,但很多站点更新不及时,特别是折扣刚开始的前半小时,页面价格已经变了,站内数据还停留在原价。而我想在打折开始当天就收到提醒,这个频率要求很多现成站做不到。

另外,我需要把价格跟自己的游戏库关联。一个游戏是否想买,取决于它是不是在我的“愿望单”里,而这个愿望单的数据其实已经躺在AnyPS5的游戏库里。自建模块可以直接做本地关联,不需要在两个平台之间来回导数据。

5.2 商店页面抓取与价格解析

我选用了各个商店的公开网页版价格页面作为数据源,因为它们不需要登录,价格字段也在页面里直接渲染。PS商店页面结构比较稳定,我用Playwright加载页面后等待特定价格节点出现,再提取文本并转成数字。

给一个简化版的抓取思路:

async def fetch_price(store_url): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() await page.goto(store_url, timeout=30000) await page.wait_for_selector(".price-display__price", timeout=10000) price_text = await page.inner_text(".price-display__price") price = parse_price(price_text) # 去掉货币符号和尾字 await browser.close() return price

这个流程看起来很顺,但真实环境比这残酷。不同区域商店的页面结构不完全一样,港服的价格节点样式跟欧美服不同;有的区域页面不展示折扣倒计时,只展示最终价。我的办法是把区域差异配置化,写了一个很长的区域配置表,每个区域对应一组选择器和货币单位。

5.3 定时任务的触发与“礼貌抓取”策略

价格监控跑的是每日定时任务,我会在上午和晚上各抓一次。为了避免给商店服务器压力,每个请求之间至少间隔5秒,并对同一个游戏的同一天抓取结果做了缓存:如果今天已经抓到价格,且页面没有明显的折扣标记变化,就直接复用昨天的记录,节省请求量。

这里踩过一个典型坑:商店有促销时,页面里可能出现“折扣价”和“原始价”两个数字,解析器如果只取第一个,可能误把原价当作当前价,导致提醒触发条件完全失效。我的解决方案是同时抓取两个字段,只有当折扣价字段存在时才认为是促销状态,否则一律视为原价。

另一个坑是货币单位。欧美服常用美元、欧元,港服是港币,日服是日元,我记录时全部保留原始币种,换算成自己熟悉的币种放在展示层做,避免汇率波动污染历史数据。

6. 外设兼容性清单与游玩习惯统计:从数据库到可用面板

外设和习惯统计这两个功能其实都不复杂,但它们是让AnyPS5从“工具集”变成“个人数据中心”的关键。一个只有价格和存档记录的工具,用起来冷冷清清的,加上外设和习惯数据之后,才真正有了“给我自己用”的感觉。

6.1 外设记录的字段设计

我手头的外设类型很杂,所以字段特意设计得宽松。核心字段包括:外设名称、类型、连接方式、固件版本、兼容状态、备注、关联游戏列表。

兼容状态我分三级:完美兼容、部分兼容、不兼容。部分兼容这一级最有价值,比如我的某第三方方向盘在GT7里力反馈正常,但在《WRC》里按键映射错乱,这些细节如果不记录,几个月后再次接上时又得重新试一遍。

我还给每个外设加了“最后验证日期”字段。有些外设的固件升级之后,兼容表现会变化。我会在每次验证后更新这个字段,面板上自动排序出“很久没验证的设备”,提醒我该插上试试了。

6.2 游玩习惯统计的维度

游玩习惯数据统计依赖于前面的OCR模块。每次扫描截图,我都会得到一张“什么时间、玩什么游戏、大概进度多少”的记录表。虽然它不是精确的分钟级统计,但足够回答这些粗粒度问题:我最近一个月玩得最多的类型是什么?哪个时间段最容易开始新游戏?我的烂尾率到底有多高?

我把这些维度做成几个简单聚合查询:

  • 按月份统计各游戏出现次数
  • 按游戏状态统计数量分布
  • 按游戏类型统计游玩时长占比(数据来自这个游戏的总截图数,而非真实时长)

必须承认,基于截图的统计无法做到100%准确,但它的优势在于完全离线、完全隐私,也不依赖任何官方数据接口。我宁愿查阅五天后修正的统计,也不愿上传数据换一个实时的报表。

6.3 Web面板的交互设计

面板我用的是FastAPI+Jinja2+Chart.js,没有引入太重的后台模板。首页是汇总卡片:游戏总数、玩过的比例、最近备份时间、今天的价格提醒数。第二页是游戏库表格,支持按状态、来源、标签筛选。第三页是价格趋势,点进某个游戏能看到历史价格曲线。第四页是外设清单,按兼容状态排序。

数据交互有一个需要注意的点:游戏名排序在SQLite里默认按ASCII,中文游戏名排序会乱。我最后在查询层用了自定义排序函数,按拼音首字母排序,显示效果立刻正常了。这种小细节很琐碎,但直接决定了面板到底好不好用。

7. 从开发到日常使用:AnyPS5实际跑了三个月后的心得

项目从开始到稳定运行,差不多花了三个月。前一个月在写OCR和价格模块,后两个月是持续调优。现在它已经成了我打开电脑后第一个启动的本地服务,这里说点真实的使用体会。

7.1 使用频率最高的功能

排第一的是备份提醒,不是价格监控。这其实超出我预期。价格监控虽然方便,但真正触发购买的次数并不多;反而是每月两次的备份提醒,帮我避免了好几次“更新版本前忘记备份”的险情。

排第二的是游戏库标签整理。因为OCR模块能自动把新截图识别进库,我不用手动维护,游戏库状态反而成了最精确的一份“游戏台账”。我甚至把PS Plus会免游戏单独打了标签,每年到期前能快速看到哪些会免游戏还没玩,优先补进度。

排第三才是价格监控。它的价值不在于省钱多少,而在于消除“怕买贵”的焦虑。看到某个游戏的历史价格曲线后,我自然就知道该在什么价位入手。

7.2 最大的意外收获:游戏习惯的可视化

我之前以为自己是个偏重单机RPG的玩家,统计结果打脸:动作冒险和竞速类占了近六成,RPG反而很少。这个发现让我开始认真思考自己真正喜欢什么类型的游戏,也直接影响了我后来的购买决策。

另外一个收获是,OCR扫描让我的截图库变得可搜索。之前拍了几千张截图,基本永远不会再去翻;现在它们被识别、归类、打上游戏标签,偶尔查一个奖杯截图,一秒钟就能找到。这个体验远好于在相册里手动翻页。

7.3 还想继续做下去的方向

AnyPS5目前的方案是单用户本地工具,我会继续迭代的方向有三个。第一是多人协作:家里有不止一台PS5的情况,把备份登记和游戏库同步起来;第二是更精细的OCR模型:针对PS5系统UI字体做微调,减少别名表维护量;第三是把提醒做成桌面通知和手机推送,不再依赖打开面板才能看到。

但说实话,写到最后我更想说的不是功能清单,而是一种感觉。PS5只是一台游戏机,但围绕它的数据、习惯和记忆,是我自己的数字生活的一部分。AnyPS5帮我做的,不过是把这一部分生活从混乱变清晰。每解决一个小需求,数据库里就多一张可靠的表,这种一点点把问题收进抽屉里的踏实感,可能才是这个项目真正让我上瘾的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询