ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python+SQLite构建本地资料库:从设计到查询导出全攻略

用Python+SQLite构建本地资料库:从设计到查询导出全攻略 如果你喜欢“超级机器人大战”系列一定有这样的经历机体档案、机师技能、参战作品、隐藏路线和攻略笔记分散在几十个网页、Excel 表格和聊天记录里。今天查装甲值要打开一个页面明天查机师 ACE 奖励又要翻好几层收藏夹非常零散。最近我尝试把这些资料整理到一个本地化的资料库工具里代号叫 ZOPZero Operation Planner零号作战规划助手。这篇文章会把整个搭建过程完整拆开从数据库设计、数据导入、命令行查询到报表导出全部给出可复现代码。不管你是机战系列爱好者还是想用 Python SQLite 做一个离线资料管理小工具都可以直接参考。1. 背景与核心概念1.1 机战系列资料整理为什么需要工具化《超级机器人大战》是万代南梦宫旗下知名的回合制策略角色扮演游戏系列从公开资料来看系列首作于 20 世纪 90 年代初登场核心特色是把不同机器人动画作品中的机体、机师和剧情放在同一个世界观里让玩家体验“跨作品共斗”的乐趣。这类游戏的数据天然具备以下特点数据量大参战作品多机师多机体形态多每一作都会追加新单位。字段维度多装甲、运动性、射程、武器威力、地形适应、机师技能、ACE 奖励、剧情分支条件等。版本差异明显同一台机体在不同作中的数值、可用改造段数、隐藏获得条件可能完全不同。资料分散官方设定集、攻略 wiki、论坛文章、视频攻略各说一部分很难统一维护。如果只靠人工复制粘贴时间一长就会出现版本混乱、数据缺失、重复记录等问题。与其依赖零散文档不如用一个本地 SQLite 数据库把“作品、机师、机体、章节事件”四类核心信息管理起来。ZOP 项目做的就是这个事。1.2 ZOP 项目的定位与功能边界ZOP 不是游戏本体也不是模拟器工具而是一个面向个人的离线资料整理与查询脚本。它只负责把结构化的机体、机师、作品、章节数据存进数据库并提供常用查询和导出能力方便你在查阅攻略、撰写评测、整理资料时快速定位信息。项目边界如下只处理本地文本和 JSON 数据不涉及游戏 ROM、镜像、启动器、破解相关内容。不提供联网自动抓取数据需要你手动整理成 JSON 格式后导入。示例数据全部使用占位设定不引用任何真实作品的具体数值仅用于演示数据结构。查询和导出都在本地命令行完成不需要额外安装数据库服务。这样既能保持工具轻量也避免了版权和数据来源方面的风险。如果你只是想做一个通用型“游戏资料管理助手”同样可以沿用这套结构。1.3 本文适合哪些读者这篇文章比较适合以下人群机战系列爱好者想建立自己的机体/机师资料库。Python 初学者想学习 SQLite 在真实项目中的基本用法。需要做离线数据整理但不想引入重型数据库中间件的开发者。想了解“脚本导入数据 命令行查询 CSV 导出”这种小工具套路的读者。读完本文后你会掌握SQLite 数据库表设计、Python 标准库连接 SQLite、JSON 数据的批量导入、复杂 SQL 查询、CSV 中文导出以及常见异常的处理思路。2. 环境准备与项目规划2.1 运行环境本文示例使用 Python 3依赖全部来自标准库不需要安装第三方包。开发环境建议如下操作系统Windows / macOS / Linux 均可。Python 版本3.8 及以上主要用到sqlite3、json、argparse、csv、pathlib。数据库SQLitePython 自带驱动无需单独安装。命令行工具Windows 建议使用 PowerShell 或 CMDmacOS/Linux 使用 Terminal。版本需要根据你的项目实际情况调整本文以常见环境为例重点演示配置思路。如果你使用的是更新的 Python 3.12代码仍然兼容。2.2 项目目录结构为了让项目便于维护我们先规划好目录结构srw_zop/ ├── data/ │ ├── works.json │ ├── pilots.json │ └── units.json ├── scripts/ │ ├── schema.sql │ ├── init_db.py │ ├── import_data.py │ ├── query_units.py │ └── export_report.py ├── db/ │ └── srw_zop.db └── README.md简单说明一下data目录存放手工整理的 JSON 原始数据。scripts目录存放数据库初始化和业务脚本。db目录存放生成的 SQLite 数据库文件程序会自动创建。采用这种“数据、脚本、产物”分离的结构后续如果要接入新的数据来源只需要替换data下的 JSON 文件不需要改动业务逻辑。2.3 技术选型说明很多人可能会问为什么不用 MySQL 或直接写 ExcelSQLite 的优势在于零配置、单文件、跨平台非常契合个人资料库场景。你不需要启动数据库服务也不用处理端口、账号、权限一个.db文件就包含全部数据备份时直接复制文件即可。JSON 则是最适合人工编写和小工具处理的数据格式。相比 Excel 二进制文件JSON 更适合做版本管理也方便在代码中做字段校验。至于为什么不引入爬虫是因为个人资料整理场景下手动维护一份精简 JSON 往往比爬取大量网页更稳定且不涉及站点访问规则风险。3. 数据库设计与核心概念3.1 表结构与关系ZOP 数据库设计四张核心表作品表works、机师表pilots、机体表units、章节事件表chapters。它们之间的关系可以理解为一部作品可以包含多位机师和多台机体。一台机体通常由一位机师驾驶但部分机体可能无人驾驶所以pilot_id允许为空。章节事件属于某一部作品记录该作品中的关键剧情节点或隐藏条件。字段设计尽量贴合机战资料整理习惯works表字段字段类型说明idINTEGER主键自增titleTEXT作品名称唯一release_yearINTEGER发售或播出年份media_typeTEXT媒体类型如动画、游戏、OVAstatusTEXT参战状态如已参战、传闻参战pilots表字段字段类型说明idINTEGER主键自增nameTEXT机师姓名work_idINTEGER所属作品外键skillTEXT通常带什么技能如底力、指挥官ace_bonusTEXTACE 奖励说明units表字段字段类型说明idINTEGER主键自增nameTEXT机体名称work_idINTEGER所属作品外键modelTEXT机体型号如 ZS-01categoryTEXT机体分类如近战、格斗、万能armorINTEGER装甲值weapon_powerINTEGER武器攻击力pilot_idINTEGER默认机师外键可空remarkTEXT备注隐藏条件可写在这里chapters表字段字段类型说明idINTEGER主键自增work_idINTEGER所属作品外键chapter_noTEXT章节编号titleTEXT章节标题condition_descTEXT进入或通关条件rewardTEXT奖励说明这种设计足够覆盖个人资料库的常见使用场景后续还可以按需要增加“武器表”或“剧情分支表”这里先保持适度精简。3.2 初始化数据库脚本在scripts/schema.sql中写入建表语句PRAGMA foreign_keys ON; CREATE TABLE IF NOT EXISTS works ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL UNIQUE, release_year INTEGER, media_type TEXT DEFAULT 动画, status TEXT DEFAULT 已参战 ); CREATE TABLE IF NOT EXISTS pilots ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, work_id INTEGER, skill TEXT, ace_bonus TEXT, FOREIGN KEY (work_id) REFERENCES works(id) ); CREATE TABLE IF NOT EXISTS units ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, work_id INTEGER, model TEXT, category TEXT, armor INTEGER DEFAULT 0, weapon_power INTEGER DEFAULT 0, pilot_id INTEGER, remark TEXT, FOREIGN KEY (work_id) REFERENCES works(id), FOREIGN KEY (pilot_id) REFERENCES pilots(id) ); CREATE TABLE IF NOT EXISTS chapters ( id INTEGER PRIMARY KEY AUTOINCREMENT, work_id INTEGER, chapter_no TEXT, title TEXT, condition_desc TEXT, reward TEXT, FOREIGN KEY (work_id) REFERENCES works(id) );这里有几个需要注意的地方PRAGMA foreign_keys ON在 SQLite 中默认是关闭的必须在每次连接中手动开启否则外键约束不会生效。使用AUTOINCREMENT会让自增主键不会复用已删除的编号适合资料库这种需要稳定编号的场景。units.pilot_id没有加NOT NULL因为部分支援机、战舰单位可能没有固定机师。然后编写init_db.pyimport sqlite3 from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent DB_PATH BASE_DIR / db / srw_zop.db SCHEMA_PATH BASE_DIR / scripts / schema.sql def init_db(): DB_PATH.parent.mkdir(parentsTrue, exist_okTrue) conn sqlite3.connect(DB_PATH) with open(SCHEMA_PATH, r, encodingutf-8) as f: conn.executescript(f.read()) conn.commit() conn.close() print(f数据库初始化完成{DB_PATH}) if __name__ __main__: init_db()这段代码会先创建db目录然后连接 SQLite 数据库文件最后执行schema.sql。如果数据库文件不存在SQLite 会自动创建如果已经存在CREATE TABLE IF NOT EXISTS也不会导致报错。运行初始化命令cd srw_zop python scripts/init_db.py预期输出数据库初始化完成...\db\srw_zop.db到这里数据库骨架已经搭好。4. 完整实战数据导入与查询4.1 准备演示数据为了演示数据导入过程我们准备三份 JSON 文件。这里使用的都是占位名称方便你理解结构后替换成自己的资料。data/works.json[ { title: 铁机甲兵队, release_year: 2018, media_type: TV动画, status: 已参战 }, { title: 圣剑战记, release_year: 2021, media_type: OVA, status: 已参战 } ]data/pilots.json[ { name: 赤木隼人, work_title: 铁机甲兵队, skill: 底力, ace_bonus: 气力130以上时伤害提升10% }, { name: 冰室千寻, work_title: 圣剑战记, skill: 指挥, ace_bonus: 周围2格内我方命中率提升 } ]data/units.json[ { name: 试作型突击机, work_title: 铁机甲兵队, model: ZS-01, category: 近战, armor: 1200, weapon_power: 3200, pilot_name: 赤木隼人, remark: 第3话前段击坠数达到10可提前解锁后续形态 }, { name: 圣剑昆古尼尔, work_title: 圣剑战记, model: SG-02, category: 格斗, armor: 900, weapon_power: 4800, pilot_name: 冰室千寻, remark: } ]需要注意这里我在 JSON 中存储的是work_title和pilot_name而不是数据库自增 ID。这样编写 JSON 时更直观即使不知道数据库里的具体主键也能理解数据含义。真正写入数据库时代码会通过名称反查 ID完成外键关联。这种“名称引用”的方案比直接写死 ID 更容易维护。一旦某条作品记录被删除重建旧的自增 ID 发生变化名称关联仍然能正确对应。4.2 编写数据导入模块在scripts/import_data.py中实现完整的导入逻辑import argparse import json import sqlite3 from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent DATA_DIR BASE_DIR / data DB_PATH BASE_DIR / db / srw_zop.db def read_json(filename): with open(DATA_DIR / filename, r, encodingutf-8) as f: return json.load(f) def get_work_id(cur, title): cur.execute(SELECT id FROM works WHERE title ?, (title,)) row cur.fetchone() if row is None: raise ValueError(f作品不存在{title}) return row[0] def get_pilot_id(cur, name): if not name: return None cur.execute(SELECT id FROM pilots WHERE name ?, (name,)) row cur.fetchone() return row[0] if row else None def clean_tables(cur): cur.execute(DELETE FROM units) cur.execute(DELETE FROM pilots) cur.execute(DELETE FROM works) cur.execute(DELETE FROM sqlite_sequence) def import_data(cleanFalse): works read_json(works.json) pilots read_json(pilots.json) units read_json(units.json) conn sqlite3.connect(DB_PATH) conn.execute(PRAGMA foreign_keys ON) cur conn.cursor() try: if clean: clean_tables(cur) for w in works: cur.execute( INSERT INTO works(title, release_year, media_type, status) VALUES(?, ?, ?, ?), ( w[title], w.get(release_year), w.get(media_type, 动画), w.get(status, 已参战), ), ) for p in pilots: work_id get_work_id(cur, p[work_title]) cur.execute( INSERT INTO pilots(name, work_id, skill, ace_bonus) VALUES(?, ?, ?, ?), (p[name], work_id, p.get(skill), p.get(ace_bonus)), ) for u in units: work_id get_work_id(cur, u[work_title]) pilot_id get_pilot_id(cur, u.get(pilot_name)) cur.execute( INSERT INTO units(name, work_id, model, category, armor, weapon_power, pilot_id, remark) VALUES(?, ?, ?, ?, ?, ?, ?, ?), ( u[name], work_id, u.get(model), u.get(category), u.get(armor, 0), u.get(weapon_power, 0), pilot_id, u.get(remark, ), ), ) conn.commit() print( f导入完成{len(works)} 部作品 f{len(pilots)} 位机师{len(units)} 台机体 ) except Exception as e: conn.rollback() print(f导入失败已回滚{e}) raise finally: conn.close() if __name__ __main__: parser argparse.ArgumentParser(description导入机战资料 JSON 数据) parser.add_argument(--clean, actionstore_true, help先清空所有表再导入) args parser.parse_args() import_data(cleanargs.clean)这段代码有几个设计细节值得展开说明先把 JSON 文件全部读入内存再统一写入数据库。个人资料库的数据量通常很小不会产生内存压力但这样做的好处是可以在操作前发现 JSON 格式错误避免“数据库写了一半后面突然报错”的尴尬情况。写入时没有手动管理事务而是依赖 SQLite 的默认行为并在每次循环结束时统一commit。一旦任何一条数据插入失败就执行rollback让数据库恢复到导入前的状态。比如works表存在唯一约束如果重复导入同一部作品就会触发异常并回滚不会留下半截脏数据。通过名称反查外键 ID 时如果作品名称不存在会抛出带中文提示的异常方便定位是哪一行数据写错了。运行导入命令python scripts/import_data.py --clean预期输出导入完成2 部作品2 位机师2 台机体4.3 编写查询模块数据导入只是第一步日常使用中更重要的是快速查询。在scripts/query_units.py中实现按作品、关键词、最低装甲值过滤的功能import argparse import sqlite3 from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent DB_PATH BASE_DIR / db / srw_zop.db def query_units(work_nameNone, keywordNone, min_armorNone, limit20): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row cur conn.cursor() sql SELECT u.id, u.name AS unit_name, w.title AS work_title, u.category, u.armor, u.weapon_power, p.name AS pilot_name FROM units u LEFT JOIN works w ON u.work_id w.id LEFT JOIN pilots p ON u.pilot_id p.id WHERE 1 1 params [] if work_name: sql AND w.title ? params.append(work_name) if keyword: sql AND (u.name LIKE ? OR u.model LIKE ?) params.append(f%{keyword}%) params.append(f%{keyword}%) if min_armor is not None: sql AND u.armor ? params.append(min_armor) sql ORDER BY u.armor DESC LIMIT ? params.append(limit) rows cur.execute(sql, params).fetchall() conn.close() if not rows: print(没有匹配的机体记录。) return print(f{ID:4}{机体名称:14}{作品:12}{分类:6}{装甲:6}{武器:6}{机师:8}) print(- * 60) for row in rows: print( f{row[id]:4}{row[unit_name]:14}{row[work_title]:12} f{row[category]:6}{row[armor]:6}{row[weapon_power]:6} f{row[pilot_name] if row[pilot_name] else 无:8} ) if __name__ __main__: parser argparse.ArgumentParser(description查询机体资料) parser.add_argument(--work-name, help按作品名称过滤) parser.add_argument(--keyword, help按机体名称或型号模糊匹配) parser.add_argument(--min-armor, typeint, help最低装甲值) parser.add_argument(--limit, typeint, default20, help最多显示条数默认20) args parser.parse_args() query_units( work_nameargs.work_name, keywordargs.keyword, min_armorargs.min_armor, limitargs.limit, )这个模块的核心是“拼接 SQL 时只追加参数占位符不直接把用户输入拼进 SQL 字符串”。这是防御 SQL 注入的常见做法虽然本地资料库风险不高但养成习惯很重要。运行效果演示python scripts/query_units.py --min-armor 1000输出ID 机体名称 作品 分类 装甲 武器 机师 ------------------------------------------------------------ 1 试作型突击机 铁机甲兵队 近战 1200 3200 赤木隼人再试试按关键词查询python scripts/query_units.py --keyword 圣剑输出ID 机体名称 作品 分类 装甲 武器 机师 ------------------------------------------------------------ 2 圣剑昆古尼尔 圣剑战记 格斗 900 4800 冰室千寻需要注意的是终端里的中文对齐效果在不同平台上可能略有差异。Windows 控制台默认使用中文字符宽度简单format对齐在部分英文字体下会显得参差不齐。如果追求更严格的对齐效果可以后续引入tabulate或pandas这里先用标准库实现保持零依赖。4.4 编写报表导出模块资料整理工具的另一个重要功能是导出报表。在scripts/export_report.py中实现 CSV 导出import argparse import csv import sqlite3 from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent DB_PATH BASE_DIR / db / srw_zop.db def export_csv(output): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row cur conn.cursor() rows cur.execute( SELECT u.name AS unit_name, w.title AS work_title, u.category, u.armor, u.weapon_power, p.name AS pilot_name FROM units u LEFT JOIN works w ON u.work_id w.id LEFT JOIN pilots p ON u.pilot_id p.id ORDER BY w.title, u.armor DESC ).fetchall() conn.close() with open(output, w, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerow([机体名称, 所属作品, 分类, 装甲, 武器威力, 机师]) for row in rows: writer.writerow( [ row[unit_name], row[work_title], row[category], row[armor], row[weapon_power], row[pilot_name] if row[pilot_name] else 无, ] ) print(f导出完成{output}) if __name__ __main__: parser argparse.ArgumentParser(description导出机体资料报表) parser.add_argument( --output, defaultsrw_zop_units.csv, help输出 CSV 文件路径, ) args parser.parse_args() export_csv(args.output)这里有两个容易踩坑的细节写入 CSV 时使用encodingutf-8-sig而不是普通的utf-8。因为 Excel 在打开 UTF-8 编码的 CSV 时如果没有 BOM 头很容易把中文识别成乱码。utf-8-sig会自动写入 BOM兼容性更好。newline是 Python 官方文档推荐的 CSV 写入方式可以避免 Windows 下出现多余空行。运行导出命令python scripts/export_report.py --output zop_units.csv预期输出导出完成zop_units.csv用文本编辑器或 Excel 打开zop_units.csv可以看到表头和两条机体数据。4.5 运行与验证总结到这里一个最小可用的资料管理闭环已经完成手工整理 JSON - 初始化数据库 - 导入数据 - 命令行查询 - 导出 CSV实际操作顺序如下cd srw_zop python scripts/init_db.py python scripts/import_data.py --clean python scripts/query_units.py --keyword 试作 python scripts/export_report.py如果每一步都正常执行说明数据库结构、JSON 数据、导入逻辑、查询逻辑都处于一致状态。接下来就可以开始把自己的真实资料整理成 JSON替换掉演示数据。5. 常见问题与排查思路在实际运行过程中新手可能会遇到下面这些问题我把常见现象、原因和解决方案整理成了表格问题现象常见原因解决思路运行init_db.py后提示目录不存在项目目录结构不完整或BASE_DIR计算错误检查是否从srw_zop根目录运行确认scripts与db的相对位置导入 JSON 时提示JSONDecodeErrorJSON 文件里有多余逗号、单引号或注释用支持 JSON 语法检查的编辑器打开逐个修正格式提示作品不存在xxxpilots.json或units.json里的work_title与works.json不完全一致对比三份文件中的作品名称注意全角/半角空格重复导入时报UNIQUE constraint failed未使用--clean而works.title有唯一约束首次建库后使用--clean重导或先查询确认是否已经存在查询结果为空数据未导入成功或过滤条件过于严格先去掉过滤条件查询全部数据确认是否有记录导出 CSV 在 Excel 中打开乱码写入编码不是utf-8-sig修改为encodingutf-8-sig后重新导出外键约束看似不生效连接时没有执行PRAGMA foreign_keys ON每次连接后执行开关或封装统一的连接函数数据库提示database is locked多个连接同时写同一数据库文件关闭多余的 Python 进程或终端窗口避免并发写入排查这些问题的通用顺序是先确认数据文件格式再确认数据库表结构最后打印 SQL 和参数看是否匹配。个人工具出现问题时90% 都是 JSON 字段名写错或数据重复不要一开始就怀疑是 SQLite 的问题。6. 工程实践与扩展建议6.1 数据处理层面的建议在实际项目中我建议你在 JSON 数据中增加一个source字段用来记录这条资料的来源比如“官方设定集”“某期杂志”“个人实测截图”。这样后续发现数据有误时能快速回溯来源。另外每次导入数据前最好先做去重。上面的--clean是“全量重建”思路适合数据量小、结构稳定的场景。如果数据量变大或需要保留手工修正记录更合适的做法是导入时先按“作品名称 机体名称”查数据库如果存在则更新字段不存在则插入。这种“upsert”逻辑可以避免重复记录。6.2 数据备份与版本管理SQLite 单文件备份非常方便直接把db/srw_zop.db复制走就行。为了避免数据丢失建议结合定时任务每天备份一次或至少在每次批量导入前先手动复制一份。同时data目录下的 JSON 文件建议用 Git 管理。JSON 是纯文本格式Git 可以清楚地展示每次改动差异方便回滚错误修改。数据库文件本身不适合纳入 Git因为它是二进制文件每次变化都会产生大量冲突。6.3 安全与合规边界这个工具运行在本地不涉及网络请求安全性已经很高。但如果后续你想扩展成 Web 服务请注意以下几点不要把数据库文件直接暴露到公网。如果增加用户登录功能密码必须使用哈希存储不能明文保存。如果增加联网抓取功能需要遵守目标站点的服务条款和robots.txt约定只抓取允许公开的数据。不要把这个工具用于游戏账号作弊、绕过数据保护或任何违反平台规则的操作。技术本身是中性的但使用边界需要自己把握好。6.4 功能扩展方向当前 ZOP 只是一个命令行工具已经满足了“查资料”的核心需求。如果还想继续完善可以从下面几个方向入手增加本地 Web 页面用 Flask 提供简单页面按作品浏览机体点击机体查看详细字段。增加 Excel 导出安装pandas和openpyxl后把查询结果直接导出为.xlsx文件。增加攻略笔记功能新增notes表与works或units关联记录个人打法和隐藏条件。增加对比功能针对同一台机体在不同作品中的数值做对比这在分析参战作品平衡性时很有用。增加标签系统为机师和机体增加tags字段比如“超级系”“真实系”“支援型”方便按玩法风格检索。扩展时要注意保持现有接口稳定。比如query_units这个函数目前接收多个命名参数后续即使增加新筛选条件也不影响已写好的调用代码。7. 总结与下一步这篇文章以“超级机器人大战”资料整理为背景完成了一个代号 ZOP 的本地资料管理工具。它的核心价值在于把零散的机体、机师、作品、章节信息统一收进 SQLite 数据库并提供统一的导入、查询、导出流程避免反复查阅碎片化资料。整个项目用到的东西并不复杂Python 标准库 SQLite JSON CSV。难度适中很适合作为学习 Python 文件处理、数据库操作和命令行工具开发的练手项目。你可以在本文代码基础上把演示数据替换成自己关心的内容甚至可以迁移到其他任何需要离线资料管理的领域。下一步建议先把项目跑通然后尝试修改schema.sql增加一个你需要的字段比如“地形适应”或“特殊技能”再相应调整import_data.py和query_units.py。改动一圈之后你会更清楚每个模块之间是如何协作的。资料整理工具的价值正是在一次次使用和调整中被慢慢打磨出来的。
返回列表