docs: decouple favorites, manual ingest, write-for-sort-only
This commit is contained in:
1 parent
6c4287b43a
commit
0d6e62f9ee
5 files changed
+197
-206
No files matched your search
+65
-61
@@ -1,21 +1,23 @@
|
||||
# 咻咻满 Reaction 视频数据看板 —— 可行性分析与技术路线
|
||||
|
||||
> 状态:**设计定稿;P0(数据底座与收藏夹同步)、P1(每日快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 与一键加入)已实施;第 9 轮加入时长效率榜、UP 粉丝快照与导航拆分**
|
||||
> 最后更新:2026-11-01(第 9 轮:时长效率榜、粉丝快照采集、视频/UP 导航拆分与数据说明页)
|
||||
> B 站探测均为只读(GET),尚未调用 `fav/resource/deal`、`fav/resource/sort`
|
||||
> 状态:**设计定稿;P0(数据底座)、P1(每日快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 发现与入库)已实施;第 9 轮加入时长效率榜、UP 粉丝快照与导航拆分;第 10 轮与 B 站收藏夹解耦并加入 Telegram 手动入库**
|
||||
> 最后更新:2026-11-01(第 10 轮:收藏夹解耦、手动入库、写鉴权仅用于重排序)
|
||||
> B 站写操作仅保留 `fav/resource/sort`(由 `favsort.py` 触发,频率极低);`fav/resource/deal` 已移除
|
||||
|
||||
---
|
||||
|
||||
> 说明:本文档记录项目完整设计与历史验证。**第 10 轮起,收藏夹不再是库的真相源**:入库改由「搜索候选确认」与「Telegram 私聊手动入库」承担,项目不再轮询收藏夹;下方 §4、§7 等章节中关于「收藏夹同步 / 一键加入」的描述为历史设计,已被第 10 轮取代(见 §15)。
|
||||
|
||||
## 0. 结论摘要
|
||||
|
||||
**判定:可行,工程难度中等偏低。** 四个功能点都有稳定可用的官方 Web 接口,且匿名即可取数。
|
||||
**判定:可行,工程难度中等偏低。** 核心功能点都有稳定可用的官方 Web 接口,且取数匿名即可完成。
|
||||
|
||||
| 功能点 | 主力接口 | 实测 |
|
||||
|---|---|---|
|
||||
| 视频发现 | `wbi/search/type` | ✅ `code=0`,匿名可用,单关键词上限 800~1000 条 |
|
||||
| 收藏夹同步(库的真相源) | `fav/resource/ids` + `fav/resource/infos` | ✅ infos **一次 400 条 / 0.2s**,664 条全量 2 次请求 |
|
||||
| 手动入库(链接/BV/av/短链) | `view?aid=` / `view?bvid=`、`b23.tv` 跳转 | ✅ 只读 |
|
||||
| 每日统计 | `view?aid=` | ✅ 7 项指标齐全,0.07~0.14s/条,无风控 |
|
||||
| 一键加入 | `fav/resource/deal` + `fav/resource/sort` | 排序语义前作已验证;加入待授权实测 |
|
||||
| 收藏夹重排序(唯一写操作) | `fav/resource/ids` + `infos` + `sort` | ✅ `favsort.py` 已实现 |
|
||||
| 看板 | 本地 SQLite | ✅ 无外部依赖 |
|
||||
|
||||
**本轮确认的设计前提**(来自你的两轮答复):
|
||||
@@ -70,18 +72,18 @@
|
||||
> **容量处理(不做自动改夹)**:目标夹满了就让它失败——`deal` 返回错误时记 `fav_action.state='failed'` 并通过通知渠道告警(含"请新建年度夹并更新配置")。可在接近上限(如 `media_count >= 950`)时提前发一次预警,但**系统绝不擅自把视频塞进另一个夹**。
|
||||
> **注意**:这只是"收藏夹归属",`snapshot` 与看板**不按年分表**,全部视频统一查询、统一出榜。
|
||||
|
||||
### 1.4 两个数据来源的关系
|
||||
### 1.4 数据来源的关系(第 10 轮更新)
|
||||
|
||||
```
|
||||
人工加收藏(兜底、可信) ──┐
|
||||
├──► video 表(status=active)──► 每日统计 ──► 看板
|
||||
搜索候选 + 点击确认(补充) ──┘
|
||||
搜索候选 + Telegram 点确认 ──┐
|
||||
├──► video 表(status=active)──► 每日统计 ──► 看板
|
||||
Telegram 私聊发链接/BV 号 ───┘
|
||||
```
|
||||
|
||||
- **收藏夹是真相源**:用户手工加进去的,默认就是"符合条件",系统自动入库;
|
||||
- **搜索是补漏**:只发通知,点确认才入库,同时顺手加进目标收藏夹;
|
||||
- **日更对象** = 库内 `active` 视频(跨年度夹累计),与收藏夹当前内容解耦;
|
||||
- **黑名单只挡"发现"**:收藏夹里已有的黑名单 UP 视频一律当普通视频处理,不判断、不排除、也不反向更新黑名单。
|
||||
- **本地库是真相源**:入库来自「搜索候选确认」与「Telegram 私聊手动入库」,只写本地 `video(status='active')`;
|
||||
- **收藏夹解耦**:项目不再轮询收藏夹,也不会因取消收藏改变视频状态;收藏夹由人工维护,`favsort.py` 负责低频重排序;
|
||||
- **日更对象** = 库内 `active` 视频;
|
||||
- **黑名单只挡"发现"**:已入库的黑名单 UP 视频一律当普通视频处理,不判断、不排除、也不反向更新黑名单。
|
||||
|
||||
### 1.5 配置与凭据(统一放配置文件)
|
||||
|
||||
@@ -114,7 +116,7 @@
|
||||
261052419, 1908537643, 1701121088, 542262, 376316958,
|
||||
145178393, 66389997, 293976264, 3026109
|
||||
],
|
||||
"schedule": { "discover_minutes": 60, "folder_sync_minutes": 60,
|
||||
"schedule": { "discover_minutes": 60,
|
||||
"snapshot_at": "00:01", "candidate_ttl_days": 7 },
|
||||
"notify": { "min_score": 2, "max_items_per_message": 10 },
|
||||
"classify": {
|
||||
@@ -134,7 +136,7 @@
|
||||
- 日志里**永不打印 token / cookie**,启动时只打印"已配置/未配置";
|
||||
- 容器里把配置目录挂成**可写卷**(`-v ./config:/config`,权限 600),因为 `auth` 扫码续期后要回写 cookie;
|
||||
- `python app.py auth --account read|write` 对指定账号扫码续期,只替换该账号身份字段、保留该账号设备指纹;
|
||||
- `read` 只用于年度收藏夹同步、快照和查询;`write` 只用于目标收藏夹、`deal` 和 `sort` 写操作;不支持顶层扁平凭据;
|
||||
- `read` 用于快照、搜索发现、入库与所有查询;`write` 仅用于 `favsort.py` 收藏夹重排序(`sort`);不支持顶层扁平凭据;
|
||||
- 可选的 `ENV` 覆盖(`TG_BOT_TOKEN` 等)留给 CI/临时调试,不作为主路径。
|
||||
|
||||
### 1.6 初始黑名单(已核对昵称,实测 2026-10-31)
|
||||
@@ -166,12 +168,12 @@
|
||||
| 旧版搜索(降级) | `GET x/web-interface/search/type` | 否 | ✅ `code=0`,结果略少 | 兜底 |
|
||||
| UP 主搜索 | 同上 `search_type=bili_user` | 否 | ✅ 找到 咻咻满 mid=37754047 | 仅元数据 |
|
||||
| 空间投稿 | `GET x/space/wbi/arc/search` | 否(需 WBI) | ✅ `code=0` | 本方案不使用 |
|
||||
| 视频完整统计 | `GET x/web-interface/view?aid=/bvid=` | **否** | ✅ 7 项齐全,0.1s | **日更主力** |
|
||||
| 收藏夹 ID 列表 | `GET x/v3/fav/resource/ids` | 是 | ✅ 664 条 1 次拿全 | 收藏夹监控 |
|
||||
| 收藏夹批量详情 | `GET x/v3/fav/resource/infos` | 是 | ✅ **一次 400 条**,0.2s;664 条返回 648(缺 16) | 元数据同步/导入 |
|
||||
| 加入收藏夹 | `POST x/v3/fav/resource/deal` | 是 + csrf | 文档明确,待实测 | 一键加入 |
|
||||
| 收藏夹内排序 | `POST x/v3/fav/resource/sort` | 是 + csrf | ✅ 已验证 `A:2:B:2` | 定位 |
|
||||
| 收藏夹列表 | `GET x/v3/fav/folder/created/list-all` | 是 | ✅ 返回 media_count | 容量监控 |
|
||||
| 视频完整统计 | `GET x/web-interface/view?aid=/bvid=` | **否** | ✅ 7 项齐全,0.1s | **日更 + 手动入库主力** |
|
||||
| 收藏夹 ID 列表 | `GET x/v3/fav/resource/ids` | 是 | ✅ 664 条 1 次拿全 | `favsort.py` 重排序 |
|
||||
| 收藏夹批量详情 | `GET x/v3/fav/resource/infos` | 是 | ✅ **一次 400 条**,0.2s;664 条返回 648(缺 16) | `favsort.py` 重排序 |
|
||||
| ~~加入收藏夹~~ | `POST x/v3/fav/resource/deal` | — | — | **第 10 轮已移除** |
|
||||
| 收藏夹内排序 | `POST x/v3/fav/resource/sort` | 是 + csrf | ✅ 已验证 `A:2:B:2` | 收藏夹重排序(唯一写操作) |
|
||||
| 收藏夹列表 | `GET x/v3/fav/folder/created/list-all` | 是 | ✅ 返回 media_count | 列出重排序目标夹 |
|
||||
| 粉丝数 | `GET x/relation/stat?vmid=` | **否** | ✅ `code=0`,匿名可用 | 粉丝追踪 |
|
||||
| ~~视频统计(旧)~~ | `x/web-interface/archive/stat` | — | ❌ **HTTP 404,已下线** | 不可用 |
|
||||
| ~~批量 view~~ | `view?aid=1,2` | — | ❌ `-400` | 不可用 |
|
||||
@@ -528,17 +530,15 @@ CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT);
|
||||
|
||||
**要点**
|
||||
|
||||
- `status` 是核心状态机:
|
||||
- `pending` 搜索候选,等确认(只在 `candidate` 表,不进 `video`)
|
||||
- `status` 是核心状态机(第 10 轮更新):
|
||||
- `active` 在库,参与日更
|
||||
- `unfaved` 用户取消收藏,**仍继续日更**,看板可筛
|
||||
- `removed` 视频失效(`-404` / `attr=9` / `infos` 缺失),停止请求但保留历史
|
||||
- `dropped` 用户手动"移出库",**停止发现与追踪**;若之后又出现在收藏夹,同步任务会自动改回 `active`
|
||||
- `rejected` 用户点过"忽略",不再通知
|
||||
- `candidate` 与 `video` 分表:候选可能永远不入库,避免污染主表;候选 `first_seen` 超过 **7 天**且 `decision IS NULL` → **直接删除 candidate 行**(按你的偏好),去重由 `seen_aid` 保证,不会重复通知;
|
||||
- `unfaved` 历史遗留状态(迁移 v5 已归一为 `active`),不再由系统产生
|
||||
- `removed` 视频失效(`view` 返回 `-404` / `62002` / `62012`),停止请求但保留历史
|
||||
- `dropped` 手动"移出库",**停止发现与追踪**;再次被候选确认或手动入库时会恢复 `active`
|
||||
- `candidate` 与 `video` 分表:候选可能永远不入库,避免污染主表;候选 `first_seen` 超过 **7 天**且 `decision IS NULL` → 删除 candidate 行,去重由 `seen_aid` 保证,不会重复通知;
|
||||
- `seen_aid` 只存 `(aid, first_seen, source)`,每条约 30 字节,按 5 条/天增长,10 年也不到 100KB;
|
||||
- `up_blacklist` 只影响发现:`folder_sync` **不查**这张表,收藏夹里黑名单 UP 的视频按普通视频入库;
|
||||
- `folder_id` 的取值遵循 §1.3:优先同 UP 最早视频所在夹,其次当前自然年夹;
|
||||
- `up_blacklist` 只影响发现,不影响已入库视频;
|
||||
- `folder_id` 与 `folder_resource` 保留为历史数据,不再由同步任务写入(第 10 轮);
|
||||
- 不预存"日增量",全部查询时算(历史数据修正后不会自相矛盾)。
|
||||
|
||||
---
|
||||
@@ -755,8 +755,9 @@ config.docker.example.json 服务器配置样例(0.0.0.0 + /data 绝对路径
|
||||
**进程结构**
|
||||
|
||||
```
|
||||
scheduler 线程:discovery(每 60min) / folder_sync(每 60min) / snapshot(每日 00:01)
|
||||
scheduler 线程:discovery(每 60min) / snapshot(每日 00:01) / candidate_cleanup(每日)
|
||||
web 线程:ThreadingHTTPServer :8080
|
||||
telegram:getUpdates long polling(回调 + 私聊文本入库)
|
||||
storage:SQLite WAL,单写线程
|
||||
```
|
||||
|
||||
@@ -766,33 +767,33 @@ storage:SQLite WAL,单写线程
|
||||
|
||||
| 风险 | 影响 | 概率 | 缓解 |
|
||||
|---|---|---|---|
|
||||
| 搜索排序抖动 + 只取 2 页 | 漏抓个别新视频 | 中 | 每小时扫一次 + 收藏夹监控兜底 + 候选池;业务上可接受 |
|
||||
| 搜索排序抖动 + 只取 2 页 | 漏抓个别新视频 | 中 | 每小时扫一次 + 候选池 + 私聊手动入库兜底;业务上可接受 |
|
||||
| 搜索结果噪声 | 通知太吵 | 中 | 轻量规则降噪 + 人点确认;不达阈值进候选池 |
|
||||
| 收藏夹 1000 条上限 | 夹满后无法加入 | 中 | 接近 950 时预警;夹满则 `deal` 失败 → **报错并通知**(不自动改夹),由人工新建年度夹并更新配置 |
|
||||
| 同 UP 归属导致旧夹持续增长 | 2026 夹被 2027 视频撑爆 | **高** | 与上一条同一处置:预警 + 失败告警 + 人工建新夹;不加自动改夹逻辑 |
|
||||
| 收藏夹与库不一致 | 库内视频不在收藏夹 | 低 | 收藏夹已解耦;人工维护收藏夹,`favsort.py` 只做重排序 |
|
||||
| 收藏夹 1000 条上限 | 重排序目标夹过满 | 低 | 由人工新建收藏夹;`favsort.py` 仅排序,不自动加条 |
|
||||
| 黑名单误伤 | 漏掉正常 reaction | 低 | 黑名单可随时移除;被排除的 UP 不写候选表,但可从日志审计 |
|
||||
| `infos` 静默丢条 | 误判同步成功 | **高** | 用 `请求 ids - 返回 ids` 判断缺失;缺失即 `removed` |
|
||||
| `infos.reply` 恒 0 / 缺 like/coin/share | 统计不全 | **确定** | 日更以 `view` 为唯一事实来源,`infos` 只做同步/降级 |
|
||||
| GET 链接被预览预取 | 误加入收藏夹 | 中 | GET 只渲染页面,写操作走 POST |
|
||||
| `deal` / `sort` 写失败 | 收藏夹状态不一致 | 中 | 先查 ids 幂等、失败重试、`fav_action` 记录、页面展示失败原因 |
|
||||
| 视频失效(当前 5.4%) | 日更报错 | **高** | `-404` / `attr=9` / 缺失 → `removed`,不再请求 |
|
||||
| 接口下线(`archive/stat` 已 404) | 功能中断 | 中 | 接口层抽象 + 降级(旧版 search / infos 三项)+ 监控告警 |
|
||||
| 风控 `-352/-412` | 采集中断 | 低(实测未见) | 限速 1.2s + 抖动 + 指数退避 + 告警 |
|
||||
| 跨年切换遗漏 | 新视频进错夹 | 低 | 按 pubdate 选夹 + 1 月 1 日提醒 |
|
||||
| `infos` 静默丢条 | 重排序计划缺条 | 低 | `favsort.py` 用 `请求 ids - 返回 ids` 判断缺失 |
|
||||
| `infos.reply` 恒 0 / 缺 like/coin/share | 统计不全 | **确定** | 日更以 `view` 为唯一事实来源,`infos` 仅用于重排序 |
|
||||
| `b23.tv` 短链失效或改版 | 手动入库解析失败 | 低 | 解析失败会在 Telegram 回执提示;可直接改发 BV 号 |
|
||||
| GET 链接被预览预取 | 无(已无写入型 GET) | — | 写操作走 Telegram 消息/回调,Web 全只读 |
|
||||
| `sort` 写失败 | 收藏夹顺序不一致 | 中 | `favsort.py` 回读校验、失败冷却重试、可 `run` 续跑 |
|
||||
| 视频失效 | 日更报错 | **高** | `view` 返回 `-404` / `62002` / `62012` → `removed`,不再请求 |
|
||||
| 接口下线 | 功能中断 | 中 | 接口层抽象 + 降级(旧版 search);监控告警 |
|
||||
| 风控 `-352/-412` | 采集中断 | 低(实测未见) | 限速 + 抖动 + 指数退避 + 告警 |
|
||||
| SQLite 并发 | 写冲突 | 低 | WAL + 单写线程 |
|
||||
| cookie 过期 | 收藏夹相关接口失败 | 中 | 使用 `python app.py auth --account read|write` 扫码续期;`view`/搜索匿名可用作降级 |
|
||||
| cookie 过期 | 只读采集失败 | 中 | `python app.py auth --account read|write` 扫码续期;`view`/搜索匿名可用作降级 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 待确认
|
||||
|
||||
**无。** 三轮问答已覆盖:收录口径、发现策略、收藏夹归属与容量、通知渠道与动作、黑名单语义、候选池生命周期、粉丝追踪、部署形态。
|
||||
**无。** 第 10 轮口径已确认:入库只写本地库;收藏夹解耦、不再追踪;手动入库支持视频链接 / BV 号 / av 号 / `b23.tv` 短链;`bilibili.write` 仅用于 `favsort.py` 重排序。
|
||||
|
||||
进入定稿/实施前只需要你提供**运行期凭据**(不建议写进本文档):
|
||||
进入运行前只需要你提供**运行期凭据**(不建议写进本文档):
|
||||
|
||||
- Telegram `bot_token` + 私聊 `chat_id`
|
||||
- B 站 cookie(也可以先用 `auth` 扫码生成)
|
||||
- 年度收藏夹 `media_id`(已有 4451053851 / 3700023351)
|
||||
- B 站 cookie(`read` 必需;`write` 仅重排序用;也可先用 `auth` 扫码生成)
|
||||
- 收藏夹 `media_id`(`favsort.py` 重排序目标;已有 4451053851 / 3700023351)
|
||||
|
||||
---
|
||||
|
||||
@@ -845,16 +846,16 @@ storage:SQLite WAL,单写线程
|
||||
| 41 | **全量离线测试** | 32 个 `tests/test_*.py` 全部通过(含新增 `test_up_profile.py` 与迁移/数据库断言更新) |
|
||||
| 42 | **内容效率噪声修正** | 预览库实测:视频级 EB 收缩(κ=60 分钟)+ 播放 ≥ 2000 后,效率榜 Top10 全为 ≥110 分钟视频;「什么哦吼吼」4 条不再入榜;综合热度榜同样应用 2000 门槛(637 条) |
|
||||
| 43 | **榜单交互改版** | 排序/时间改为下拉;时间筛选实测 `year=2026` 640 条、`year=2025` 156 条(合计 796);`sort=published` 可用;`/api/overview` 返回 years `[2026,2025]`;`year=1800` 返回 400 |
|
||||
| 44 | **第 10 轮全量离线测试** | 32 个 `tests/test_*.py` 全部通过;新增 `test_video_ref.py`、`test_library.py`,重写 `test_telegram.py`,删除收藏夹同步相关测试 |
|
||||
| 45 | **迁移 v5** | 历史 `video.status='unfaved'` 归一为 `active`;`app-pre-v4-*` 自动备份;`folder` / `folder_resource` 保留 |
|
||||
|
||||
**未验证(需授权或外部配合)**:
|
||||
|
||||
- `fav/resource/deal` 实际加入 + `sort` 定位(会写收藏夹;需用户点击 Telegram 按钮或授权测试视频);
|
||||
- Telegram 按钮真实回调(长轮询代码与授权逻辑已离线测试);
|
||||
- 真实 Telegram 私聊文本入库与按钮回调(长轮询与解析逻辑已离线测试);
|
||||
- 真实 `b23.tv` 短链跳转(客户端跟随重定向逻辑已离线测试);
|
||||
- 手机扫码 `auth` 完整链路;
|
||||
- 各通知渠道的按钮/链接实际表现;
|
||||
- 长期(>72h)每小时扫描的风控表现;
|
||||
- 收藏夹真实上限是否 1000;
|
||||
- 本地真实数据副本删除后,未在生产库上执行 `sync --full`;预览库的 `video.pages` 仍为 NULL,需在真实库跑一次全量同步才会填充(不影响效率榜,效率榜只用 `duration`)。
|
||||
- `favsort.py` 对真实收藏夹的重排序(写操作,需用户确认计划后手动执行)。
|
||||
|
||||
**本轮有意不做**:基于粉丝数的"破圈"榜单(只采集与回填数据,见 §14)。
|
||||
|
||||
@@ -862,19 +863,22 @@ storage:SQLite WAL,单写线程
|
||||
|
||||
## 14. 下一步
|
||||
|
||||
P0、P1、P2、P3 与第 9 轮已实施;P2 交付了只读本地看板与可重复生成的 7 天预览数据。
|
||||
P0、P1、P2、P3、第 9 轮与第 10 轮已实施;P2 交付了只读本地看板与可重复生成的 7 天预览数据。
|
||||
|
||||
第 9 轮(2026-11-01)交付:
|
||||
第 10 轮(2026-11-01)交付:
|
||||
|
||||
1. schema v4:`video.pages`、`video.up_follower*`、`up_profile`;
|
||||
2. 入库时采集 UP 粉丝快照 + `python app.py backfill-followers` 历史回填(基于粉丝数的榜单暂不做);
|
||||
3. 时长效率榜(内容效率/互动效率)与 UP 内容效率(EB 收缩),过滤 < 600 秒与未满 2 天的视频;
|
||||
4. 主导航拆分:「榜单」→「视频」+「UP主」,两者独立页面;新增「数据说明」页(`/about` + `/api/about`);排序/时间改为下拉筛选,支持按发布时间排序与按年份过滤;
|
||||
5. 列表精简:卡片只保留主指标 + 互动率,七项明细与分 P、粉丝快照移到详情页。
|
||||
1. 收藏夹解耦:删除 `folder_sync`、`FavoriteActionService`、`app.py sync` 与调度同步任务;`folder` / `folder_resource` / `folder_id` 仅保留历史;
|
||||
2. 迁移 v5:历史 `unfaved` 归一为 `active`;
|
||||
3. 入库:候选确认与 Telegram 私聊手动入库统一走 `services/library.py`,只写本地库并补当天快照;
|
||||
4. 手动入库解析:`video_ref.extract_video_refs`(BV/av/URL/`b23.tv`)+ `bilibili.client.view?bvid=` / `resolve_short_link`;
|
||||
5. Telegram:`allowed_updates` 增加 `message`;按钮改为「入库 / 忽略 / 拉黑」(`add:`,兼容 `fav:`);
|
||||
6. 写鉴权收口:删除 `write_client.deal`,`bilibili.write` 仅 `favsort.py` 重排序使用;
|
||||
7. 配置移除 `schedule.folder_sync_minutes` / `full_folder_refresh_hours`;
|
||||
8. 看板文案改为「历史收藏夹」,候选页改为「入库」。
|
||||
|
||||
仍待处理:
|
||||
|
||||
1. 真实 `deal` / `sort` 与 Telegram 按钮回调的补充验证记录;
|
||||
1. 真实 Telegram 文本入库 / 按钮回调 / `b23.tv` 跳转的补充验证记录;
|
||||
2. 基于粉丝数的"破圈"维度(播放/入库时粉丝数、粉丝分段、UP 中位数)——数据已就绪,榜单未实现;
|
||||
3. P4(容器化 + 反代)继续保持可选。
|
||||
|
||||
|
||||
Reference in new issue
Block a user