docs: decouple favorites, manual ingest, write-for-sort-only

This commit is contained in:
kairee committed 2026-11-01 14:09:09 +08:00
1 parent 6c4287b43a
commit 0d6e62f9ee
5 files changed
+197 -206

No files matched your search

+53 -61
View File
@@ -2,11 +2,11 @@
**咻咻满 Reaction 视频数据看板与收藏夹管理工具。**
项目以 B 站年度收藏夹为可信视频来源,补充关键词发现和 Telegram 人工确认,持续采集 reaction 视频的七项数据与咻咻满粉丝数,并提供本地数据看板。现有的收藏夹排序工具仍保留并作为一键加入后的排序能力基础。
项目以本地 SQLite 视频库为可信来源:搜索发现命中的候选经 Telegram 人工确认后入库,也可以在 Telegram 私聊直接发送视频链接或 BV 号手动入库。入库后持续采集七项数据与咻咻满粉丝数,并提供本地数据看板。B 站收藏夹由人工维护,项目只用写鉴权对收藏夹做低频重排序,不再自动加入收藏夹,也不再轮询收藏夹来标记入库/出库。
> 当前状态:**P0(数据底座与收藏夹同步)、P1(快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 与一键加入)已实施。**
> 当前状态:**P0(数据底座)、P1(快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 发现与入库)已实施,并已完成「收藏夹解耦 + 手动入库」改造。**
>
> 当前已可用:扫码授权、收藏夹读取、排序计划生成、执行与校验;SQLite 建库迁移、收藏夹首次导入与增量同步、失效识别、黑名单种子;每日七项视频快照、粉丝数快照、失败占位、断点续跑与日备份;搜索发现、候选池、Telegram 通知与收藏/忽略/拉黑回调;只读本地看板与可重复生成的预览数据。
> 当前已可用:扫码授权、收藏夹重排序(`favsort.py`);SQLite 建库迁移与黑名单种子;每日七项视频快照、粉丝数快照、失败占位、断点续跑与日备份;搜索发现、候选池、Telegram 入库/忽略/拉黑回调与私聊手动入库;只读本地看板与可重复生成的预览数据。
>
> 详细业务口径、接口验证和风险记录见 [project.md](project.md);开发架构与实施计划见 [DEVELOPMENT.md](DEVELOPMENT.md)。
@@ -14,31 +14,29 @@
| 功能 | 状态 | 说明 |
|---|---|---|
| 收藏夹排序 | 可用 | 同一 UP 连续,组内按发布时间倒序,组间按该 UP 最早视频发布时间倒序。 |
| 收藏夹排序 | 可用 | 同一 UP 连续,组内按发布时间倒序,组间按该 UP 最早视频发布时间倒序。唯一使用写鉴权的功能。 |
| 扫码授权与 Cookie 续期 | 可用 | 本地生成二维码;更新身份 Cookie 时保留设备指纹。 |
| 收藏夹同步与首次导入 | 可用(P0) | 监控已配置年度夹,自动发现人工新收藏的视频与失效资源。 |
| 视频入库 | 可用 | 搜索候选经 Telegram 确认入库;私聊发送视频链接、BV 号、av 号或 `b23.tv` 短链手动入库。只写本地库。 |
| 视频日快照 | 可用(P1) | 逐条采集播放、点赞、投币、收藏、转发、评论、弹幕。 |
| 粉丝追踪 | 可用(P1) | 每日记录咻咻满等配置 UP 的粉丝数。 |
| 搜索发现与候选池 | 可用(P3) | 每小时搜索前 2 页,分类打分后写入候选池。 |
| Telegram 通知与一键加入 | 可用(P3) | 收藏 / 忽略 / 拉黑按钮,`deal` + `sort` 回读校验。 |
| Telegram 通知与入库 | 可用(P3) | 入库 / 忽略 / 拉黑按钮;私聊消息手动入库。 |
| 本地数据看板 | 可用(P2/P9) | 只读总览、视频榜、UP 主榜、内容效率、视频/UP 详情、粉丝曲线、数据说明、候选池和系统状态。 |
## 核心工作流
```text
关键词搜索(补漏) ──► 候选池 / Telegram 人工确认 ──► 收藏夹 + 视频库
│
人工加入年度收藏夹 ──► 收藏夹同步(真相源)───────────┤
▼
每日指标与粉丝快照
▼
本地看板
关键词搜索(补漏) ──► 候选池 ──► Telegram「入库」──┐
├──► 本地视频库(active)──► 每日指标与粉丝快照 ──► 本地看板
Telegram 私聊发链接/BV 号 ──► 解析 ──► 入库 ─────────┘
B 站收藏夹(人工维护)──► favsort.py 重排序(唯一使用写鉴权,频率极低)
```
- **收录范围**:只收录他人观看、试听或点评咻咻满内容的 reaction 视频;最终是否收录由人工确认。
- **收藏夹是真相源**:手工加入已配置年度夹的视频自动入库;关键词搜索仅负责发现候选。
- **黑名单仅作用于发现**:已收藏或已入库的黑名单 UP 视频不会被清理,也会继续追踪。
- **年度夹不自动创建或切换**:同一 UP 的新视频优先进入该 UP 最早视频所在的收藏夹;容量接近上限时预警,失败时告警,不擅自改夹。
- **本地库是真相源**:入库来自「搜索候选确认」与「Telegram 私聊手动入库」;收藏夹不再自动同步。
- **收藏夹解耦**:项目不再轮询收藏夹,也不会因取消收藏而改变视频状态;收藏夹由人工维护,重排序使用 `favsort.py`。
- **黑名单仅作用于发现**:已入库的黑名单 UP 视频不会被清理,也会继续追踪。
- **数据不回填**:B 站不提供任意日期的历史指标;新视频从首次入库日起开始积累快照。
## 本地开发环境
@@ -58,35 +56,32 @@ uv run bili-fav help
完整的 `uv` 使用方式、依赖规范、架构与开发约定见 [DEVELOPMENT.md](DEVELOPMENT.md)。
## P0:数据底座与收藏夹同步
## P0:数据底座、采集与手动入库
P0 已实现 SQLite 迁移、配置热加载、B 站只读客户端、收藏夹首次导入与增量同步、`infos` 全量元数据刷新、失效识别、容量预警和黑名单种子。所有请求均为只读,不会修改收藏夹。
P0 已实现 SQLite 迁移、配置热加载、B 站只读客户端、黑名单种子与每日快照调度。入库来源为搜索候选确认与 Telegram 私聊手动入库;**项目不再轮询收藏夹**,收藏夹由人工维护。
```powershell
# 建库、执行迁移、写入配置文件夹与黑名单种子(离线)
# 建库、执行迁移、写入黑名单种子(离线)
uv run python app.py init
# 首次导入或增量同步配置中的年度收藏夹(只读联网)
uv run python app.py sync
# 手动入库:视频链接、BV 号、av 号或 b23.tv 短链(只写本地库)
uv run python app.py ingest BV1x8x166EzY
uv run python app.py ingest https://www.bilibili.com/video/BV1x8x166EzY
uv run python app.py ingest av12345
# 按配置周期常驻运行收藏夹同步 + 每日快照;--once 只跑一次到期任务
# 按配置周期常驻运行发现 + 每日快照;--once 只跑一次到期任务
uv run python app.py run
uv run python app.py run --once
# 强制全量刷新元数据(每 6 小时自动执行一次)
uv run python app.py sync --full
# 查看库内状态(离线)
uv run python app.py status
```
- 数据写入 `data/app.db`(SQLite WAL);升级已有数据库前会自动备份到 `data/backup/`;
- 内置调度器按 `schedule.folder_sync_minutes` 周期运行,`run` 常驻、`sync` 单次;
- `infos` 静默缺失或 `attr=9` 的资源标记为 `removed`,保留历史;
- 从收藏夹移除的资源标记为 `unfaved`,仍保留数据;
- 黑名单只作为发现阶段的种子写入,**不会过滤收藏夹同步**;
- 收藏夹达到 950 条时预警,系统绝不自动换夹;
- 远端返回 0 条但本地仍有记录时默认按异常跳过差集,确认收藏夹确实被清空后才使用 `--allow-empty-folder`。
- 入库只写本地 `video(status='active')`,不写 B 站收藏夹;已失效/移出库的视频被再次入库时会重新激活;
- 视频不可访问(`view` 返回 `-404` / `62002` / `62012`)由每日快照标记为 `removed`;
- 黑名单只作为发现阶段的种子写入,不影响已入库视频;
- `folders` 配置仅作为历史来源与重排序目标,系统不再自动同步或换夹。
## P1:每日快照与粉丝追踪
@@ -103,10 +98,7 @@ uv run python app.py snapshot --force
uv run python app.py backfill-followers
uv run python app.py backfill-followers --dry-run
# 全量同步默认也会在结尾采集缺失的 UP 粉丝数;如只想刷新元数据可跳过
uv run python app.py sync --full --no-capture-followers
# 分别为只读和写入账号扫码授权(默认 read)
# 分别为只读和写入账号扫码授权(默认 read;write 仅用于 favsort.py)
uv run python app.py auth
uv run python app.py auth --account write
@@ -128,9 +120,9 @@ uv run python app.py auth-check --account all
首次真实全量采集结果:795 条视频全部成功、0 失败、0 移除,耗时约 19 分钟;粉丝 `follower=2767270`;日备份为 `data/backup/app-20261031.db`。
## P3:搜索发现与 Telegram 一键加入
## P3:搜索发现、Telegram 入库与手动入库
P3 已实现 WBI 搜索发现、候选池、Telegram 通知与按钮回调,以及使用写账号完成的 `deal` / `sort` 加入流程。
P3 已实现 WBI 搜索发现、候选池、Telegram 通知与按钮回调(仅写本地库),以及 Telegram 私聊手动入库。
```powershell
# 执行一次发现;--no-notify 只写候选池,不发 Telegram
@@ -141,21 +133,21 @@ uv run python app.py discover --no-notify
uv run python app.py candidates
uv run python app.py candidates --decision rejected
# 常驻运行:同步 + 快照 + 发现 + Telegram long polling
# 常驻运行:快照 + 发现 + Telegram long polling
uv run python app.py run
```
- 搜索使用 `x/web-interface/wbi/search/type`,WBI 密钥缓存 6 小时,失败时降级旧版 `search/type`;
- 每轮按 `discover.keywords` 搜索前 `discover.pages` 页,过滤已入库/已见/超时间窗/黑名单/标题不含要求词的结果;
- 分数规则与 `classify` 配置一致;`score >= notify.min_score` 才发送 Telegram;
- 通知按钮:**收藏**、**忽略**、**拉黑**;回调只接受 `telegram.chat_id` 对应的私聊用户;
- 收藏使用 `bilibili.write`:先查目标夹是否已存在,再 `deal` 加入、`sort` 插入同 UP 组头、回读校验位置;
- 失败保留候选并写 `fav_action.state='failed'`,可直接重试;不自动换夹;
- 通知按钮:**入库**、**忽略**、**拉黑**;回调只接受 `telegram.chat_id` 对应的私聊用户;
- 「入库」把视频写入本地库并置为 `active`,**不写 B 站收藏夹**;同时用取到的 `view` 数据补一条当天快照;
- 私聊文本消息会自动解析其中的视频链接 / BV 号 / av 号 / `b23.tv` 短链并逐条入库,回执汇总结果;
- 忽略写 `candidate.decision='rejected'`;拉黑写 `up_blacklist` 并拒绝该 UP 的其他待决策候选;
- 候选超过 `schedule.candidate_ttl_days` 且未决策时自动删除,`seen_aid` 防止重复通知;
- 人工把候选加入收藏夹后,收藏夹同步会入库并删除同 aid 候选。
- 手动入库不做「标题含咻咻满」等规则校验,用户明确指定即入库。
> 真实 `deal` / `sort` 需要用户点击 Telegram 按钮才会触发;离线测试使用契约一致的 Mock 覆盖。
> 入库只依赖只读接口(`view`);写鉴权仅用于 `favsort.py` 收藏夹重排序。离线测试使用契约一致的 Mock 覆盖。
## P2:只读本地看板
@@ -173,7 +165,7 @@ uv run python tools/seed_preview_db.py --days 7
uv run python app.py serve --preview --config data/preview/config.json
```
- 首页默认“最新动态”:今日增量、最新入库、最近发布、增长动态、热度速览、收藏夹容量与采集状态;
- 首页默认“最新动态”:今日增量、最新入库、最近发布、增长动态、热度速览、历史收藏夹与采集状态;
- 「视频」与「UP主」是两个独立入口;工具条顺序为「排序」下拉 → 时间下拉(无需 label)→ 右侧自适应搜索框(最大 320px,放大镜按钮在输入框内),三者在 PC 与移动端都保持一行;
- 视频排序:内容效率(默认)、综合热度、发布时间、入库时间、播放/投币/收藏/评论、互动率、播放增量/播放增速;UP 排序:内容效率、互动效率、内容量、总播放、视频数、平均播放、总互动、最近发布;
- 时间筛选:全部 / 近 7 天 / 近 30 天 / 近 90 天 / 近 1 年 / 已入库的年份;不再提供按收藏夹过滤,UP 页也不再按视频数筛选;
@@ -187,7 +179,7 @@ uv run python app.py serve --preview --config data/preview/config.json
- 配置 `web.auth_token` 后,“候选”和“系统”页面及其接口需要登录,启用登录页和 HttpOnly Cookie;访客的底部导航只显示公开页面;
- 本地回环地址且未配置 token 时视为无鉴权,全部页面可见;非回环地址且未配置 token 时拒绝启动;
- 所有业务接口只接受 GET,写方法返回 `405`;
- 首页/榜单聚合结果缓存 1 小时,粉丝结果缓存 24 小时;快照、收藏夹同步、发现、收藏操作和粉丝 CSV 导入会立即让对应缓存失效。
- 首页/榜单聚合结果缓存 1 小时,粉丝结果缓存 24 小时;快照、发现、入库/忽略/拉黑、候选清理和粉丝 CSV 导入会立即让对应缓存失效。
### 预览数据
@@ -296,18 +288,18 @@ Copy-Item config.example.json config.json
}
```
- `bilibili.read`:年度收藏夹同步、视频数据快照、粉丝数与其他只读查询使用;
- `bilibili.write`:后续加入目标收藏夹、回读验证与现有 `favsort.py` 排序使用;
- `bilibili.read`:视频数据快照、粉丝数、搜索发现与入库等所有只读查询使用;
- `bilibili.write`:仅 `favsort.py` 收藏夹重排序使用;
- 两个账号的 Cookie、CSRF 与设备指纹严格隔离;顶层 `mid` / `cookie` / `csrf` **不再支持**;
- `overrides`:可选,为代发或联合投稿视频指定归属 UP;
- `folders`:年度收藏夹配置(`year` + `media_id` + `title`),系统不自动建夹;
- `folders`:历史来源与重排序目标(`year` + `media_id` + `title`),系统不再自动同步或建夹;
- `up_blacklist_seed`:首次启动写入数据库的 UP 黑名单种子;
- `track_fans`:需要每日追踪粉丝数的 UP mid;
- `discover`:搜索关键词、页数与时间窗(`keywords` / `pages` / `window_days`);
- `storage`:数据库与备份目录;
- `schedule` / `notify` / `classify` / `heat_weights`:任务节奏、通知阈值、分类规则和热度权重。
配置每 60 秒检查一次 mtime,凭据、收藏夹、分类规则等变更无需重启即可在下次任务读取;但 `schedule.folder_sync_minutes` 与 `schedule.snapshot_at` 在启动时固化,修改后需重启 `run` 进程。
配置每 60 秒检查一次 mtime,凭据、分类规则等变更无需重启即可在下次任务读取;但 `schedule.discover_minutes` 与 `schedule.snapshot_at` 在启动时固化,修改后需重启 `run` 进程。
完整配置口径以 [project.md](project.md) 和 [DEVELOPMENT.md](DEVELOPMENT.md) 为准。
@@ -378,14 +370,15 @@ uv run python app.py auth --account read --timeout 180
| 接口 | 当前用途 | 身份 Cookie |
|---|---|---|
| `fav/resource/ids` / `infos` | 年度收藏夹同步与排序计划 | `read` 用于 P0;`favsort.py` 计划使用 `write`。 |
| `view` / `relation/stat` / `nav` | P1 快照与 read 登录态 | `read`。 |
| `fav/folder/created/list-all` | 列出目标自建收藏夹 | `write`。 |
| `fav/resource/deal` / `sort` | 加入收藏夹、收藏夹排序 | `write`,需要完整 Cookie 和 CSRF。 |
| `view` / `relation/stat` / `nav` | 每日快照、入库详情、粉丝数、read 登录态 | `read`。 |
| `wbi/search/type` | 关键词搜索发现 | `read`(匿名可用)。 |
| `fav/resource/ids` / `infos` | 收藏夹重排序计划(`favsort.py`) | `write`。 |
| `fav/folder/created/list-all` | 列出目标自建收藏夹(`favsort.py`) | `write`。 |
| `fav/resource/sort` | 收藏夹重排序 | `write`,需要完整 Cookie 和 CSRF。 |
只读客户端对 `ids` / `infos` 默认剥离 `read` 身份 Cookie,仅保留该账号设备指纹;如果失败才使用完整 `read` Cookie 重试。写操作一律使用完整 `write` Cookie 与 CSRF。
只读客户端默认剥离 `read` 身份 Cookie,仅保留该账号设备指纹;如果失败才使用完整 `read` Cookie 重试。写操作(`sort`)一律使用完整 `write` Cookie 与 CSRF,且仅由 `favsort.py` 触发。
B 站请求通过 `httpx` 连接池发送,并按 `data/raw-request.txt` 对齐 Chrome 155 的 UA、`sec-ch-ua`、`origin`、`referer` 和 `priority` 等请求头;收藏夹请求的 `referer` 会带上目标 `media_id`。
B 站请求通过 `httpx` 连接池发送,并按 `data/raw-request.txt` 对齐 Chrome 155 的 UA、`sec-ch-ua`、`origin`、`referer` 和 `priority` 等请求头;收藏夹请求的 `referer` 会带上目标 `media_id`。私聊手动入库的 `b23.tv` 短链通过同一传输层跟随重定向还原为视频 URL。
`x/v3/fav/resource/sort` 的实测语义为:
@@ -407,16 +400,15 @@ uv run python tests/test_config.py # 配置加载、校验、ENV 覆盖
uv run python tests/test_migrations.py # 迁移、备份、回滚与保留期
uv run python tests/test_database.py # 单写队列、事务与并发
uv run python tests/test_repository.py # 收藏夹/视频状态机与黑名单
uv run python tests/test_bilibili_client.py # ids/infos/view/fans/nav 契约与重试
uv run python tests/test_bilibili_client.py # ids/infos/view/view-detail/fans/nav/短链契约与重试
uv run python tests/test_httpx_transport.py # httpx B 站/Telegram 传输层
uv run python tests/test_classify.py # 标题归一化、时长解析与打分
uv run python tests/test_video_ref.py # BV/av/URL/b23.tv 引用解析(纯函数)
uv run python tests/test_search.py # WBI 签名、搜索解析与降级
uv run python tests/test_candidates.py # 候选池、去重、通知与 fav_action
uv run python tests/test_discovery.py # 发现过滤、候选池与通知批次
uv run python tests/test_fav_position.py # 新视频插入位置
uv run python tests/test_favorites.py # 收藏/忽略/拉黑与失败重试
uv run python tests/test_telegram.py # 消息、分片、长轮询与回调授权
uv run python tests/test_folder_sync.py # 首次导入、差集、失效识别、容量预警
uv run python tests/test_library.py # 入库(候选/手动)、忽略、拉黑与幂等
uv run python tests/test_telegram.py # 消息、分片、长轮询、回调与文本入库
uv run python tests/test_snapshot.py # 七项快照、补采、断点续跑、粉丝、备份重试
uv run python tests/test_maintenance.py # 日备份与保留期
uv run python tests/test_timeutil.py # 上海时区与每日任务时间
@@ -438,7 +430,7 @@ uv run python tests/test_cli.py # CLI 离线路径
config.json.bak 最近一次 auth 更新前的配置备份
data/plan-<media_id>.json 排序计划、顺序快照和元数据缓存
data/journal-<media_id>.jsonl 排序执行日志
data/app.db SQLite 数据库(收藏夹、视频、快照、粉丝、黑名单)
data/app.db SQLite 数据库(视频、快照、粉丝、黑名单、历史收藏夹)
data/backup/app-YYYYMMDD.db 每日快照后的 SQLite 备份
data/backup/app-pre-v*.db 数据库迁移前的自动备份
data/backup/app-pre-fans-*.db 粉丝历史回填前的手动备份