Files
re-xxm/README.md
T

425 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# bili-fav
**咻咻满 Reaction 视频数据看板与收藏夹管理工具。**
项目以 B 站年度收藏夹为可信视频来源,补充关键词发现和 Telegram 人工确认,持续采集 reaction 视频的七项数据与咻咻满粉丝数,并提供本地数据看板。现有的收藏夹排序工具仍保留并作为一键加入后的排序能力基础。
> 当前状态:**P0(数据底座与收藏夹同步)、P1(快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 与一键加入)已实施。**
>
> 当前已可用:扫码授权、收藏夹读取、排序计划生成、执行与校验;SQLite 建库迁移、收藏夹首次导入与增量同步、失效识别、黑名单种子;每日七项视频快照、粉丝数快照、失败占位、断点续跑与日备份;搜索发现、候选池、Telegram 通知与收藏/忽略/拉黑回调;只读本地看板与可重复生成的预览数据。
>
> 详细业务口径、接口验证和风险记录见 [project.md](project.md);开发架构与实施计划见 [DEVELOPMENT.md](DEVELOPMENT.md)。
## 功能概览
| 功能 | 状态 | 说明 |
|---|---|---|
| 收藏夹排序 | 可用 | 同一 UP 连续,组内按发布时间倒序,组间按该 UP 最早视频发布时间倒序。 |
| 扫码授权与 Cookie 续期 | 可用 | 本地生成二维码;更新身份 Cookie 时保留设备指纹。 |
| 收藏夹同步与首次导入 | 可用(P0) | 监控已配置年度夹,自动发现人工新收藏的视频与失效资源。 |
| 视频日快照 | 可用(P1) | 逐条采集播放、点赞、投币、收藏、转发、评论、弹幕。 |
| 粉丝追踪 | 可用(P1) | 每日记录咻咻满等配置 UP 的粉丝数。 |
| 搜索发现与候选池 | 可用(P3) | 每小时搜索前 2 页,分类打分后写入候选池。 |
| Telegram 通知与一键加入 | 可用(P3) | 收藏 / 忽略 / 拉黑按钮,`deal` + `sort` 回读校验。 |
| 本地数据看板 | 可用(P2) | 只读总览、榜单、视频/UP 详情、粉丝曲线、候选池和系统状态。 |
## 核心工作流
```text
关键词搜索(补漏) ──► 候选池 / Telegram 人工确认 ──► 收藏夹 + 视频库
│
人工加入年度收藏夹 ──► 收藏夹同步(真相源)───────────┤
▼
每日指标与粉丝快照
▼
本地看板
```
- **收录范围**:只收录他人观看、试听或点评咻咻满内容的 reaction 视频;最终是否收录由人工确认。
- **收藏夹是真相源**:手工加入已配置年度夹的视频自动入库;关键词搜索仅负责发现候选。
- **黑名单仅作用于发现**:已收藏或已入库的黑名单 UP 视频不会被清理,也会继续追踪。
- **年度夹不自动创建或切换**:同一 UP 的新视频优先进入该 UP 最早视频所在的收藏夹;容量接近上限时预警,失败时告警,不擅自改夹。
- **数据不回填**:B 站不提供任意日期的历史指标;新视频从首次入库日起开始积累快照。
## 本地开发环境
项目默认使用 **Python 3.14** 和 [uv](https://docs.astral.sh/uv/)。本地开发、测试和现有排序工具的运行均不依赖 Docker、Node.js、数据库服务或宿主机 cron。
```powershell
# 在项目根目录初始化或同步环境
uv sync --all-groups
# 确认锁定的解释器版本
uv run python --version
# 查看现有 CLI 帮助
uv run bili-fav help
```
完整的 `uv` 使用方式、依赖规范、架构与开发约定见 [DEVELOPMENT.md](DEVELOPMENT.md)。
## P0:数据底座与收藏夹同步
P0 已实现 SQLite 迁移、配置热加载、B 站只读客户端、收藏夹首次导入与增量同步、`infos` 全量元数据刷新、失效识别、容量预警和黑名单种子。所有请求均为只读,不会修改收藏夹。
```powershell
# 建库、执行迁移、写入配置文件夹与黑名单种子(离线)
uv run python app.py init
# 首次导入或增量同步配置中的年度收藏夹(只读联网)
uv run python app.py sync
# 按配置周期常驻运行收藏夹同步 + 每日快照;--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`。
## P1:每日快照与粉丝追踪
P1 已实现每日七项视频指标快照、咻咻满粉丝数快照、失败占位、断点续跑、Cookie 失效告警和 SQLite 日备份。
```powershell
# 采集当日快照与粉丝数(只读联网,约 18~20 分钟 / 795 条)
uv run python app.py snapshot
# 忽略当日完成标记,重新采集
uv run python app.py snapshot --force
# 分别为只读和写入账号扫码授权(默认 read)
uv run python app.py auth
uv run python app.py auth --account write
# 检查指定账号或两套账号的登录态
uv run python app.py auth-check --account read
uv run python app.py auth-check --account all
```
- `snapshot` 对 `active` / `unfaved` 视频逐条请求 `view`,每条请求间隔 1.0~1.6 秒;
- 成功写入 `snapshot` 的七项指标;同日 `ok=1` 的记录直接跳过,可断点续跑;
- 当天已完成后新入库或从 `dropped` 恢复的视频会立即补采当天快照,不必等到次日;
- 视频不可访问(`-404` / `62002` / `62012`)标记为 `removed`;
- 其他错误重试 3 次,仍失败写入 `ok=0` 和错误信息,不写伪造的零值;
- 粉丝数写入 `up_stat`,失败同样写 `ok=0`;续跑时已成功的 `(stat_date, mid)` 会跳过,不会被失败结果覆盖;
- 当日全部视频与粉丝采集成功后写入完成标记;服务启动时若当天未完成会自动补跑;
- 每次采集后生成 `data/backup/app-YYYYMMDD.db`,按 `storage.backup_keep_days` 清理旧日备份;
- 备份失败时 `snapshot` 返回 1 便于任务告警;再次执行只重试备份,不重复请求 B 站;
- Cookie 失效只告警,不阻塞匿名 `view` 采集;P0/P1 只使用 `bilibili.read`。
首次真实全量采集结果:795 条视频全部成功、0 失败、0 移除,耗时约 19 分钟;粉丝 `follower=2767270`;日备份为 `data/backup/app-20261031.db`。
## P3:搜索发现与 Telegram 一键加入
P3 已实现 WBI 搜索发现、候选池、Telegram 通知与按钮回调,以及使用写账号完成的 `deal` / `sort` 加入流程。
```powershell
# 执行一次发现;--no-notify 只写候选池,不发 Telegram
uv run python app.py discover
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
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'`,可直接重试;不自动换夹;
- 忽略写 `candidate.decision='rejected'`;拉黑写 `up_blacklist` 并拒绝该 UP 的其他待决策候选;
- 候选超过 `schedule.candidate_ttl_days` 且未决策时自动删除,`seen_aid` 防止重复通知;
- 人工把候选加入收藏夹后,收藏夹同步会入库并删除同 aid 候选。
> 真实 `deal` / `sort` 需要用户点击 Telegram 按钮才会触发;离线测试使用契约一致的 Mock 覆盖。
## P2:只读本地看板
P2 提供移动端优先的只读看板,默认绑定 `127.0.0.1:8080`。Web 页面不提供收藏、忽略、拉黑、移出库等写操作;数据管理仍通过 Telegram、CLI 或数据库完成。
```powershell
# 只启动只读 Web(不运行调度、不连 B 站)
uv run python app.py serve
# 无子命令启动完整服务:只读 Web + 调度 + Telegram
uv run python app.py
# 生成 7 天预览库并启动预览
uv run python tools/seed_preview_db.py --days 7
uv run python app.py serve --preview --config data/preview/config.json
```
- 首页默认“最新动态”:今日增量、最新入库、最近发布、增长动态、热度速览、收藏夹容量与采集状态;
- 榜单支持综合热度、七项单项、互动率、播放增量/播放增速,以及 UP 聚合榜;UP 榜默认按**平均播放**排序,总播放和视频数放在排序项末尾;服务端筛选、排序和分页;
- 视频详情提供七项指标、单指标趋势、快照表和同 UP 对比;UP 页提供聚合趋势与单视频对比;
- 粉丝页只绘制粉丝曲线,不叠加视频数据;
- 候选池和系统状态只读展示,操作入口指向 Telegram/CLI;
- 本地回环地址默认免登录;配置 `web.auth_token` 后启用登录页和 HttpOnly Cookie;非回环地址且未配置 token 时拒绝启动;
- 所有业务接口只接受 GET,写方法返回 `405`。
### 预览数据
`tools/seed_preview_db.py` 通过 SQLite backup API 复制真实库到 `data/preview/app.db`,再回填 7 天合成快照:
- 合成行标记 `src='preview'`,并写入 `kv preview:seeded=1`;
- 真实库只读,预览库与真实库完全分离;
- 默认包含少量缺失日、3 条失败快照、4 条预览候选和 1 条预览黑名单;
- `--profile first-day` 可生成保持“首次导入”状态的冷启动库;
- 预览配置不包含真实 Cookie,端口 8081,Telegram 关闭;
- 重新生成前必须先停止预览服务,否则 Windows 会因数据库被占用而拒绝覆盖。
### 粉丝历史回填
小满虫之家 `data.html` 的 `assets/js/data.js` 包含 90 天粉丝锚点和每日净增。可以只读拉取并回填 `up_stat`:
```powershell
# 先预览将要写入的内容
uv run python tools/import_fans_history.py --days 90 --dry-run
# 实际写入;默认跳过已有成功记录,--force 才覆盖
uv run python tools/import_fans_history.py --days 90
# 站点不可达时,用保存下来的 data.js 离线导入
uv run python tools/import_fans_history.py --from-file data.js --days 90
```
- 数据源当前快照日期为 `2026-10-24`,导入范围 `2026-07-27 ~ 2026-10-24`;
- 只写 `up_stat`,不修改视频快照、收藏夹或黑名单;
- 缺失日期不插值,粉丝曲线会断开;
- 默认跳过已有 `ok=1` 记录;`ok=0` 的历史记录会用站点数据修复;
- 页面的“日增”只在存在前一天数据时计算,跨多天不会标成日增;
- 写入前建议先备份 `data/app.db`,例如 `data/backup/app-pre-fans-YYYYMMDD.db`。
## 快速部署(Docker)
仓库根目录已包含 `Dockerfile`、`compose.example.yml`、`.dockerignore` 和 `config.docker.example.json`。Compose 文件是示例,需要显式指定:
```powershell
# 1. 准备服务器配置;至少填写 B 站 Cookie / Telegram,并保留 /data 绝对路径
Copy-Item config.docker.example.json config.json
# 2. 构建并后台启动
docker compose -f compose.example.yml up -d --build
# 3. 查看日志与健康状态
docker compose -f compose.example.yml logs -f
curl http://127.0.0.1:8080/api/health
```
如果希望省略 `-f`,可以复制为 `compose.yml`(该文件不提交):
```powershell
Copy-Item compose.example.yml compose.yml
docker compose up -d --build
```
- 容器执行 `python app.py --config /config/config.json`,同时运行只读 Web、调度和 Telegram;
- `config.json` 挂载到 `/config/config.json`,`data/` 挂载到 `/data/`;
- 服务器配置需使用 `web.host = 0.0.0.0` 并设置 `web.auth_token`,否则服务拒绝启动;
- `storage.database_path` 使用 `/data/app.db`,`storage.backup_dir` 使用 `/data/backup`;
- 首次部署可先在本地执行 `uv run python app.py auth --account read` 扫码,再把生成好的 `config.json` 放到服务器;
- 也可在容器内扫码:`docker compose -f compose.example.yml run --rm board python app.py auth --account read --no-browser`,二维码会打印在临时文件路径,可用 `docker cp` 取出;
- 数据卷包含数据库、备份与预览库,建议定期备份 `/data`。
不使用 Compose 时:
```powershell
docker build -t reaction-board .
docker run -d --name reaction-board --restart unless-stopped `
-p 8080:8080 `
-v ${PWD}/config.json:/config/config.json `
-v ${PWD}/data:/data `
reaction-board
```
## 配置
复制样例配置,并填写 B 站账号信息:
```powershell
Copy-Item config.example.json config.json
```
`config.json` 已被 `.gitignore` 忽略,**不得提交**。配置必须使用双账号格式:
```json
{
"bilibili": {
"read": { "mid": 0, "cookie": "...", "csrf": "..." },
"write": { "mid": 0, "cookie": "...", "csrf": "..." }
}
}
```
- `bilibili.read`:年度收藏夹同步、视频数据快照、粉丝数与其他只读查询使用;
- `bilibili.write`:后续加入目标收藏夹、回读验证与现有 `favsort.py` 排序使用;
- 两个账号的 Cookie、CSRF 与设备指纹严格隔离;顶层 `mid` / `cookie` / `csrf` **不再支持**;
- `overrides`:可选,为代发或联合投稿视频指定归属 UP;
- `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` 进程。
完整配置口径以 [project.md](project.md) 和 [DEVELOPMENT.md](DEVELOPMENT.md) 为准。
## 当前可用:收藏夹排序 CLI
所有命令建议通过 `uv run` 执行:
```powershell
# 查看全部命令;无需 config.json
uv run bili-fav help
# 为写账号扫码登录与检查状态(认证入口统一在 app.py)
uv run python app.py auth --account write
uv run python app.py auth-check --account write
# 列出本人创建的收藏夹
uv run bili-fav folders
# 只读:拉取收藏夹,生成排序计划
uv run bili-fav plan <media_id> -v
# 查看或离线重算已有计划
uv run bili-fav show <media_id>
uv run bili-fav replan <media_id>
# 写操作:按计划逐步排序;完成后校验
uv run bili-fav run <media_id>
uv run bili-fav verify <media_id>
```
`plan` 会生成 `data/plan-<media_id>.json`,其中含原始顺序、目标顺序、移动脚本及视频元数据缓存。`run` 直接使用该计划,不会重复请求 `infos`;如排序算法变更,可用 `replan` 离线重算。
### 排序规则
1. **同一 UP 必须连续**:按 `upper.mid` 分组,组内按发布时间倒序;发布时间相同则按 `avid` 升序。
2. **组间按最早投稿倒序**:取该 UP 在当前收藏夹中最早视频的发布时间作为组键,按组键倒序;并列时按组内最小 `avid` 升序。
3. **失效或异常资源置底**:保持它们原有的相对顺序。
4. **可配置归属修正**:`config.json` 的 `overrides` 可为指定视频指定实际归属 UP。
> 组间规则是有意设计:某个 UP 只要存在较老视频,即使近期继续投稿,整个分组也会靠后。
## 扫码授权
当接口返回 `-101` 或 Cookie 即将过期时,使用统一认证入口:
```powershell
uv run python app.py auth --account read
uv run python app.py auth --account write
uv run python app.py auth-check --account all
```
授权流程在本地生成二维码图片并等待手机客户端扫码确认。成功后:
- 更新 `SESSDATA`、`bili_jct`、`DedeUserID`、`sid` 等身份字段;
- 保留 `buvid3`、`buvid4`、`_uuid`、`buvid_fp` 等设备指纹;
- 将选中账号的 `csrf` 同步为新的 `bili_jct`,并从 `nav` 回写该账号实际 mid;
- 原子写入 `config.json`,并保留 `config.json.bak`;
- 通过 `x/web-interface/nav` 验证新登录态,失败时自动回滚;不会修改另一个账号。
常用选项:
```powershell
uv run python app.py auth --account write --no-browser
uv run python app.py auth --account read --timeout 180
```
## B 站访问与安全约束
| 接口 | 当前用途 | 身份 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。 |
只读客户端对 `ids` / `infos` 默认剥离 `read` 身份 Cookie,仅保留该账号设备指纹;如果失败才使用完整 `read` Cookie 重试。写操作一律使用完整 `write` Cookie 与 CSRF。
B 站请求通过 `httpx` 连接池发送,并按 `data/raw-request.txt` 对齐 Chrome 155 的 UA、`sec-ch-ua`、`origin`、`referer` 和 `priority` 等请求头;收藏夹请求的 `referer` 会带上目标 `media_id`。
`x/v3/fav/resource/sort` 的实测语义为:
```text
sort=A:2:B:2 → 将 B 插到 A 之后
```
运行排序时会重新读取实际顺序、逐步回读校验,并在收藏夹内容于执行期间变动时中止,避免用过期计划强制排序。写操作存在风险,请确认计划后再执行;中断后可直接再次运行 `run` 继续收敛。
## 测试
测试全部离线运行,外部接口只在 transport 边界用真实响应形状的 Mock 替换:
```powershell
uv run python tests/test_auth.py # Cookie 合并、设备指纹、Set-Cookie 解析
uv run python tests/test_moves.py # 排序移动脚本:随机排列与头部用例
uv run python tests/test_favsort_http.py # favsort httpx 请求头与匿名回退
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_httpx_transport.py # httpx B 站/Telegram 传输层
uv run python tests/test_classify.py # 标题归一化、时长解析与打分
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_snapshot.py # 七项快照、补采、断点续跑、粉丝、备份重试
uv run python tests/test_maintenance.py # 日备份与保留期
uv run python tests/test_timeutil.py # 上海时区与每日任务时间
uv run python tests/test_analytics.py # 热度、互动率、增长与封面
uv run python tests/test_preview_seed.py # 预览库回填、确定性与安全边界
uv run python tests/test_fans_history.py # 粉丝历史解析、净增计算与幂等回填
uv run python tests/test_web_queries.py # 看板查询层与冷启动
uv run python tests/test_web_server.py # 只读路由、鉴权、静态资源与预览模式
uv run python tests/test_scheduler.py # 内置调度器到期/失败隔离
uv run python tests/test_provider.py # 凭据变化重建客户端与配置 revision
uv run python tests/test_favsort_run.py # 排序参数校验与混合资源类型
uv run python tests/test_logging.py # JSON 日志脱敏
uv run python tests/test_cli.py # CLI 离线路径
```
## 运行期文件
```text
config.json.bak 最近一次 auth 更新前的配置备份
data/plan-<media_id>.json 排序计划、顺序快照和元数据缓存
data/journal-<media_id>.jsonl 排序执行日志
data/app.db SQLite 数据库(收藏夹、视频、快照、粉丝、黑名单)
data/backup/app-YYYYMMDD.db 每日快照后的 SQLite 备份
data/backup/app-pre-v*.db 数据库迁移前的自动备份
data/backup/app-pre-fans-*.db 粉丝历史回填前的手动备份
data/preview/app.db 合成预览数据库(不提交)
data/preview/config.json 无凭据的预览配置(不提交)
```
## 文档索引
- [project.md](project.md):完整可行性分析、业务口径、接口验证、数据模型草案和风险清单。
- [DEVELOPMENT.md](DEVELOPMENT.md):开发架构、数据库迁移、API、调度、Telegram 与分阶段实施计划。