feat(p0): add favorite folder sync and sqlite data foundation
Implement P0 of the reaction dashboard: config loading with hot reload, SQLite migrations with backups, single-writer queue, read-only Bilibili fav client, folder import/diff sync, invalid-resource handling, blacklist seed, scheduler, and JSON redacted logging. Includes offline unit and transport-level contract tests; all existing and new tests pass.
This commit is contained in:
commit
e2cdf997af
39 files changed
+8575
No files matched your search
+595
@@ -0,0 +1,595 @@
|
||||
# 咻咻满 Reaction 视频数据看板开发文档
|
||||
|
||||
> **状态:** P0(数据底座与收藏夹同步)已实施并通过只读联调;P1 待开始
|
||||
>
|
||||
> **技术基线:** Python 3.14、Python 标准库、SQLite、原生 HTML/CSS/JavaScript、`uv`
|
||||
>
|
||||
> **本地开发:** 不依赖 Docker;使用 `uv` 创建和管理隔离环境。
|
||||
|
||||
---
|
||||
|
||||
## 1. 技术选型
|
||||
|
||||
| 范围 | 选型 | 说明 |
|
||||
|---|---|---|
|
||||
| 开发语言 | Python 3.14 | 与现有 `favsort.py` 一致,复用 Cookie、扫码授权与收藏夹排序能力。 |
|
||||
| 包与环境管理 | `uv` | 管理 Python 版本、`.venv`、依赖同步及统一命令。 |
|
||||
| 后端服务 | Python 标准库 `ThreadingHTTPServer` | 个人单用户看板,接口与并发规模有限,不引入 Web 框架。 |
|
||||
| HTTP 客户端 | `urllib.request` | 现有 B 站请求代码已使用;无需额外依赖。 |
|
||||
| 数据库 | SQLite(WAL) | 单用户、单机、低并发写入;备份和迁移成本低。 |
|
||||
| 调度 | `threading`、`queue.Queue`、标准库时间工具 | 单进程内运行发现、同步、快照和清理任务。 |
|
||||
| 前端 | 原生 HTML/CSS/JavaScript | 无 Node.js、无构建链;前端仅渲染 `/api/*` JSON。 |
|
||||
| 图表 | 仓库内置 ECharts | 离线静态资源,Apache-2.0 许可。 |
|
||||
| 通知 | Telegram Bot long polling | 具备按钮回调能力,开发和本机运行不需要公网 URL。 |
|
||||
| 容器化 | 可选的后续部署方式 | 本地开发、调试和日常运行均不要求 Docker。 |
|
||||
|
||||
### 1.1 选型原则
|
||||
|
||||
1. **复用现有能力。** 当前仓库的 `favsort.py` 已实现 B 站设备指纹 Cookie、扫码登录、收藏夹读取和排序逻辑;新功能应拆分复用,而不是另起技术栈。
|
||||
2. **保持单体。** 当前规模约数百到数千视频,每日采集量约千级请求;单进程、SQLite 和内置调度足够。
|
||||
3. **尽量标准库。** 当前没有第三方 Python 依赖,第一期不因便利性引入 Web 框架、ORM、任务队列或 Redis。
|
||||
4. **本地优先。** 可在 Windows 上通过 `uv` 直接运行,不要求 WSL、Docker、数据库服务或宿主机 cron。
|
||||
5. **未来可部署。** 服务保持无状态代码与可挂载数据目录分离,未来可选用 Docker 与 Caddy/Nginx 部署,但不是开发前置条件。
|
||||
|
||||
---
|
||||
|
||||
## 2. 使用 uv 的本地开发环境
|
||||
|
||||
### 2.1 前置条件
|
||||
|
||||
- 安装 [uv](https://docs.astral.sh/uv/);
|
||||
- 网络联通以便 `uv` 首次下载 Python 3.14;
|
||||
- Windows PowerShell、Windows Terminal 或任意可运行 Python 的终端;
|
||||
- 不需要安装 Docker、Node.js、npm、SQLite 命令行工具或 PostgreSQL。
|
||||
|
||||
项目通过根目录的 `.python-version` 固定使用 Python 3.14。`uv` 会在需要时自动安装相应解释器,并在仓库根目录创建 `.venv`。
|
||||
|
||||
### 2.2 初始化与同步
|
||||
|
||||
在项目根目录执行:
|
||||
|
||||
```powershell
|
||||
uv sync --all-groups
|
||||
```
|
||||
|
||||
该命令会:
|
||||
|
||||
1. 读取 `pyproject.toml`;
|
||||
2. 确保 Python 3.14 可用;
|
||||
3. 创建或更新 `.venv`;
|
||||
4. 安装项目本身及后续声明的依赖;
|
||||
5. 根据 `uv.lock` 锁定可复现的环境。
|
||||
|
||||
目前项目功能仅依赖 Python 标准库,因此首次同步不会安装额外运行时包。这是预期行为,不表示 `uv` 没有生效。
|
||||
|
||||
### 2.3 常用命令
|
||||
|
||||
```powershell
|
||||
# 查看 Python 与 uv 管理的虚拟环境是否可用
|
||||
uv run python --version
|
||||
|
||||
# 查看现有收藏夹工具帮助
|
||||
uv run bili-fav help
|
||||
|
||||
# 扫码登录并原子更新 config.json
|
||||
uv run bili-fav auth
|
||||
|
||||
# 检查现有 B 站登录状态
|
||||
uv run bili-fav auth --check
|
||||
|
||||
# 执行当前无网络依赖的测试
|
||||
uv run python tests/test_auth.py
|
||||
uv run python tests/test_moves.py
|
||||
```
|
||||
|
||||
未来看板服务实现后,统一使用:
|
||||
|
||||
```powershell
|
||||
# 建库、迁移与黑名单种子(离线,P0)
|
||||
uv run python app.py init
|
||||
|
||||
# 收藏夹只读同步/首次导入(P0)
|
||||
uv run python app.py sync
|
||||
|
||||
# 按配置周期常驻同步(P0);--once 只跑一次
|
||||
uv run python app.py run
|
||||
uv run python app.py run --once
|
||||
|
||||
# 查看数据库状态(离线,P0)
|
||||
uv run python app.py status
|
||||
|
||||
# P2 起启动本地看板服务
|
||||
uv run python app.py
|
||||
uv run python app.py --config .\config.json
|
||||
```
|
||||
|
||||
不建议直接激活 `.venv`;优先使用 `uv run ...`,这样命令总是运行在项目锁定的解释器和环境中。
|
||||
|
||||
### 2.4 依赖变更规范
|
||||
|
||||
若后续确实需要新增依赖,应使用 `uv` 修改项目元数据,而不是直接使用 `pip install`:
|
||||
|
||||
```powershell
|
||||
# 新增运行依赖(仅在标准库无法合理满足需求时)
|
||||
uv add <package>
|
||||
|
||||
# 新增仅用于开发的依赖
|
||||
uv add --dev <package>
|
||||
|
||||
# 同步锁文件和环境
|
||||
uv sync --all-groups
|
||||
```
|
||||
|
||||
提交依赖变更时,必须同时提交:
|
||||
|
||||
- `pyproject.toml`
|
||||
- `uv.lock`
|
||||
|
||||
不得提交:
|
||||
|
||||
- `.venv/`
|
||||
- `config.json`
|
||||
- `config.json.bak`
|
||||
- `data/` 中的运行期数据库、日志、计划和备份。
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体架构
|
||||
|
||||
```text
|
||||
浏览器 / 手机 Telegram 私聊
|
||||
│ │
|
||||
├──────────── HTTP / JSON ─────────────┤
|
||||
│ │
|
||||
┌─────▼──────────────────────────────────────▼─────┐
|
||||
│ 单一 Python 进程 │
|
||||
│ │
|
||||
│ Web 线程:ThreadingHTTPServer :8080 │
|
||||
│ 调度线程:发现 / 收藏夹同步 / 快照 / 清理 │
|
||||
│ Telegram:getUpdates long polling │
|
||||
│ 写入线程:SQLite 单写队列 │
|
||||
│ │
|
||||
│ BilibiliClient ── B 站 Web API │
|
||||
│ SQLite (WAL) ── ./data/app.db 或配置的数据目录 │
|
||||
└────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.1 并发与事务原则
|
||||
|
||||
- Web 请求、调度任务和 Telegram 长轮询可以并发;
|
||||
- 所有 SQLite 写操作经单写队列串行执行,避免 `database is locked`;
|
||||
- 读操作使用短生命周期连接;
|
||||
- 网络请求期间不得持有数据库写事务;
|
||||
- 收藏操作通过 `fav_action` 状态锁保证幂等;
|
||||
- SQLite 启用 WAL、外键和合理的 busy timeout。
|
||||
|
||||
```sql
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
PRAGMA busy_timeout = 5000;
|
||||
PRAGMA synchronous = NORMAL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 推荐代码结构
|
||||
|
||||
```text
|
||||
.
|
||||
├── app.py # 看板服务入口(P0 起新增)
|
||||
├── favsort.py # 现有收藏夹排序与授权工具
|
||||
├── pyproject.toml # uv / Python 项目元数据
|
||||
├── uv.lock # uv 生成并提交的锁文件
|
||||
├── .python-version # 固定 Python 3.14
|
||||
├── config.json # 运行期私密配置,不提交
|
||||
├── config.example.json # 脱敏配置样例
|
||||
├── DEVELOPMENT.md
|
||||
│
|
||||
├── reaction_board/
|
||||
│ ├── config.py # 配置、ENV 覆盖、mtime 热加载
|
||||
│ ├── database.py # SQLite 连接、迁移、单写队列
|
||||
│ ├── migrations.py # schema_version 与迁移
|
||||
│ ├── scheduler.py # 内置任务调度
|
||||
│ ├── logging_json.py # 脱敏 JSON 日志
|
||||
│ ├── bilibili/
|
||||
│ │ ├── client.py # HTTP、WBI、重试与限速
|
||||
│ │ ├── auth.py # 从 favsort 抽取的扫码授权能力
|
||||
│ │ └── favorite.py # 加入收藏夹与排序定位
|
||||
│ ├── services/
|
||||
│ │ ├── discovery.py
|
||||
│ │ ├── folder_sync.py
|
||||
│ │ ├── snapshot.py
|
||||
│ │ ├── fans.py
|
||||
│ │ ├── candidates.py
|
||||
│ │ ├── rankings.py
|
||||
│ │ └── maintenance.py
|
||||
│ ├── notify/telegram.py
|
||||
│ └── web/
|
||||
│ ├── server.py
|
||||
│ ├── api.py
|
||||
│ └── static/
|
||||
│ ├── index.html
|
||||
│ ├── app.js
|
||||
│ ├── styles.css
|
||||
│ └── vendor/echarts.min.js
|
||||
│
|
||||
├── tests/
|
||||
└── data/ # 本地运行期数据,默认不提交
|
||||
```
|
||||
|
||||
`favsort.py` 继续保持可独立运行。P0 实施时优先抽取其通用的 Cookie、请求头、扫码授权和收藏夹 API 逻辑;不要直接复制出第二套不一致的身份与请求实现。
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置与凭据
|
||||
|
||||
### 5.1 运行期配置
|
||||
|
||||
所有凭据和可变业务参数放在 `config.json` 中。文件必须被 Git 忽略,日志中不得输出其敏感值。
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"bilibili": {
|
||||
"mid": 28802351,
|
||||
"csrf": "<bili_jct>",
|
||||
"cookie": "SESSDATA=...; bili_jct=...; buvid3=..."
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"bot_token": "123456:ABC",
|
||||
"chat_id": 123456789
|
||||
},
|
||||
"web": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 8080,
|
||||
"auth_token": "replace-with-a-long-random-token",
|
||||
"public_base_url": ""
|
||||
},
|
||||
"storage": {
|
||||
"database_path": "data/app.db",
|
||||
"backup_dir": "data/backup",
|
||||
"backup_keep_days": 14
|
||||
},
|
||||
"folders": [
|
||||
{ "year": 2025, "media_id": 4451053851, "title": "咻咻满-reaction-2025" },
|
||||
{ "year": 2026, "media_id": 3700023351, "title": "咻咻满 reaction 合集" }
|
||||
],
|
||||
"track_fans": [37754047],
|
||||
"up_blacklist_seed": [261052419, 1908537643, 1701121088, 542262],
|
||||
"schedule": {
|
||||
"discover_minutes": 60,
|
||||
"folder_sync_minutes": 60,
|
||||
"full_folder_refresh_hours": 6,
|
||||
"snapshot_at": "00:01",
|
||||
"candidate_ttl_days": 7
|
||||
},
|
||||
"notify": { "min_score": 2, "max_items_per_message": 10 }
|
||||
}
|
||||
```
|
||||
|
||||
- `folders` 中的年度夹由人工创建,系统只读;
|
||||
- 兼容现有 `favsort.py` 的扁平 `mid` / `csrf` / `cookie` 写法;存在 `bilibili` 嵌套对象时以嵌套值为准;
|
||||
- P0 已实现的命令行:`python app.py init`、`python app.py sync [--full]`、`python app.py status`。
|
||||
|
||||
### 5.2 安全规则- Windows 本地开发时应限制 `config.json` 的文件访问权限;部署到 Linux 时权限必须为 `600`;
|
||||
- 配置文件每 60 秒检查一次 mtime,收藏夹、分类规则和权重变更后无需重启;
|
||||
- Cookie 或 CSRF 改变时重建 B 站请求客户端;
|
||||
- Telegram 操作必须同时验证 `from.id` 和 `chat.id` 是否为配置的私聊用户;
|
||||
- 仅允许环境变量临时覆盖敏感配置,不能将真实凭据硬编码进源代码;
|
||||
- `GET` 路由绝不进行收藏、忽略、拉黑等写操作。
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据模型
|
||||
|
||||
### 6.1 核心状态
|
||||
|
||||
| 表 | 状态 | 含义 | 是否参与每日快照 |
|
||||
|---|---|---|---|
|
||||
| `video` | `active` | 在库追踪的视频 | 是 |
|
||||
| `video` | `unfaved` | 已从收藏夹移除,但保留历史追踪 | 是 |
|
||||
| `video` | `removed` | 视频失效、删除或不可访问 | 否 |
|
||||
| `video` | `dropped` | 在看板中手动移出库 | 否 |
|
||||
| `candidate` | `NULL` | 待人工决策 | 否 |
|
||||
| `candidate` | `rejected` | 已忽略 | 否 |
|
||||
| `candidate` | `blacklisted` | 所属 UP 已拉黑 | 否 |
|
||||
|
||||
### 6.2 建表原则
|
||||
|
||||
最终 DDL 由迁移模块维护,关键约束如下:
|
||||
|
||||
- `snapshot` 主键为 `(stat_date, aid)`,保证同日幂等;
|
||||
- `up_stat` 主键为 `(stat_date, mid)`;
|
||||
- `folder_resource(media_id, aid)` 记录每个收藏夹实际拥有的资源,不能仅依赖 `video.folder_id`;
|
||||
- `video.folder_id` 表示视频的主归属夹,用于新视频同 UP 归属和排序;
|
||||
- `up_blacklist` 只在发现任务中生效,收藏夹同步不得用它过滤既有视频;
|
||||
- `seen_aid` 永久保留搜索去重标记,候选过期删除后也不重复通知;
|
||||
- `fav_action` 保存 Telegram 或签名链接的动作状态,防止重复点击造成重复写入。
|
||||
|
||||
### 6.3 迁移规范
|
||||
|
||||
- 使用 `schema_version(version, applied_at)`;
|
||||
- 每个版本迁移必须包在单一 SQLite 事务;
|
||||
- 迁移只能向前追加,不能用“删除数据库重建”替代升级;
|
||||
- 开发和正式运行产生的数据均保留;
|
||||
- 迁移前自动创建 SQLite 备份,失败时回滚事务并保留原库。
|
||||
|
||||
---
|
||||
|
||||
## 7. B 站接口与任务设计
|
||||
|
||||
### 7.1 接口职责
|
||||
|
||||
| 能力 | 接口 | 用途 |
|
||||
|---|---|---|
|
||||
| 发现 | `x/web-interface/wbi/search/type` | 每小时搜索候选 |
|
||||
| 搜索降级 | `x/web-interface/search/type` | WBI 搜索失败时使用 |
|
||||
| 收藏夹 ID | `x/v3/fav/resource/ids` | 计算新增和移除差集 |
|
||||
| 收藏夹详情 | `x/v3/fav/resource/infos` | 元数据同步、失效识别 |
|
||||
| 视频统计 | `x/web-interface/view?aid=` | 日更七项指标的唯一事实来源 |
|
||||
| 粉丝数 | `x/relation/stat?vmid=` | 每日粉丝快照 |
|
||||
| 收藏夹列表 | `x/v3/fav/folder/created/list-all` | 容量监控 |
|
||||
| 加入收藏 | `x/v3/fav/resource/deal` | 确认候选后收藏 |
|
||||
| 收藏夹排序 | `x/v3/fav/resource/sort` | 定位至同 UP 分组 |
|
||||
|
||||
### 7.2 发现任务
|
||||
|
||||
每小时扫描两个关键词、每个关键词前两页:
|
||||
|
||||
```text
|
||||
关键词:咻咻满 reaction、咻咻满 充电
|
||||
排序:pubdate
|
||||
页面:1、2
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
1. 已在 `video` 或 `seen_aid` 的 aid 跳过;
|
||||
2. 发布时间超过 14 天的结果跳过;
|
||||
3. 标题不含“咻咻满”跳过;
|
||||
4. 命中 `up_blacklist` 的 UP 跳过,不写候选池;
|
||||
5. 通过分类规则计算分数;
|
||||
6. 分数低于零丢弃;
|
||||
7. 其余写入候选池;达到通知阈值的候选发送 Telegram 消息;
|
||||
8. 请求之间随机等待 1.5~2.5 秒。
|
||||
|
||||
分类规则 v1:
|
||||
|
||||
```text
|
||||
+2:标题含 reaction / re / 反应 / 看 / 听 / 初听 / 首次 / 点评 / 锐评 / 路人 / 震惊 / 双人
|
||||
+2:标题含 充电 / 加急 / 专场
|
||||
+2:时长大于 60 分钟
|
||||
+1:时长大于 30 分钟且不超过 60 分钟
|
||||
-5:标题含 歌切 / 切片 / 纯享 / 搬运 / 完整版
|
||||
-3:UP 为咻咻满本人
|
||||
|
||||
score >= 2:发送通知
|
||||
0 <= score < 2:仅候选池展示
|
||||
score < 0:丢弃
|
||||
```
|
||||
|
||||
### 7.3 收藏夹同步
|
||||
|
||||
每小时对所有已配置年度夹执行:
|
||||
|
||||
1. 请求 `resource/ids` 获取远端 aid 集合;
|
||||
2. 与 `folder_resource` 的有效本地集合做差集;
|
||||
3. 对新增 aid 每 200 条请求 `resource/infos` 并入库;
|
||||
4. 对移除 aid 标记对应 `folder_resource.active=0`;若视频不在任何配置夹中,视频状态改为 `unfaved`;
|
||||
5. 每 6 小时全量刷新 infos,刷新元数据、`attr` 和收藏夹容量;
|
||||
6. `attr=9` 或请求集合中静默缺失的资源改为 `removed`;
|
||||
7. 容量达到 950 条时发送预警;系统绝不自动换夹。
|
||||
|
||||
`infos` 只能作为元数据和失效同步来源。它缺少点赞、投币、转发,且评论字段不可靠,不能替代 `view` 日更。
|
||||
|
||||
### 7.4 每日快照与粉丝采集
|
||||
|
||||
每天 `00:01`(`Asia/Shanghai`)执行:
|
||||
|
||||
- 对 `status IN ('active', 'unfaved')` 的视频逐条请求 `view?aid=`;
|
||||
- 成功写入播放、点赞、收藏、投币、转发、评论、弹幕七项数据;
|
||||
- `-404` 时标记 `removed`;
|
||||
- 其他错误最多重试 3 次,仍失败则写入 `ok=0` 与错误信息,不写伪造的零值;
|
||||
- 每条视频请求之间随机等待 1.0~1.6 秒;
|
||||
- 对 `track_fans` 中的每个 mid 请求一次 `relation/stat` 并写入 `up_stat`;
|
||||
- 任务结束向 Telegram 发送成功数、失败数、移除数和总耗时摘要。
|
||||
|
||||
服务启动后若当天快照尚未成功完成,应补跑当日任务;已成功的 `(stat_date, aid)` 不重复请求。
|
||||
|
||||
---
|
||||
|
||||
## 8. Telegram 与收藏操作
|
||||
|
||||
### 8.1 通知动作
|
||||
|
||||
候选通知提供:
|
||||
|
||||
- **收藏**:加入目标收藏夹并入库;
|
||||
- **忽略**:候选标记 `rejected`,不再通知;
|
||||
- **拉黑**:写入 `up_blacklist`,同时拒绝该 UP 的其他待决策候选;
|
||||
- **打开视频**:外链到 B 站视频页。
|
||||
|
||||
拉黑只影响未来搜索发现;不清理、不停止追踪已有视频,也不影响从收藏夹同步进来的视频。
|
||||
|
||||
### 8.2 幂等与失败处理
|
||||
|
||||
Telegram `callback_data` 只包含短操作 ID,例如 `fav:<id>`;真实 aid、目标夹、过期时间和状态保存在 `fav_action` 表。
|
||||
|
||||
收藏流程:
|
||||
|
||||
1. 将动作从 `pending` 原子切换为 `processing`;
|
||||
2. 先读取目标夹 ids,若视频已存在则按成功处理;
|
||||
3. 按“同 UP 最早视频所在夹优先,否则当年夹”的规则选择目标夹;
|
||||
4. 调用 `deal` 加入收藏;
|
||||
5. 调用 `sort` 将视频放进同 UP 连续分组;
|
||||
6. 回读 ids 校验最终位置;
|
||||
7. 成功:动作标记 `done`,视频标记 `active`,删除候选;
|
||||
8. 失败:动作标记 `failed`,保留候选与原始错误,允许用户重试。
|
||||
|
||||
GET 页面或链接预览只能展示信息,绝不能触发任何写操作。
|
||||
|
||||
---
|
||||
|
||||
## 9. Web API 与页面
|
||||
|
||||
### 9.1 API 原则
|
||||
|
||||
- `/api/health` 可匿名,仅返回健康状态;
|
||||
- 其他 `/api/*` 默认要求 `Authorization: Bearer <token>`;
|
||||
- 写操作额外要求 CSRF 防护与 Origin/Referer 校验;
|
||||
- API 返回 JSON;前端不复制热度、状态机或收藏夹归属判断;
|
||||
- CSV 导出由后端根据当前筛选条件生成。
|
||||
|
||||
核心接口:
|
||||
|
||||
| 方法 | 路径 | 用途 |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/overview` | 总览指标、容量、采集状态 |
|
||||
| `GET` | `/api/videos` | 视频分页、筛选和排序 |
|
||||
| `GET` | `/api/videos/{aid}` | 视频详情 |
|
||||
| `GET` | `/api/videos/{aid}/trend` | 七项趋势数据 |
|
||||
| `GET` | `/api/rankings` | 热度、增长、互动率与单项榜 |
|
||||
| `GET` | `/api/ups` | UP 聚合榜单 |
|
||||
| `GET` | `/api/fans` | 粉丝曲线与增量 |
|
||||
| `GET` | `/api/candidates` | 候选池 |
|
||||
| `GET` | `/api/system/status` | 最近任务、下次任务、错误日志 |
|
||||
| `POST` | `/api/candidates/{aid}/favorite` | 收藏候选 |
|
||||
| `POST` | `/api/candidates/{aid}/ignore` | 忽略候选 |
|
||||
| `POST` | `/api/candidates/{aid}/blacklist` | 拉黑候选所属 UP |
|
||||
| `DELETE` | `/api/blacklist/{mid}` | 移除黑名单 |
|
||||
| `POST` | `/api/videos/{aid}/drop` | 手动停止追踪 |
|
||||
|
||||
### 9.2 页面
|
||||
|
||||
| 页面 | 内容 |
|
||||
|---|---|
|
||||
| 总览 | 库内数、UP 数、今日新增、增量、容量条、采集成功率、故障提示。 |
|
||||
| 榜单 | 综合热度、七项单项、增长、增速、互动率、UP 聚合、筛选和 CSV。 |
|
||||
| 视频详情 | 元数据、七项趋势、快照表、失败标记、同 UP 对比。 |
|
||||
| UP 页 | UP 的全部视频、聚合趋势和平均互动率。 |
|
||||
| 粉丝页 | 粉丝总数、日增、7 日净增、累计增长;不叠加视频数据。 |
|
||||
| 候选池 | 待确认候选、规则命中、剩余保留时间和三个操作。 |
|
||||
| 系统状态 | 最近扫描、同步、快照、下一次运行时间和错误。 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 热度与趋势口径
|
||||
|
||||
### 10.1 综合热度
|
||||
|
||||
对每项最新有效快照值计算:
|
||||
|
||||
```text
|
||||
x_i = log1p(metric_i)
|
||||
z_i = (x_i - median(x)) / (IQR(x) / 1.349)
|
||||
score = Σ weight_i * z_i
|
||||
```
|
||||
|
||||
默认权重:
|
||||
|
||||
```text
|
||||
view 0.30, like 0.20, coin 0.20, favorite 0.15,
|
||||
share 0.10, reply 0.03, danmaku 0.02
|
||||
```
|
||||
|
||||
权重从配置读取,改动后查询时重算,不存冗余结果。
|
||||
|
||||
### 10.2 增长与互动率
|
||||
|
||||
```text
|
||||
d7 = (view_today - view_previous) / actual_day_gap
|
||||
r7 = (view_today - view_previous) / max(view_previous, 1)
|
||||
interaction_rate = (like + coin + favorite) / max(view, 1)
|
||||
```
|
||||
|
||||
- 查询前一有效快照而不是填补缺失日期;
|
||||
- 缺失日期在图表中断开;
|
||||
- 增速榜要求播放基数至少 200;
|
||||
- 互动率榜要求播放至少 100;
|
||||
- 样本不足两天的视频明确标注,不伪造增长数据。
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试与质量要求
|
||||
|
||||
在引入第三方测试框架前,测试继续使用可直接运行的 Python 脚本:
|
||||
|
||||
```powershell
|
||||
uv run python tests/test_auth.py
|
||||
uv run python tests/test_moves.py
|
||||
```
|
||||
|
||||
新增模块至少覆盖:
|
||||
|
||||
- 标题归一化、时长解析、分类打分;
|
||||
- 黑名单只影响发现;
|
||||
- `infos` 静默缺失资源的识别;
|
||||
- `active`、`unfaved`、`removed`、`dropped` 状态迁移;
|
||||
- 数据库迁移与幂等写入;
|
||||
- 每日快照缺失处理;
|
||||
- 热度、增长和互动率计算;
|
||||
- 收藏夹目标选择与排序锚点;
|
||||
- `fav_action` 的重复点击、失败重试和过期处理;
|
||||
- Telegram 用户与 chat ID 授权校验。
|
||||
|
||||
只有当标准库测试维护成本明显过高时,才考虑通过 `uv add --dev pytest` 引入 pytest。
|
||||
|
||||
---
|
||||
|
||||
## 12. 实施阶段
|
||||
|
||||
### P0:数据底座与收藏夹同步 ✅
|
||||
|
||||
- SQLite 迁移、配置加载、B 站只读客户端;
|
||||
- 收藏夹首次导入与每小时差集同步;
|
||||
- 全量 infos 元数据刷新、失效识别、黑名单种子;
|
||||
- 在 Windows + `uv` 环境完成只读联调。
|
||||
|
||||
完成情况(2026-10-31):
|
||||
|
||||
- 已实现 `reaction_board/`:配置热加载、单写队列、`schema_version` 迁移与升级前备份;
|
||||
- 已实现内置调度器,按 `schedule.folder_sync_minutes` 周期运行收藏夹同步;
|
||||
- 已实现 `fav/resource/ids`、`fav/resource/infos`、`fav/folder/created/list-all` 只读客户端;
|
||||
- 首次导入两个年度夹共 831 条资源:795 `active`、36 `removed`(20 条 `attr=9` + 16 条 `infos` 静默缺失),与探测结果一致;
|
||||
- 增量同步在无变化时只发 2 次 `ids` 请求,不请求 `infos`;
|
||||
- 黑名单 9 个 mid 已写入 `up_blacklist`,且不参与收藏夹同步过滤;
|
||||
- 自动化测试见 `tests/test_config.py`、`test_migrations.py`、`test_database.py`、`test_repository.py`、`test_bilibili_client.py`、`test_folder_sync.py`、`test_scheduler.py`、`test_logging.py`、`test_cli.py`。
|
||||
|
||||
### P1:快照与粉丝追踪
|
||||
|
||||
- 每日七项视频快照;
|
||||
- 粉丝快照;
|
||||
- 失败占位、断点续跑、Cookie 失效告警、SQLite 备份。
|
||||
|
||||
### P2:本地看板
|
||||
|
||||
- 总览、榜单、详情、UP、粉丝、候选和系统状态页面;
|
||||
- 原生前端和 ECharts;
|
||||
- 本机默认绑定 `127.0.0.1:8080`。
|
||||
|
||||
### P3:Telegram 与一键加入
|
||||
|
||||
- long polling、候选通知、收藏/忽略/拉黑;
|
||||
- `deal`、`sort`、回读验证和失败重试;
|
||||
- 对真实写入操作使用独立测试视频完成授权验证后再启用。
|
||||
|
||||
### P4:可选部署
|
||||
|
||||
- 反向代理、TLS、远程访问鉴权;
|
||||
- 如有需要再提供 Dockerfile;
|
||||
- Docker 不作为本机开发、联调或日常运行的前置条件。
|
||||
|
||||
---
|
||||
|
||||
## 13. 明确非目标
|
||||
|
||||
第一期不实现:
|
||||
|
||||
- 多用户、注册、角色权限;
|
||||
- 自动创建收藏夹或容量满后自动换夹;
|
||||
- AI 自动判定 reaction;
|
||||
- UP 白名单;
|
||||
- 视频历史统计回填;
|
||||
- Redis、消息队列、微服务、云数据库;
|
||||
- Node.js 前端工程;
|
||||
- Docker 作为本地开发依赖。
|
||||
Reference in new issue
Block a user