31 KiB
咻咻满 Reaction 视频数据看板开发文档
状态: P0(数据底座与收藏夹同步)、P1(每日快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 与一键加入)已实施
技术基线: 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 客户端 | httpx |
连接池、keepalive、明确的 connect/read 超时;B 站请求头与 UA 对齐 data/raw-request.txt。 |
| 数据库 | SQLite(WAL) | 单用户、单机、低并发写入;备份和迁移成本低。 |
| 调度 | threading、queue.Queue、标准库时间工具 |
单进程内运行发现、同步、快照和清理任务。 |
| 前端 | 原生 HTML/CSS/JavaScript | 无 Node.js、无构建链;前端仅渲染 /api/* JSON。 |
| 图表 | 仓库内置 ECharts | 离线静态资源,Apache-2.0 许可。 |
| 通知 | Telegram Bot long polling + httpx |
具备按钮回调能力,开发和本机运行不需要公网 URL。 |
| 容器化 | 可选的后续部署方式 | 本地开发、调试和日常运行均不要求 Docker。 |
1.1 选型原则
- 复用现有能力。 当前仓库的
favsort.py已实现 B 站设备指纹 Cookie、扫码登录、收藏夹读取和排序逻辑;新功能应拆分复用,而不是另起技术栈。 - 保持单体。 当前规模约数百到数千视频,每日采集量约千级请求;单进程、SQLite 和内置调度足够。
- 标准库优先,成熟依赖可用。 当依赖能明确改善可靠性、性能或可维护性时允许引入;必须锁定在
pyproject.toml/uv.lock,隔离在适配层,并用契约测试覆盖。 - 本地优先。 可在 Windows 上通过
uv直接运行,不要求 WSL、Docker、数据库服务或宿主机 cron。 - 未来可部署。 服务保持无状态代码与可挂载数据目录分离,未来可选用 Docker 与 Caddy/Nginx 部署,但不是开发前置条件。
2. 使用 uv 的本地开发环境
2.1 前置条件
- 安装 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 初始化与同步
在项目根目录执行:
uv sync --all-groups
该命令会:
- 读取
pyproject.toml; - 确保 Python 3.14 可用;
- 创建或更新
.venv; - 安装项目本身及后续声明的依赖;
- 根据
uv.lock锁定可复现的环境。
目前项目功能仅依赖 Python 标准库,因此首次同步不会安装额外运行时包。这是预期行为,不表示 uv 没有生效。
2.3 常用命令
# 查看 Python 与 uv 管理的虚拟环境是否可用
uv run python --version
# 查看现有收藏夹工具帮助
uv run bili-fav help
# 扫码登录并原子更新指定账号
uv run python app.py auth --account read
uv run python app.py auth --account write
# 检查两套 B 站登录状态
uv run python app.py auth-check --account all
# 执行当前无网络依赖的测试
uv run python tests/test_auth.py
uv run python tests/test_moves.py
未来看板服务实现后,统一使用:
# 建库、迁移与黑名单种子(离线,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
# 每日七项快照与粉丝数(P1)
uv run python app.py snapshot
uv run python app.py snapshot --force
# 检查 Cookie 登录态(P1)
uv run python app.py auth-check
# 查看数据库状态(离线,P0)
uv run python app.py status
# P2 起启动本地看板服务
uv run python app.py serve
uv run python app.py serve --config .\config.json
# 无子命令启动完整服务:只读 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
不建议直接激活 .venv;优先使用 uv run ...,这样命令总是运行在项目锁定的解释器和环境中。
2.4 依赖变更规范
若后续确实需要新增依赖,应使用 uv 修改项目元数据,而不是直接使用 pip install:
# 新增运行依赖(仅在标准库无法合理满足需求时)
uv add <package>
# 新增仅用于开发的依赖
uv add --dev <package>
# 同步锁文件和环境
uv sync --all-groups
提交依赖变更时,必须同时提交:
pyproject.tomluv.lock
不得提交:
.venv/config.jsonconfig.json.bakdata/中的运行期数据库、日志、计划和备份。
3. 总体架构
浏览器 / 手机 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。
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
4. 推荐代码结构
.
├── 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 日志
│ ├── classify.py # 标题归一化、时长解析与打分
│ ├── fav_position.py # 新视频插入位置计算
│ ├── timeutil.py # 上海时区与每日时间
│ ├── bilibili/
│ │ ├── client.py # 只读 HTTP、重试与限速
│ │ ├── write_client.py # deal / sort 写客户端
│ │ ├── search.py # WBI 搜索与旧接口降级
│ │ ├── provider.py # 凭据变化重建客户端
│ │ ├── auth.py # Cookie、设备指纹与请求头工具
│ │ ├── auth_flow.py # app.py auth 的扫码与原子写回流程
│ │ └── models.py # 接口响应模型
│ ├── services/
│ │ ├── discovery.py # 搜索发现与候选池
│ │ ├── favorites.py # 收藏 / 忽略 / 拉黑
│ │ ├── telegram_poller.py # getUpdates 与回调分发
│ │ ├── folder_sync.py
│ │ ├── snapshot.py
│ │ └── maintenance.py
│ ├── notify/telegram.py # Telegram 消息与按钮
│ └── web/ # P2 只读看板
│ ├── server.py # HTTP、路由、鉴权、静态资源
│ ├── queries.py # 看板 SQL 与数据整形
│ └── static/ # 原生 JS/CSS、ECharts、预览页面
│
├── tests/
└── data/ # 本地运行期数据,默认不提交
favsort.py 继续保持可独立运行,但只操作 bilibili.write;扫码授权统一由 app.py auth --account read|write 提供。Cookie、设备指纹与请求头逻辑必须共用 reaction_board.bilibili.auth。
5. 配置与凭据
5.1 运行期配置
所有凭据和可变业务参数放在 config.json 中。文件必须被 Git 忽略,日志中不得输出其敏感值。
{
"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中的年度夹由人工创建,系统只读;- 配置必须为
bilibili.read/bilibili.write双账号结构;不兼容顶层扁平mid/csrf/cookie; - 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 变化时由
ClientProvider重建 B 站请求客户端,并保留该角色的限速与重试策略; schedule.folder_sync_minutes与schedule.snapshot_at在启动时固化,修改后需重启run;- 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;folder_resource.fav_time记录该视频在该夹的加入时间,用于跨夹主归属选择;video.folder_id表示视频的主归属夹,按有效收藏关系重算,不依赖收藏夹遍历顺序;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 分组 |
B 站请求统一使用 httpx 连接池,并按 data/raw-request.txt 对齐浏览器特征:
user-agent:Chrome 155 / Windows;sec-ch-ua、sec-ch-ua-mobile、sec-ch-ua-platform;origin: https://space.bilibili.com;referer:收藏夹请求指向https://space.bilibili.com/<mid>/favlist?fid=<media_id>&ftype=create;priority: u=1, i;- 匿名请求仍剥离账号身份 Cookie,仅保留设备指纹。
请求头集中在 bilibili/auth.py::make_headers,不要在业务层散落自定义 Header。
7.2 发现任务
每小时扫描两个关键词、每个关键词前两页:
关键词:咻咻满 reaction、咻咻满 充电
排序:pubdate
页面:1、2
处理规则:
- 已在
video或seen_aid的 aid 跳过; - 发布时间超过 14 天的结果跳过;
- 标题不含“咻咻满”跳过;
- 命中
up_blacklist的 UP 跳过,不写候选池; - 通过分类规则计算分数;
- 分数低于零丢弃;
- 其余写入候选池;达到通知阈值的候选发送 Telegram 消息;
- 请求之间随机等待 1.5~2.5 秒。
分类规则 v1:
+2:标题含 reaction / re / 反应 / 看 / 听 / 初听 / 首次 / 点评 / 锐评 / 路人 / 震惊 / 双人
+2:标题含 充电 / 加急 / 专场
+2:时长大于 60 分钟
+1:时长大于 30 分钟且不超过 60 分钟
-5:标题含 歌切 / 切片 / 纯享 / 搬运 / 完整版
-3:UP 为咻咻满本人
score >= 2:发送通知
0 <= score < 2:仅候选池展示
score < 0:丢弃
P3 实现说明:
- WBI 密钥来自
nav.data.wbi_img,缓存 6 小时;WBI 失败自动降级旧版search/type; - 关键词、页数和时间窗由
discover配置控制; - 同一 aid 在多个关键词/页重复出现时,候选与通知都按 aid 去重;
- 已通知但未决策的候选不会重复通知;未通知的候选会在后续发现轮次补发;
- 人工把候选加入收藏夹后,收藏夹同步会入库并删除同 aid 候选。
7.3 收藏夹同步
每小时对所有已配置年度夹执行:
- 请求
resource/ids获取远端 aid 集合; - 与
folder_resource的有效本地集合做差集; - 对新增 aid 每 200 条请求
resource/infos并入库; - 对移除 aid 标记对应
folder_resource.active=0;若视频不在任何配置夹中,视频状态改为unfaved; - 每 6 小时全量刷新 infos,刷新元数据、
attr和收藏夹容量; attr=9或请求集合中静默缺失的资源改为removed;- 容量达到 950 条时发送预警;系统绝不自动换夹。
infos 只能作为元数据和失效同步来源。它缺少点赞、投币、转发,且评论字段不可靠,不能替代 view 日更。
7.4 每日快照与粉丝采集
每天 00:01(Asia/Shanghai)执行:
- 对
status IN ('active', 'unfaved')的视频逐条请求view?aid=; - 成功写入播放、点赞、收藏、投币、转发、评论、弹幕七项数据;
-404/62002/62012时标记removed(62002/62012 为当前实测返回);- 其他错误最多重试 3 次,仍失败则写入
ok=0与错误信息,不写伪造的零值; - 每条视频请求之间随机等待 1.0~1.6 秒;
- 对
track_fans中的每个 mid 请求一次relation/stat并写入up_stat; - 任务结束向 Telegram 发送成功数、失败数、移除数和总耗时摘要。
服务启动后若当天快照尚未成功完成,应补跑当日任务;已成功的 (stat_date, aid) 不重复请求。
P1 实现说明:
- 时区使用固定 UTC+8(中国无夏令时),避免 Windows 缺少
tzdata; - 当日全部视频与粉丝采集成功后在
kv写入snapshot:completed:<date>=1;启动时未完成会补跑; - 完成标记不是唯一门槛:每次检查
pending_snapshot_aids(date),新入库或从dropped恢复的视频会立即补采当天快照; ok=0的当日记录会在下次补跑时重试;ok=1直接跳过;粉丝同样跳过已成功的(stat_date, mid);- 开始采集前先写
snapshot:completed:<date>=0,强制重采中断后不会残留旧的完成标记; - 每次采集后生成
data/backup/app-YYYYMMDD.db并清理超过storage.backup_keep_days的日备份; - 采集完成与备份完成分开记录;备份失败时
snapshot返回 1,下次执行只重试备份、不重复请求 B 站; - Cookie 登录态通过
x/web-interface/nav检查,失效只告警,不阻塞匿名view; - Telegram 摘要推迟到 P3,P1 先写结构化 JSON 日志和
kv状态。
8. Telegram 与收藏操作
8.1 通知动作
候选通知提供:
- 收藏:加入目标收藏夹并入库;
- 忽略:候选标记
rejected,不再通知; - 拉黑:写入
up_blacklist,同时拒绝该 UP 的其他待决策候选; - 打开视频:外链到 B 站视频页。
拉黑只影响未来搜索发现;不清理、不停止追踪已有视频,也不影响从收藏夹同步进来的视频。
8.2 幂等与失败处理
Telegram callback_data 只包含短操作 ID,例如 fav:<id>;真实 aid、目标夹、过期时间和状态保存在 fav_action 表。
收藏流程:
- 将动作从
pending原子切换为processing; - 先读取目标夹 ids,若视频已存在则按成功处理;
- 按“同 UP 最早视频所在夹优先,否则当年夹”的规则选择目标夹;
- 调用
deal加入收藏; - 调用
sort将视频放进同 UP 连续分组; - 回读 ids 校验最终位置;
- 成功:动作标记
done,视频标记active,删除候选; - 失败:动作标记
failed,保留候选与原始错误,允许用户重试。
GET 页面或链接预览只能展示信息,绝不能触发任何写操作。
P3 实现说明:
callback_data使用fav:<aid>/ign:<aid>/blk:<aid>,真实状态存fav_action;begin_fav_action把pending/failed原子切换为processing;并发点击返回busy;- 已在目标夹时直接成功,不重复
deal; - 目标夹优先同 UP 在库最早视频所在夹,其次当前自然年夹,最后最新年度夹;
- 插入位置由
fav_position.plan_insertion计算;组头需要两次sort; deal或sort失败写fav_action.state='failed',保留候选,用户可再次点击重试;- Telegram 只接受
chat_id与from.id都等于配置私聊用户的回调。
9. Web API 与页面
9.1 API 原则
- P2 看板只读:业务接口只注册 GET,写方法统一返回
405; - 数据管理(收藏、忽略、拉黑、移出库、移除黑名单)继续通过 Telegram、CLI 或数据库完成;
/api/health可匿名;配置web.auth_token后其他/api/*要求 Cookie 或Authorization: Bearer <token>;- 只读接口不需要 CSRF;登录 POST 只建立会话,不修改业务数据;
- API 返回 JSON;前端不复制热度、状态机或收藏夹归属判断;
- CSV 导出由后端根据当前筛选条件生成。
核心接口:
| 方法 | 路径 | 用途 |
|---|---|---|
GET |
/api/overview |
首页动态、总览指标、容量、采集状态 |
GET |
/api/videos |
视频分页、筛选和排序(支持 added/published/heat/七项/互动/增长) |
GET |
/api/videos/{aid} |
视频详情 |
GET |
/api/videos/{aid}/trend |
七项趋势数据 |
GET |
/api/rankings |
视频榜或 scope=up 的 UP 聚合榜 |
GET |
/api/ups |
UP 聚合榜单 |
GET |
/api/ups/{mid} |
UP 详情、聚合趋势和视频列表 |
GET |
/api/fans |
粉丝曲线与增量 |
GET |
/api/candidates |
候选池(只读) |
GET |
/api/system |
最近任务、下次任务、备份、黑名单 |
GET |
/api/export.csv |
按当前视频筛选导出 CSV |
9.2 页面
| 页面 | 内容 |
|---|---|
| 首页 | 最新动态优先:今日增量、最新入库、最近发布、增长动态、热度速览、容量与采集状态。 |
| 榜单 | 综合热度、七项单项、增长、增速、互动率、UP 聚合、筛选和 CSV。 |
| 视频详情 | 元数据、七项指标、单指标趋势、快照表、失败标记、同 UP 对比。 |
| UP 页 | UP 的全部视频、聚合趋势、单视频对比和平均互动率。 |
| 粉丝页 | 粉丝总数、日增、7 日净增、累计增长;不叠加视频数据。 |
| 候选池 | 待确认候选、规则命中、剩余保留时间;操作指向 Telegram/CLI。 |
| 系统状态 | 最近扫描、同步、快照、下次运行、备份、鉴权和黑名单。 |
9.3 预览模式
tools/seed_preview_db.py复制真实库并回填合成快照,真实库只读;- 合成快照写
src='preview',预览库写kv preview:seeded=1; python app.py serve --preview校验标记,缺失时拒绝启动;- 预览配置不包含真实凭据,默认端口 8081,Telegram 关闭;
- 预览页面显示固定“预览数据 · 只读”横幅;
- 重新生成预览库前必须停止预览服务,避免 Windows 文件占用。
10. 热度与趋势口径
10.1 综合热度
对每项最新有效快照值计算:
x_i = log1p(metric_i)
z_i = (x_i - median(x)) / (IQR(x) / 1.349)
score = Σ weight_i * z_i
默认权重:
view 0.30, like 0.20, coin 0.20, favorite 0.15,
share 0.10, reply 0.03, danmaku 0.02
权重从配置读取,改动后查询时重算,不存冗余结果。
10.2 增长与互动率
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 脚本:
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、36removed(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 备份。
完成情况(2026-10-31):
- 已实现
services/snapshot.py:view七项指标、relation/stat粉丝数、ok=0失败占位、ok=1断点跳过; - 实测不可访问视频返回
62012,已和旧口径-404、62002一起按removed处理; run调度器同时注册收藏夹同步与每日snapshot_at任务,启动时立即检查当日是否完成以支持补跑;- 每次快照后生成日备份并清理超过 14 天的日备份;
auth-check与快照启动时检查 Cookie 登录态,失效写kv并告警;- 真实全量联调:795 条视频全部
ok=1,0 失败、0 移除,耗时 1124 秒;粉丝follower=2767270;日备份data/backup/app-20261031.db含 795 条快照;二次执行正确命中完成标记并跳过; - 自动化测试见
tests/test_snapshot.py、test_maintenance.py、test_timeutil.py,并扩展了test_bilibili_client.py与test_scheduler.py。
P1 补丁:数据完整性与热更新修复 ✅
- 新入库/恢复视频当天补采:
pending_snapshot_aids()+ 同步后立即触发快照; - 凭据热更新:
ClientProvider检测mid/cookie/csrf变化后重建客户端; - 协议防御:
ids/infos缺失data或data=null时中止,不再当作空结果; - 空远端保护:远端 0 条而本地非空时跳过差集,需
--allow-empty-folder才执行清空; - 粉丝续跑:非强制模式跳过已成功的
(stat_date, mid); - 强制重采:开始前先写未完成标记,中断后可续跑;
- 备份恢复:采集完成与备份完成分离,备份失败返回 1 并支持无请求重试;
- 跨夹主归属:迁移 v2 增加
folder_resource.fav_time,按有效关系重算video.folder_id; dropped视频失效时保留原始标题;favsort.py:--verify-every必须为正数;sort使用真实资源类型,不再硬编码2;- 配置每 60 秒检查 mtime;调度参数变更需重启。
P3:Telegram 与一键加入 ✅
- long polling、候选通知、收藏/忽略/拉黑;
deal、sort、回读验证和失败重试;- 对真实写入操作使用独立测试视频完成授权验证后再启用。
完成情况(2026-10-31):
- 已实现
bilibili/search.py:WBI 签名、密钥缓存 6 小时、旧版搜索降级; - 已实现
classify.py:标题归一化、时长解析与打分; - 已实现
services/discovery.py:关键词/页数/时间窗过滤、黑名单、候选池、通知批次; - 已实现
notify/telegram.py与services/telegram_poller.py:消息分片、Inline Keyboard、回调授权、offset 推进; - 已实现
bilibili/write_client.py、fav_position.py、services/favorites.py:目标夹选择、deal、sort、回读校验、失败保留候选; app.py新增discover/candidates;run注册发现与候选清理任务,并启动 Telegram poller;- 真实只读联调:4 次 WBI 搜索、168 条结果、1 条候选(score=6),Telegram 通知发送成功;
- 真实
deal/sort尚未在收藏夹执行,等待用户点击 Telegram 按钮或授权测试视频后验证; - 自动化测试见
tests/test_classify.py、test_search.py、test_candidates.py、test_discovery.py、test_fav_position.py、test_favorites.py、test_telegram.py、test_httpx_transport.py、test_favsort_http.py。
P2:只读本地看板 ✅
- 首页、榜单、视频/UP 详情、粉丝、候选和系统状态页面;
- 原生前端和本地 vendored ECharts;
- 本机默认绑定
127.0.0.1:8080; - Web 业务接口只读,写操作继续由 Telegram/CLI/数据库承担;
- 首页默认“最新动态”,增长榜在不足 2 天时显示空态;
- 视频趋势按单指标切换,缺失日断开、失败快照按缺失处理;
- 实现
tools/seed_preview_db.py:复制真实库并回填 7 天合成快照,支持--profile live|first-day; serve --preview校验preview:seeded=1标记,预览页面显示只读横幅;- 自动化测试见
tests/test_analytics.py、test_preview_seed.py、test_web_queries.py、test_web_server.py。
P4:可选部署
- 反向代理、TLS、远程访问鉴权;
- 如有需要再提供 Dockerfile;
- Docker 不作为本机开发、联调或日常运行的前置条件。
13. 明确非目标
第一期不实现:
- 多用户、注册、角色权限;
- 自动创建收藏夹或容量满后自动换夹;
- AI 自动判定 reaction;
- UP 白名单;
- 视频历史统计回填;
- Redis、消息队列、微服务、云数据库;
- Node.js 前端工程;
- Docker 作为本地开发依赖。