Malody 数据工作台:围绕数据采集、查询、分析和运维的本地化工具集。
Malody Data Studio 是一个由 FastAPI、CLI 和 Tauri 桌面端组成的本地数据平台,提供对 Malody 游戏数据的结构化采集、访问和分析。该项目源自 malody_rankings 爬虫项目。
- 玩家数据查询 - 获取玩家排名、详细信息、个人资料(头衔、成就)、历史记录
- 谱面数据查询 - 谱面统计、热门谱面、创作者信息、稳定者分析、谱面详情
- 实时页面解析 - 解析 Malody 谱面页面,获取最新排行榜数据
- 高级查询系统 - 支持灵活的数据库查询和统计分析
- 数据可视化 - 趋势分析、模式比较、数据统计
- 爬虫控制 - 远程触发爬虫更新、查看爬虫进度
- RESTful API 设计,标准化 JSON 响应格式
- SQLite 数据库,支持复杂查询和数据分析
- 完整的错误处理和状态管理
- 请求频率限制和缓存机制
- 自动数据更新和爬虫集成
- Python 3.8+
- SQLite 3
- 克隆项目
git clone https://github.com/ChuanYuanNotBoat/malody-data-studio.git
cd malody-data-studio- 安装依赖
pip install -r requirements.txt-
准备数据库 确保
data/malody_rankings.db数据库文件存在。 -
启动服务
python run.py # 兼容入口
# 或
python app/main.py # 新入口服务将在 http://localhost:8000 启动,API 文档可在 http://localhost:8000/docs 查看。
从本地数据库按 charts.recommend_count 降序查询(同推荐数按 CID 升序):
python malody_stats.py stb_hot -1 recommend_count 20-1 表示所有模式,可替换为 0(Key)、3(Catch)等模式编号;最后的 20 为返回数量。
交互模式也可输入 stb_hot -1 recommend_count 20,支持现有选择器筛选,输出包含推荐数及已删除谱面标记。
CLI 优先使用 MALODY_DB_PATH,默认读取 data/malody_rankings.db,兼容项目根目录下的旧数据库路径。
爬虫确认歌曲 SID 返回不存在的 404 页面后,会将本地关联谱面的 server_exists 标为 0,
保留首次 deleted_at 和历史数据。未入库的 CID/SID 也会在 crawl_observations 的
availability 记录中保存失效状态。新官网 API 的失败响应需通过页面核实后才标记删除;
登录页、空白 404、访问拦截和服务异常保留重试。谱面重新采集成功后会恢复存在状态并清除删除时间。
新官网 API 不提供打赏数和独立游玩次数。采集器从谱面 HTML 补充打赏数,缺失字段保留已有值, 新记录的未知值保存为 NULL。CLI 将未知值及旧版采集器的游玩数占位值显示为“未知”。 修复推荐排行前 20 张谱面的计数(默认每两秒一次请求,写入前自动备份原值):
python scripts/refresh_chart_metrics.py --limit 20 # 预览
python scripts/refresh_chart_metrics.py --limit 20 --apply # 保存
python scripts/refresh_chart_metrics.py --cid 17302 --apply # 指定 CID该修复只更新官网明确给出的热度和打赏数,并将这些谱面未采集的游玩数占位 0 改为 NULL。 热度(Hot)不作为独立游玩次数。其余谱面的历史计数需要后续重新采集。
谱面详情 GET /charts/{cid} 包含 not_recommend_count(官方 dislike,未采集时为 null)。
评论明细包含 is_recommend 和 is_not_recommend;旧站 type=1 同时用于正、负投票,因此按去除 HTML 后的明确投票前缀区分方向。不推荐事件不计入普通评论数量,历史事件数量也不作为当前不推荐总数。
旧库可运行 python scripts/backfill_not_recommend.py --apply 增加字段并回填明确的历史投票分类;可加 --cids 17302 8438 按官方接口补齐指定谱面的当前不推荐数。写入前会在 logs/not_recommend_backups/ 保存旧值。其余谱面的总数由后续爬虫采集补齐,已有汇总推荐数仍需按来源另行核正。
GET /players/top- 获取顶级玩家排名GET /players/mm/stats- 获取 MM/MMR 统计概览(含 tracked players、快照去重指标、抓取状态)GET /players/{player_identifier}- 获取玩家基本信息GET /players/{identifier}/profile- 获取玩家详细资料(头像、头衔、成就、个人信息)GET /players/{player_name}/history- 获取玩家历史排名GET /players/search/{keyword}- 搜索玩家
GET /charts/stats- 获取谱面统计信息GET /charts/summary- 获取谱面综合统计摘要GET /charts/quality- 获取谱面数据质量检查结果GET /charts/hot- 获取热门谱面GET /charts/recent- 获取最近更新的谱面GET /charts/stable-creators- 获取 Stable 谱面创作者排行榜GET /charts/stabilizers/top- 获取稳定者排行榜GET /charts/creators/{creator_name}/details- 获取创作者详情及谱面列表GET /charts/creators/{creator_name}/trends- 获取创作者谱面更新趋势GET /charts/{cid}- 获取单个谱面的详细信息(包含歌曲数据)GET /charts/stabilizers/{player_name}/stats- 获取稳定者的统计信息GET /charts/stabilizers/{player_name}/charts- 获取稳定者审核的谱面列表GET /charts/search/{keyword}- 搜索谱面GET /charts/creators/search/{keyword}- 搜索创作者GET /charts/export/charts- 导出谱面数据为 CSV/XLSX 文件(支持筛选)
GET /page-parser/chart/{cid}- 解析谱面页面(包含谱面信息和排行榜)GET /page-parser/chart/{cid}/ranking- 仅获取谱面排行榜数据GET /page-parser/song/search- 搜索歌曲(从数据库)GET /page-parser/song/{sid}- 获取歌曲详细信息(歌曲 + 谱面 + 统计)
GET /analytics/player-trends- 分析玩家数据变化趋势GET /analytics/chart-trends- 获取谱面更新趋势GET /analytics/mode-comparison- 比较不同模式的谱面数据GET /analytics/player-compare- 比较多个玩家在时间窗口内的排名变化
POST /query/execute- 执行高级数据库查询GET /query/tables/{table_name}/schema- 获取表结构信息GET /query/database/stats- 获取数据库统计信息
POST /crawler/run- 启动爬虫(后台运行,支持类型:leaderboard, player, stb)GET /crawler/status- 获取各爬虫的进度状态
GET /system/health- 健康检查GET /system/database-info- 获取数据库信息POST /system/tasks- 统一任务入口(crawler / quality / db / plugin / query)GET /system/tasks- 统一任务列表GET /system/tasks/{task_id}- 统一任务详情GET /system/tasks/{task_id}/log- 统一任务 JSONL 事件日志(tail)GET /system/analysis-app/status- 分析程序路径与入口检测POST /system/analysis-app/launch- 启动外部分析程序(支持--api-base/--open-task-id)
所有 API 均返回统一格式的 JSON 响应:
{
"success": true,
"data": {...},
"message": "操作成功",
"error": null,
"timestamp": "2024-01-01T00:00:00Z"
}- 玩家对比:
GET /analytics/player-compare?players=Alice,Bob&mode=0&days=30- 谱面综合统计:
GET /charts/summary?mode=0&detail_level=detailed&time_range=90d- 谱面质量检查:
GET /charts/quality?mode=0- 稳定者排行榜(默认
limit=20,上限100):
GET /charts/stabilizers/top?mode=-1&limit=20- 歌曲详情:
GET /page-parser/song/12345?include_charts=true- 参数语义错误(400):
{
"detail": "period 仅支持 days 或 months"
}- 参数校验错误(422):
{
"detail": [
{
"type": "less_than_equal",
"loc": ["query", "mode"],
"msg": "Input should be less than or equal to 9"
}
]
}- 服务配置错误(500):
{
"detail": "爬虫脚本不存在: <absolute_script_path>"
}项目支持以下环境变量配置:
MALODY_DB_PATH- 数据库文件路径(默认:data/malody_rankings.db)MALODY_DATA_DIR- 运行时数据目录(默认:data/,影响 cookies/进度文件/xlsx 导出位置)MALODY_API_HOST- 服务绑定地址(默认:0.0.0.0)MALODY_API_PORT- 服务端口(默认:8000)MALODY_DEBUG- 调试模式(默认:false)MALODY_LOG_LEVEL- 日志级别(默认:info)
项目使用 SQLite 数据库,主要包含以下表:
player_rankings- 玩家排名数据player_identity- 玩家身份信息player_aliases- 玩家别名历史player_profiles- 玩家详细资料(头像、个人信息、游戏数据)player_titles- 玩家头衔player_achievements- 玩家成就charts- 谱面信息songs- 歌曲信息import_metadata- 导入元数据stb_crawler_state- 谱面爬虫状态
- malody_rankings - 原始数据爬虫项目
- Malody 官方网站 - 游戏官方网站
malody-data-studio/
├── run.py # 兼容入口(转发到 app/main.py)
├── app/ # FastAPI 服务
│ ├── main.py # 主启动文件
│ └── config.py # 服务配置
├── crawlers/ # 爬虫子系统
│ ├── rankings.py # 主爬虫(原 malody_rankings.py)
│ ├── player_profile.py # 玩家资料爬虫(原 player_profile_crawler.py)
│ ├── stb.py # STB 谱面爬虫(原 stb_crawler.py)
│ └── controller.py # 爬虫编排控制器(原 crawler_controller.py)
├── cli/ # CLI 共享模块
│ └── selector.py # 数据筛选器(原根目录 selector.py)
├── data/ # 运行时数据(不入库)
│ ├── malody_rankings.db # SQLite 数据库
│ ├── config.yaml # 爬虫配置
│ ├── cookies.local.json # 会话凭据
│ ├── mod_mapping.json # MOD 映射
│ └── *_progress.json # 爬虫进度
├── requirements.txt # Python 依赖
├── core/ # 核心模块
│ ├── database.py # 数据库连接
│ ├── models.py # 数据模型
│ └── services/ # 业务服务
├── routers/ # API 路由
│ ├── players.py # 玩家相关
│ ├── charts.py # 谱面相关
│ ├── analytics.py # 数据分析
│ ├── system.py # 系统管理
│ ├── query.py # 高级查询
│ ├── page_parser.py # 页面解析
│ └── crawler.py # 爬虫控制
└── utils/ # 工具类
├── query_builder.py # 查询构建器
├── selector.py # 数据选择器
└── crawler_manager.py # 爬虫管理
要添加新的 API 端点:
- 在
routers/目录创建新的路由文件 - 在
routers/__init__.py中注册路由 - 在
core/services/中实现业务逻辑 - 在
core/models.py中定义数据模型
本项目采用 MIT 许可证。详见 LICENSE 文件。
欢迎提交 Issue 和 Pull Request 来改进这个项目。
如有问题,请通过 GitHub Issues 提交。
To keep the project root cleaner while preserving current manual deployment behavior:
- Security guide moved to
docs/SECURITY_SETUP.md - Example config moved to
examples/config.yaml.example - Example cookies moved to
examples/cookies.local.example.json
Runtime files now live in data/:
data/config.yamldata/cookies.local.jsondata/malody_rankings.db
(Old root-level paths are still accepted as fallback for compatibility.)
Before merging changes, run:
powershell -ExecutionPolicy Bypass -File scripts/run_pre_merge_gate.ps1This enforces:
- unit tests
- compile check
- stats/api consistency gate
DoD and gate details:
docs/DELIVERY_GATES.md
Crawler repair details: implementation and validation. Full recrawls require an explicit human start; see the manual recrawl guide for isolated batches, progress, reports and guarded publication.
Quick checks:
GET /crawler/statusand inspectdata_source_health.- Confirm source-level status for:
homelatestapi
- Inspect failure category counters:
networkstructureauthunknown
If structure failures rise, prioritize parser compatibility validation.
- Python and dependencies installed.
data/malody_rankings.dbavailable.- Optional API key configured:
MALODY_API_KEYorMALODY_API_TOKEN
- Optional crawler/session files:
data/cookies.local.jsondata/config.yaml
- Optional scheduled consistency task:
powershell -ExecutionPolicy Bypass -File scripts/install_consistency_task.ps1
See:
docs/OPERATIONS_RUNBOOK.md
This includes:
- failure category handling
- rollback steps
- recovery verification flow