news 2026/9/9 6:24:43

用SQLite和Python打造Ave Mujica个人资料库

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用SQLite和Python打造Ave Mujica个人资料库

第一次接触 Ave Mujica 少女时代这类跨媒体企划时,最先留在记忆里的往往是舞台和音乐带来的冲击:舞台氛围很爽,音乐能力很强,成员互动很可爱。这些观感如果没有及时沉淀,几天后就会变成几条截图和一堆收藏夹链接,再过一段时间连“当时到底看了哪场演出、里面唱了哪首歌、互动片段出现在哪一集”都说不清楚。

真正适合一个信息密集企划的打开方式,不是靠记忆,而是先把信息对象拆出来,再决定用什么结构保存。后续你如果想继续了解角色、歌曲、现场演出、声优节目和粉丝二创,也不应该继续用聊天记录里的碎片信息硬撑。这篇文章会沿着一条工程化路径来走:先识别 Ave Mujica 这类企划里有哪些信息实体,再设计一套最小数据模型,然后用 Markdown 和 CSV 完成第一轮印象记录,最后用 Python 和 SQLite 把这些资料变成“能查询、能更新、能复用”的个人资料库。

1. 先理清这个企划里到底有哪些信息对象

1.1 一个舞台观感背后至少有四类信息

“好爽的舞台”“好震撼的音乐能力”“互动也好可爱”可以看作三类观感入口,但它们指向的信息对象完全不同。如果不做拆分,后面无论用表格还是数据库都会乱。

从一个观众视角出发,通常能看到四条信息线索:

信息线索典型内容保存价值
企划层作品名称、世界观、乐队构成、角色关系决定资料库需要哪些主表和关系表
演出层舞台演出、视觉设计、曲目顺序、现场气氛记录“哪一场演出”“什么效果”
音乐层歌曲、专辑、作词作曲、演唱者建立歌曲和演出之间的关联
互动层成员互动、声优节目、粉丝话题、CP简称标签化保存,便于后续检索

以项目标题中的观感为例。“好爽的舞台”属于演出层,“好震撼的音乐能力”更偏向音乐层和舞台表现,而“互动也好可爱”属于互动层。每一句话都可以成为资料库里的一个记录,但必须把它们落进不同字段,后面才能分别回答“我看过哪几场高质量舞台”“哪首歌最打动我”“哪些互动片段值得回看”。落进同一条文本笔记虽然省事,但一旦记录数量超过几十条,同类信息就很难横向对比了。

1.2 “弄李”这类CP昵称也是一种结构化信息

很多刚接触企划的用户会在讨论中看到“弄李更是kdl”这类表达。这里的“弄李”往往是粉丝群体中对角色、声优或人物关系的简称,“kdl”则是“磕到了”的首字母缩写。这类内容对你理解角色互动有帮助,但它本身并不适合直接作为正式字段写入数据库。

比较合适的做法是把它当成“标签”和“别名”来处理。标签系统可以独立于正文存在,也可以作为角色表、互动表中的独立字段。比如有一个互动事件,内容描述是成员在节目里分享了一段趣事,粉丝讨论中把它称为“弄李”,那么正式记录可以这样拆:

  • 事件标题:按官方节目名称或者视频标题记录
  • 事件类型:节目、直播、舞台MC、线下活动
  • 事件时间:以官方发布时间为准
  • 标签:不用强制解释,直接记录“弄李”“可爱”“互动”等关键词

这种处理方式的好处是:你不需要立刻搞清楚每一个昵称的来龙去脉,也可以先把资料记录下来。等到后续信息充足了,再通过别名表把“弄李”和具体角色对应起来。数据永远可以先保存后建模,但前提是原始记录本身没有被丢失。

1.3 信息对象之间不是孤立的,而是强关联的

一个典型的事实是:某场演出里演唱了某首歌,某首歌由某个角色或乐队背景支撑,某个互动事件发生在某个时间点,并关联到多个角色。如果只用一张大表,这些关系会在写入时被硬编码成一行文本,后面要统计“这个角色参与过多少次互动”“这首歌被演唱过几个现场版本”就会非常困难。

正确的做法是把“实体”和“关系”分开。实体包括角色、歌曲、演出、互动事件;关系包括演出和歌曲的对应关系、互动和角色的对应关系。这样设计的原因是数据变更成本低。比如一场演出结束后追加一首安可曲,只需要修改“演出-歌曲”关系表,而不是在一大段文字描述里搜索替换。

这也是为什么后面要设计实体表而不是直接创建文本笔记的原因。

2. 从收藏夹升级成一个小型数据模型

2.1 先设计六张核心表

在个人项目层面,不需要一开始就上重型系统,SQLite 足够用。先把六张核心表设计出来:

表名用途核心字段
characters角色与声优信息id、name_cn、name_jp、aliases、role_desc、team
songs歌曲信息id、title、original_title、type、release_date、status
performances演出和舞台信息id、title、date、venue、type、source_url
performance_songs演出与歌曲关系performance_id、song_id、order_no
interactions互动事件记录id、title、type、occurred_at、source_url、tags、note
interaction_characters互动与角色关系interaction_id、character_id、role_type

对于个人整理资料的需求,六张表已经是上限附近。如果继续膨胀会出现大量空字段,维护成本反而超过收益。初期可以只建前五张,互动角色关系可以先放在 interactions 的 note 或 tags 字段里,等数据量大了再拆分。

2.2 为什么要拆表,而不是一张大表

一个常见的反例是建一张all_data表,里面放标题、正文、时间和分类。初期录入很快,但当你要统计某个角色的全部互动时,就只能在正文里做模糊查询。模糊查询本身就容易漏数据,而且数据库无法理解“一条互动关联了多个角色”这种多对多关系。

拆表的根本原因是尊重数据关系。一场演出关联多首歌曲,一个角色参与多条互动,这种关系只有用一张独立的关系表才能表达清楚。同时,拆表之后单条记录会变得更短,更新某一个字段时不会影响其他字段。对于个人笔记库来说,这条原则同样适用,只是落地形式从数据库表变成了多个笔记文件。

2.3 用 SQLite 建表,字段命名一开始就要规范

下面是完整建表 SQL,适用于个人桌面端使用。字段使用小写下划线风格,主键使用自增整型,时间字段统一存成YYYY-MM-DDYYYY-MM-DD HH:MM:SS,避免格式混用。

PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS characters ( id INTEGER PRIMARY KEY AUTOINCREMENT, name_cn TEXT NOT NULL, name_jp TEXT DEFAULT '', aliases TEXT DEFAULT '', role_desc TEXT DEFAULT '', team TEXT DEFAULT '', created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS songs ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, original_title TEXT DEFAULT '', type TEXT DEFAULT 'single', release_date TEXT DEFAULT '', notes TEXT DEFAULT '', status TEXT DEFAULT 'active' ); CREATE TABLE IF NOT EXISTS performances ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, date TEXT DEFAULT '', venue TEXT DEFAULT '', type TEXT DEFAULT 'live', source_url TEXT DEFAULT '', notes TEXT DEFAULT '' ); CREATE TABLE IF NOT EXISTS performance_songs ( performance_id INTEGER NOT NULL, song_id INTEGER NOT NULL, order_no INTEGER DEFAULT 0, remark TEXT DEFAULT '', PRIMARY KEY (performance_id, song_id), FOREIGN KEY (performance_id) REFERENCES performances(id), FOREIGN KEY (song_id) REFERENCES songs(id) ); CREATE TABLE IF NOT EXISTS interactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, type TEXT DEFAULT 'program', occurred_at TEXT DEFAULT '', source_url TEXT DEFAULT '', tags TEXT DEFAULT '', note TEXT DEFAULT '' ); CREATE TABLE IF NOT EXISTS interaction_characters ( interaction_id INTEGER NOT NULL, character_id INTEGER NOT NULL, role_type TEXT DEFAULT 'participant', PRIMARY KEY (interaction_id, character_id), FOREIGN KEY (interaction_id) REFERENCES interactions(id), FOREIGN KEY (character_id) REFERENCES characters(id) );

这里要注意几个设计点。第一,characters.aliases使用逗号分隔的文本,比如弄李, 初华, cuidado,这种设计在初期可以接受,但后续如果需要按别名精确查询,就要拆成character_aliases子表。第二,performance_songs的主键是两列联合主键,避免同一场演出重复录入同一首歌。第三,所有外键都显式声明,这样即使只是个人项目,也能在删除前先检查关联数据,减少误删。

2.4 字段缺省值不是随便给的

很多人建表时不重视缺省值,结果后续程序里到处写空值判断。上面的 SQL 里,status默认active的意思是:只要一条记录没有标记废弃,查询时都应该被看到。type字段默认识别为single,但如果你的记录里主要都是现场版本,可以在导入时统一指定live

另一个容易踩坑的点是date字段。如果有的记录只有年份,有的精确到分钟,数据库里就会出现多种格式。统一用YYYY-MM-DD,分钟信息单独放到note里。对于个人资料库来说,控制字段取值范围比追求字段灵活更重要。

3. 用 Markdown 和 CSV 先把第一印象沉淀下来

3.1 先写印象笔记,再进结构化表

数据库表设计完成后,不要一上来就开 Python 脚本。信息整理最自然的路径是:先记录观感,再清洗,最后导入。第一轮记录建议用 Markdown,因为摩擦最小,也不需要打开任何数据库工具。

可以建立一个固定模板,每次观看完舞台、音乐视频或互动节目后,都按同样的格式记录。模板的价值是强制你区分信息类型,避免全部挤在一段感想里。

# 日期:2025-06-01 # 标题:某场舞台演出的观感记录 ## 舞台 - 整体氛围:舞台灯光、镜头语言、编舞和视觉效果 - 印象最深的一个瞬间: ## 音乐能力 - 听到的曲目: - 印象最深的一首歌: - 值得关注的演唱或编曲细节: ## 互动 - 互动片段概述: - 出现的角色或声优: - 粉丝讨论里的关键词: ## 待查 - 需要确认的歌曲版本: - 需要补充的角色关系: - 需要核对的演出时间:

这个模板对应了数据库中三个不同方向:舞台和音乐能力对应performancessongs,互动对应interactions,待查部分则提醒你还有哪些字段需要补全。笔记可以先写得口语化,后面导入数据库前再精简字段。别误以为笔记是最终产物,它是原始输入。

3.2 把笔记转成可导入的 CSV

当笔记数量达到十几条后,再靠人肉翻文档会很累。这时候把关键字段提取到 CSV,就完成了从自由文本到结构化数据的第一次转换。

characters.csv示例:

id,name_cn,name_jp,aliases,role_desc,team,status 1,示例角色名,サンプル,示例昵称,乐队成员,示例团队(以官方资料为准),active 2,互动对象名,,互动简称,节目嘉宾,,active

interactions.csv示例:

id,title,type,occurred_at,source_url,tags,note 1,某期节目中的互动片段,program,2025-05-20,https://example.com,弄李,粉丝讨论中常见该昵称 2,某场舞台MC,live,2025-06-01,,舞台互动,安可环节

CSV 里最容易被忽略的是来源信息与标签信息。source_url在以后核对事实时会非常有用,tags则是你未来做主题检索的重要入口。不要因为嫌麻烦就把这两个字段省掉。

3.3 用 Python 做去重和必填校验

CSV 文件是用文本编辑器或 Excel 生成的,很容易出现重复行、空字段、编码混乱和前后空格问题。直接把 CSV 导入数据库,产生的脏数据后期极难清理。因此导入前先跑一次清洗脚本,打印每一行的问题,再由你决定是否跳过。

import csv import os REQUIRED = ["id", "name", "category", "summary"] def load_csv(file_path): if not os.path.exists(file_path): print(f"[WARN] 文件不存在: {file_path}") return [] with open(file_path, encoding="utf-8-sig") as f: return list(csv.DictReader(f)) def clean_rows(rows): seen = set() result = [] for line_no, row in enumerate(rows, start=2): missing = [col for col in REQUIRED if not row.get(col, "").strip()] if missing: print(f"[ERROR] 第 {line_no} 行缺少字段 {missing}: {row}") continue key = (row["name"].strip().lower(), row["category"].strip()) if key in seen: print(f"[WARN] 第 {line_no} 行重复: {key}") continue seen.add(key) for col in row: row[col] = row[col].strip() result.append(row) return result

示例代码里的REQUIRED要根据实际表结构调整。真正的重点有两个:一是使用utf-8-sig编码读取,兼容 Excel 导出文件;二是用line_no记录问题行的真实位置,方便回到 CSV 修改。加[ERROR][WARN]前缀是为了后面接日志系统时能够直接过滤。

3.4 写入 SQLite 时保持批量事务

清洗完 CSV 之后,再写入 SQLite。写入时不要一条一条commit,而是先执行BEGIN,全部插入成功再COMMIT,任一环节失败则回滚。

import sqlite3 def import_rows(db_path, table, rows): conn = sqlite3.connect(db_path) try: conn.execute("BEGIN") if table == "characters": conn.executemany( """ INSERT OR IGNORE INTO characters (id, name_cn, name_jp, aliases, role_desc, team, status) VALUES (:id, :name, :name_jp, :aliases, :role_desc, :team, 'active') """, rows, ) else: print(f"[WARN] 尚未定义表 {table} 的导入逻辑") conn.commit() except Exception as exc: conn.rollback() print(f"[ERROR] 导入失败,已回滚: {exc}") finally: conn.close()

INSERT OR IGNORE依赖唯一约束。如果建表时没有为characters.id设置唯一约束,这个语句不会真正生效。所以在导入前先确认表结构已经把主键定义清楚。个人项目里,回滚是成本最低的错误处理方式;如果不在导入脚本里现在就支持回滚,以后数据量大了想补救就更难。

4. 查得动才算整理完:写几条必要查询

4.1 查询某场演出包含哪些歌曲

数据落库后,第一步验证的一定是“演出与歌曲”这条链路。用下面的 SQL 可以查某场演出的完整曲目单,order_no决定展示顺序:

SELECT p.title AS performance, s.title AS song, ps.order_no FROM performance_songs ps JOIN performances p ON ps.performance_id = p.id JOIN songs s ON ps.song_id = s.id WHERE p.id = ? ORDER BY ps.order_no;

你可能会疑惑,为什么不能直接存一份演出标题加歌曲列表的文本字段。原因在于一旦某首歌被多次演唱,歌曲资料就会被复制到多场演出记录里,后续修改歌曲的词曲信息时就要改多行。通过关系表查询,歌曲信息始终只存在songs表一份,其他表只保存“哪一场演出用了这首歌”。

4.2 查询角色参与了哪些互动事件

第二条必查查询是从角色出发,反向找到所有互动事件。因为一个互动可以关联多个角色,必须通过关系表interaction_characters来连接:

SELECT i.title AS interaction, i.type, i.occurred_at, GROUP_CONCAT(c.name_cn, ' / ') AS characters FROM interactions i LEFT JOIN interaction_characters ic ON i.id = ic.interaction_id LEFT JOIN characters c ON ic.character_id = c.id WHERE i.id = ? GROUP BY i.id ORDER BY i.occurred_at DESC;

这里使用LEFT JOIN是为了在互动事件还没有关联到任何一个角色时,也能保留事件本身。如果改用INNER JOIN,未关联角色的事件会直接被过滤掉,在个人资料整理前期很容易造成“资料看似存在但查无此项”的结果。

4.3 把观感片段按时间线倒序输出

资料库积累一段时间后,最常见的场景不是精确查询,而是“最近发生了什么”。这种场景不需要复杂过滤,直接按时间倒序取前五十条即可:

SELECT title, type, occurred_at FROM interactions WHERE occurred_at != '' ORDER BY occurred_at DESC LIMIT 50;

注意occurred_at如果不是统一格式,排序结果会不正确。所以前面才反复强调在录入时就固定成YYYY-MM-DD。时间字段一旦混入“5月”“2025.6.1”这类格式,数据库排序就会偏离直觉。

4.4 新增一场演出时,怎么改动最小

数据维护最常见的操作是新增一场演出的曲目。先插入演出主记录,拿到新的performance_id,再清掉旧的performance_songs,最后循环插入歌曲关系。这要求整批操作尽量在同一个事务里完成,避免演出主表更新成功、关系表更新失败导致不一致。

import sqlite3 def update_performance_songs(db_path, performance_id, song_ids): conn = sqlite3.connect(db_path) try: conn.execute("BEGIN") conn.execute("DELETE FROM performance_songs WHERE performance_id = ?", (performance_id,)) conn.executemany( "INSERT INTO performance_songs(performance_id, song_id, order_no) VALUES (?, ?, ?)", [(performance_id, sid, i) for i, sid in enumerate(song_ids, start=1)], ) conn.commit() print("[INFO] 演出曲目更新完成") except Exception as exc: conn.rollback() print(f"[ERROR] 更新失败,已回滚: {exc}") finally: conn.close()

更新逻辑里最危险的不是写错代码,而是忘记删除旧关系。如果歌曲 A 被移出演出,只执行新增操作而不清旧数据,那么旧的歌曲 A 记录还挂在演出下面,查询曲目单时会出现“幽灵歌曲”。凡是多对多关系更新,建议一律先删除后插入。

5. 常见问题和排查链路

个人资料库的规模不大,但问题特征和业务系统很相似。下面整理几个最常见的坑,以及对应的排查方式。

5.1 角色名字、昵称和CP简称不统一

问题现象常见原因检查方式处理建议
同一人物出现多行记录中英文名、日文名、粉丝简称混用查 characters 表里 aliases 字段增加别名表,或至少统一在 aliases 字段里用逗号分隔
互动事件查不到某角色事件里使用昵称“弄李”,角色表里没记录检索 interactions.note 和 tags 字段先把昵称作为标签,后续再映射到 characters

培养习惯:正式写入数据库前,先建立一份“常用别名对照表”,哪怕一开始只有两三条。很多数据质量问题都来自同一个角色在不同记录里被叫成不同名字,而解决方式不是强制大家改口,而是让数据层接受别名。

5.2 同一首歌在不同现场有多个版本

如果只建一张songs表,很容易把“专辑版”“现场版”“特别版”混在一起。解决思路是区分“歌曲原始作品”和“歌曲现场演绎”。原始作品作为songs表记录,现场曲目通过performance_songs关联,对齐到具体演出。

如果某首歌在三次演出中被唱过,数据库里songs表只存一行,而performance_songs表会有三行。这样做查询“这首歌唱过几次”就变得非常直接。如果录入了多首同名歌曲,要怀疑是否把不同版本错误插入了songs表。

5.3 互动事件时间线错位

互动事件记录时,最容易出现的问题是时间字段不统一。有人写“5月20日”,有人写“2025-6-1”,还有人写“最近”。这两种格式一旦混进同一张表,排序结果就会失真,而且很难自动修复。最好的方案是在 CSV 阶段就使用datetime校验脚本,不合法的时间直接拦截。

from datetime import datetime def is_valid_date(value): try: datetime.strptime(value.strip(), "%Y-%m-%d") return True except ValueError: return False

如果历史数据已经混入多种格式,不要想着靠 SQL 一次清洗干净。先从 CSV 源文件重新整理,再重新导入,避免在 SQL 上做复杂的字符串转换。

5.4 CSV 读取中文乱码或路径找不到

Excel 默认导出 CSV 时,中文编码可能是GBKGB18030,而 Python 的默认utf-8无法直接读取。读取时建议使用utf-8-sig,如果仍然乱码,再尝试gbk。代码里不要写死相对路径,最好基于脚本所在目录拼出绝对路径,否则在命令行和 IDE 中运行时容易出现“文件不存在”。

from pathlib import Path BASE_DIR = Path(__file__).resolve().parent CSV_PATH = BASE_DIR / "data" / "characters.csv"

5.5 导入脚本重复执行后出现重复数据

没有做幂等处理的导入脚本,重复执行一遍就会产生重复行。要解决这个问题,必须从三个层面下手。第一,建表时用主键和唯一索引;第二,清洗时先检查已有数据;第三,导入语句使用INSERT OR IGNORE或先查后插。

下面是一个稳健的幂等导入检查逻辑:

  1. 查询数据库已有记录的name集合。
  2. 在内存里构建一致性哈希集合。
  3. 发现重复时打印[SKIP],不中断程序。
  4. 全部处理完后输出导入统计。

5.6 一次完整的排查链路清单

遇到资料库问题,按下面顺序检查,能覆盖绝大多数个人项目场景:

  1. 原始 CSV 是否缺失关键列,编码是否为 UTF-8。
  2. 文件路径是否基于脚本所在目录拼接,而不是依赖当前工作目录。
  3. 表结构和 CSV 列名是否完全对应。
  4. 主键和唯一索引是否在导入前已经建立。
  5. 是否多次执行同一个导入脚本,导致重复数据。
  6. 查询 SQL 里的表名、字段名是否写错。
  7. 时间字段是否统一按YYYY-MM-DD存储。
  8. 关联表的外键 ID 是否真的存在于主表。
  9. 检查程序日志里是否有[ERROR][SKIP]输出。
  10. 对比官方资料源 URL,确认事实剪裁没有偏差。

6. 把资料库从个人笔记变成可持续维护的小项目

6.1 用目录和版本管理固定项目结构

资料库落到 SQLite 后,还要考虑长期维护。一个推荐的项目结构是同时管理 CSV、脚本和数据库:

ave-mujica-notes/ data/ csv/ characters.csv interactions.csv db/ ave_mujica.db scripts/ clean_csv.py import_data.py validate.py docs/ template.md changelog.md README.md

其中docs/template.md保存 Markdown 模板,docs/changelog.md记录每次数据变更是基于哪条来源,README.md说明所有脚本的运行方式。这个结构本身不需要依赖具体框架,只要一个 IDE 和 Git 就能维护。每次导入前先复制数据库文件或提交一次 Git,操作失败时就能快速回退。

6.2 新增或修改数据的标准流程

个人资料库也需要固定更新流程,否则今天改字段,明天改格式,后天就变成另一套体系。推荐的更新流程是:

  1. 先在docs/template.md中记录观感或信息片段。
  2. 对照官方资料源确认角色、歌曲、演出的名称和时间。
  3. 修改 CSV 文件。
  4. 运行清洗脚本,处理重复行和必填字段。
  5. 运行导入脚本写入 SQLite。
  6. 运行验证查询,确认新增记录可以被按时间、按角色、按标签检索出来。

这套流程和开发环境里的“编辑 -> 构建 -> 测试”非常相似。它的目的不是让个人追星过程变得繁琐,而是保证资料库长期可信。每一次修改都有迹可循,将来追溯某条记录是否出错时,不需要靠模糊记忆。

6.3 学习环境与生产环境的维护差异

个人资料库不需要高可用设计,但如果你决定长期依赖它,也需要区分使用场景。

场景特点建议做法
学习环境数据量小,丢失可接受直接使用 SQLite,临时脚本随便跑,反复导入没问题
长期个人使用数据量增长,误操作影响大加入 Git 版本管理,导入前备份数据库,脚本使用事务和回滚
多人协作或日后扩展需要权限控制和并发写考虑迁移到 PostgreSQL,但先确认是否真的需要多人频繁写入

一个常见误区是一开始就上重型数据库和服务端框架。其实对个人资料整理场景,SQLite 足够维持几千条记录。等到数据量超过预期,或者你确实需要多人共同维护时,再迁移到 PostgreSQL 也不迟。

6.4 维护时的可复用清单

每次更新资料库前,按下面清单检查一遍:

  • 数据来源是否可靠,有没有写进source_url
  • 日期是否统一为YYYY-MM-DD
  • 昵称和 CP 简称是否已经记录到别名或 tags 字段。
  • CSV 编码是否为utf-8-sig
  • 是否已经用 Git 或数据库备份做过快照。
  • 导入脚本是否使用事务,失败时能否回滚。
  • 关键验证查询是否已经存在,更新后能否立刻跑通。
  • 日志里是否残留[ERROR][WARN]

这条清单可以直接放在README.md顶部,每次操作前刷一遍。

6.5 扩展方向:给资料库加一个简单检索界面

当资料库里的记录超过几百条时,手动打开 SQLite 执行 SQL 会变得不够直观。下一步可以使用 FastAPI 或 Flask 包一层简单的 API,把“搜歌曲”“搜互动”“看某场演出曲目”变成 HTTP 接口。这个阶段再引入框架才是合适的,因为底层数据模型已经稳定,接口层只是把查询逻辑暴露给浏览器或移动端。

更轻量的选择是使用 SQLite FTS5 全文搜索,直接在interactions.notecharacters.aliases字段上建立索引。FTS5 在个人项目中非常实用,不需要引入额外服务,只需要建虚拟表并插入同步数据,就能支持快速关键词搜索。

但无论扩展方向是什么,有一点不会变:先有稳定的实体表,再有查询逻辑,最后才谈界面和框架。反过来做的话,界面和数据模型会永远互相迁就,越改越乱。

真正值得留意的经验是:不要让“第一次了解某个企划”的兴奋感只停留在收藏夹里。舞台、音乐和互动都是入口,把这些入口转化成可以查询和更新的结构化资料,才是让接触一个新世界变得可持续的方式。对新手来说,先从一张角色表、一张互动表和一份 Markdown 模板开始,远比一开始就追求完美的信息架构更重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 6:23:51

STM32H750+RT-Thread实战:从环境搭建到多线程应用完全指南

简介:正点原子STM32H750北极星开发板与RT-Thread 4.1.1结合的完整工程包,面向希望基于Cortex-M7高性能芯片开展嵌入式RTOS开发的工程师和院校学生。资源包含完整的源码、构建脚本及HAL库文件,共418个文件,以258个.h头文件和137个.…

作者头像 李华
网站建设 2026/9/9 6:23:17

S7-200 SMART恒压无负压供水系统:从硬件选型到调试全解析

1. 项目认知:管住压力,才是这套系统设计的主线 做供水控制不少年头了,经常有朋友或同行拿着一套“恒压供水(无负压供水)全套图纸程序”来找我,问的东西其实都差不多:这套程序能不能直接用&#…

作者头像 李华
网站建设 2026/9/9 6:22:44

硬盘物理销毁全指南:机械粉碎与高温熔炼怎么选?

硬盘数据物理销毁这件事,平时没人关注,真到要处理退役硬盘的时候才发现——删文件、格式化、快速分区,全都不顶用。2026年,单块机械硬盘动辄16TB起步,NVMe固态4TB、8TB也成了标配,数据密度越大,…

作者头像 李华
网站建设 2026/9/9 6:22:15

西门子PLC采购成本控制:穿透报价单的全周期TCO策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 6:20:35

Excel组合图实战:柱状图+折线图+双坐标轴,一张图搞定体量与趋势

这个技巧,我在给业务部门做月度经营分析时几乎每次都要用到。手头一份销售明细,既想对比每个区域的销售额,又想把同比增长率的变化趋势画在同一张图里,这时候Excel组合图就是最顺手的解法——柱状图负责展示绝对体量,折…

作者头像 李华
网站建设 2026/9/9 6:20:32

震级、b值与Python:从地震目录到Gutenberg-Richter分析的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华