挖一个巨坑,缓慢推进中。。。
豆瓣数据自动更新与核查系统方案(面向 17 万视频库)
0. 总体目标与边界
0.1 目标
- 在苹果CMS后台“视频”菜单新增页面:豆瓣数据。
- 系统以可控频率、持续地对视频进行豆瓣数据更新(调用
douban.php),并满足一致性约束:
- 年份差异较大、标题差异过大、地区冲突、导演冲突的记录不自动更新,进入待核查。
- 对简介执行 AI 优化(例如 OpenRouter DeepSeek v3.2),并提供简介锁定,避免重复改写。
- 提供数据治理功能:无豆瓣ID、豆瓣ID重复、已锁定豆瓣ID、待核查豆瓣ID等列表与批量处理能力。
- 在 17 万数据规模下,保证:可持续运行、可观测、可回滚、避免全表扫描。
0.2 核心原则
- 写库必须可解释、可回滚:任何自动写入都记录来源、评分、差异点、前后值。
- 自动化只做“高置信”:低置信或冲突进入待核查,避免误伤。
- 锁定优先:豆瓣ID锁定、简介锁定后,自动任务不再覆盖。
- 任务队列化:更新、匹配、AI简介均通过队列执行,严格限速与并发控制。
1. 数据库设计与变更(字段、状态、索引)
1.1 在视频表(mac_vod)新增/确认字段
建议新增(或确认已有同义字段):
1) 豆瓣ID与锁定
vod_douban_id:varchar(20) 或 bigint(存 subject_id)douban_id_locked:tinyint(1) 默认 0(是否锁定豆瓣ID)douban_id_lock_time:datetime 可空(锁定时间)douban_id_source:varchar(16)(manual/auto/ai_search/import)douban_id_confidence:tinyint/smallint(0–100,记录最终评分)douban_review_status:varchar(16)(NOT_FOUND/REVIEW/CONFIRMED/IGNORED)douban_review_reason:varchar(255) 或 text(冲突原因摘要)douban_ignore_until:datetime 可空(忽略到期,期间不再入队)
2) 豆瓣数据更新节奏
douban_last_sync_at:datetime 可空(上次成功同步豆瓣数据时间)douban_next_sync_at:datetime 可空(下次计划同步时间,关键字段)douban_sync_fail_count:int 默认 0(连续失败次数)douban_last_fail_at:datetime 可空douban_last_fail_reason:varchar(255) 可空
3) 简介AI优化与锁定
intro_locked:tinyint(1) 默认 0(简介锁定)intro_lock_time:datetime 可空intro_ai_source:varchar(32) 可空(deepseek/openrouter/…)intro_ai_last_at:datetime 可空(上次AI改写时间)intro_ai_fail_count:int 默认 0intro_ai_last_fail_at:datetime 可空intro_ai_last_fail_reason:varchar(255) 可空
说明:锁定后不允许自动采集更新豆瓣ID和简介,上述字段直接支持该约束。
1.2 新增业务表(建议)
1) 豆瓣任务队列表 mac_douban_task
用于调度“抓取/回填”与“匹配/复核”任务,避免频繁扫主表。
字段建议:
task_id主键vod_idtask_type(SYNC_Douban / MATCH_DoubanID / AI_Intro / RECHECK)status(PENDING/RUNNING/SUCCESS/FAIL/SKIP)priority(int,最近更新/新片更高)run_after(datetime,最早运行时间)attempts(int)last_error(varchar(255))payload(text/json:候选ID列表、打分明细摘要等)created_at/updated_at
2) 豆瓣操作日志表 mac_douban_log(必须)
用于审计与回滚:
log_idvod_idaction(AUTO_CONFIRM / AUTO_SYNC / MANUAL_SET_ID / MANUAL_REPLACE_ID / LOCK_ID / UNLOCK_ID / AI_INTRO_UPDATE 等)old_values(json:旧字段快照)new_values(json:新字段快照)reason(文本/摘要)score(0–100)operator(admin账号或系统)created_at
3) 待核查候选表(可选但推荐)mac_douban_review_candidate
用于保存 TopN 候选及评分,避免每次打开页面重算:
idvod_iddouban_idscore_totalscore_detail(json)conflicts(json/文本标签)rank(1..N)created_at
1.3 索引建议(17 万规模避免全表扫描)
主表(mac_vod)关键索引:
idx_douban_next_sync_at:(douban_next_sync_at,type_id)idx_douban_review_status:(douban_review_status,douban_next_sync_at)idx_vod_douban_id:(vod_douban_id)idx_intro_locked:(intro_locked,intro_ai_last_at)idx_update_time:(update_time)(若你按更新时间优先处理)
任务表(mac_douban_task)索引:
idx_task_poll:(status,run_after,priority)idx_task_vod_type:(vod_id,task_type,status)
2. 后台页面与配置项(豆瓣数据菜单)
2.1 新增菜单:视频 → 豆瓣数据
页面建议分 Tab:
- 自动更新设置
- 待核查豆瓣ID
- 无豆瓣ID
- 豆瓣ID重复
- 已锁定豆瓣ID
- 简介锁定/AI处理
- 任务监控与日志
2.2 自动更新设置页(可配置项)
- 默认更新间隔:2分钟(随机抖动 0–120 秒)
- 排除分类ID:逗号分隔(type_id)
- 更新频率分层(按
update_time优先、vod_year兜底): - 最近1个月:每 3 天
- 最近半年:每 7 天
- 最近1年:每 30 天
- 1年以上:每 60 天
- 2年以上:每 90 天
- 候选数量 TopN:默认 5
- 自动确认阈值、差值阈值
- AI 简介:启用/禁用、模型、RPM/TPM、每日上限、失败退避
- douban.php 调用:每分钟最大请求数、并发数、失败退避
3. 任务调度与运行机制(先后顺序与队列)
3.1 初始化(一次性)
对现有 17 万视频,批量初始化:
vod_douban_id为空:
douban_review_status=NOT_FOUNDdouban_next_sync_at按更新时间分层写入未来时间(均匀散列,避免集中)
vod_douban_id不为空:
douban_review_status=CONFIRMED(不锁定,除非你已有锁定策略)douban_next_sync_at按分层策略写入
- 近期
update_time的记录提高优先级(更早 next_sync 或更高 priority)
3.2 周期调度器(Scheduler)
每隔 N 秒(例如 30 秒)执行一次:
- 从
mac_vod按douban_next_sync_at<=now()拉取一批vod_id(例如 50–200 条/批),要求:
- 不在排除分类
douban_ignore_until为空或已过期
- 对每条生成任务:
- 有
vod_douban_id:入队SYNC_Douban - 无
vod_douban_id:入队MATCH_DoubanID
- 去重:同
vod_id+task_type若已 PENDING/RUNNING,不重复入队。
3.3 Worker(执行器)
- Douban Worker:处理
MATCH_DoubanID与SYNC_Douban - AI Worker:处理
AI_Intro
每类 Worker 支持:
- 并发上限(Douban 2–5;AI 1–3,可配)
- 全局速率限制(token bucket)
- 失败退避(指数 backoff + 抖动)
- 熔断(失败率过高自动暂停)
4. 自动匹配豆瓣ID:候选获取与打分决策(关键)
4.1 候选ID来源(按顺序)
对 MATCH_DoubanID 任务:
- 规范化输入(标题去发行后缀、提取年份/季/集等)
- 生成 Query(至少三条):
"{片名} 豆瓣""{片名} {年份} site:movie.douban.com/subject""{片名} {导演/主演} 豆瓣 subject"(可选)
- 调用“联网搜索 API”获取结果,抽取
subject_idTopN - 对每个候选调用
douban.php?id=xxxx获取摘要字段 - 进入否决项与打分规则,输出决策
4.2 否决项(Hard Reject)
命中即不自动写入,进入待核查(并写明原因标签):
- 年份强冲突:两边年份都有且相差 ≥3 年 → 否决;相差 2 年 → 降级待核查
- 标题相似度过低:综合标题相似度
<0.55→ 否决 - 地区强冲突:两边地区都有、归一化后交集为空且双方都不是“未知/*/多地区” → 否决
- 导演强冲突:两边导演集合均非空且交集为空 → 否决
- 剧/影强冲突(建议启用):剧/影类型相反且标题相似度 <0.75 → 否决
4.3 打分项(0–100)与默认权重
对每个候选计算总分 S,并保存分项明细(用于后台解释)。
- 标题匹配(45分)
- 取 max(本地名vs豆瓣名、本地别名vs豆瓣名、本地名vs豆瓣别名)
- 相似度用“编辑距离 + 分词Jaccard + 包含关系”混合
- 年份匹配(15分)
- 同年 15;差1年 10;差2年 5;缺失 8;差≥3年 0
- 地区匹配(10分)
- 有交集 10;缺失 5;弱冲突 4;强冲突 0
- 导演匹配(12分)
- 有交集 12;缺失 6;交集0 0
- 主演匹配(8分)
- 交集≥2:8;交集=1:6;缺失:4;交集=0:0(不否决)
- 类型匹配(5分)
- 有交集 5;缺失 2;明显不一致 0
- 时长/集数(5分,可选)
- 电影:差<=10分钟 5;<=20 3;>20 0
- 剧集:集数接近加 3–5
- 评分存在性弱加分(2分)
vod_score_num>0:+2(仅用于同分打破平局)
4.4 阈值决策(CONFIRMED / REVIEW / NOT_FOUND)
候选按 S 降序,取 Top1/Top2:
- CONFIRMED(自动写入)
S_top1 >= 85- 且(无 Top2 或
S_top1 - S_top2 >= 8) - 且未触发否决项
- 且
douban_id_locked=0
→ 写入豆瓣ID,记录来源与分数 - REVIEW(待核查)
70 <= S_top1 < 85- 或
S_top1>=85但差值不足(歧义) - 或触发否决项
- 或被锁定阻止写入
→ 进入待核查,保存 TopN 候选与原因 - NOT_FOUND(未找到)
- 无候选 或
S_top1 < 70
→ 标记未找到,后续可人工补充/重跑
5. 调用 douban.php 更新并回填(SYNC_Douban)
5.1 执行顺序
对 SYNC_Douban:
- 检查是否允许更新(排除分类、忽略期等)
- 调
douban.php?id=vod_douban_id获取 data - 回填策略(取不到不覆盖)
- 只写入 douban.php 返回的字段
douban_id_locked=1时不改vod_douban_idintro_locked=1时不改简介
- 成功:
douban_last_sync_at=now()douban_sync_fail_count=0- 写入
douban_next_sync_at(按第6节)
- 失败:
douban_sync_fail_count++- 写失败原因与时间
douban_next_sync_at按退避策略延后(第7节)
6. 更新频率策略(按时间分层 + 随机抖动)
6.1 分层频率(你给的规则)
- 最近 1 个月:
+3天 - 最近 6 个月:
+7天 - 最近 1 年:
+30天 - 1 年以上:
+60天 - 2 年以上:
+90天
6.2 运行时抖动(避免固定节奏)
- 调度器按批次入队
- Worker 在执行前为任务设置
run_after随机抖动(0–120秒)或采用令牌桶限速 - 不建议大量
sleep,更建议run_after + token bucket的组合
7. 并发、限速、退避与熔断(稳定性关键)
7.1 Douban 请求限速
默认建议:
- 并发:2(可配到 5)
- 速率:每分钟 20–60 次(逐步调大)
- 退避:指数退避 + 抖动
- 1次失败:+10分钟
- 2次失败:+30分钟
- 3次失败:+2小时
- 4次失败:+6小时
- ≥5次:+24小时,并提示人工关注/或转待核查
- 熔断:5分钟失败率>80% 或出现大量“登录/风控”特征 → 暂停 30–60分钟
7.2 搜索 API 限速(MATCH)
- 并发:1–3
- 速率:按主机限制配置
- 缓存:同一
(normalized_title, year)结果缓存 7–30 天
7.3 AI 简介接口限速(OpenRouter)
- AI Worker 独立队列,严格限速(RPM/TPM/RPD)
- 429:延后 10–30 分钟;5xx:延后 5–10 分钟;其它:延后 30–120 分钟
- 成功写入后:
intro_locked=1,后续自动任务不再改写(除非手动触发)
8. 待核查工作流(后台呈现、批量处理、一键确认/替换/锁定)
8.1 待核查列表页(REVIEW)
列表字段:
- 本地:
vod_id、名称、年份、地区、导演(摘要) - 当前豆瓣ID(如有)+ 锁定状态
- Top1:豆瓣标题/年份/地区/导演/评分/人数
S_top1、标题相似度、Top2简要、差值- 冲突标签(年份/地区/导演/歧义/相似度低/锁定阻止)
- 更新时间
- 操作
展开面板:
- Top1–Top5 候选卡片:标题/别名/年份/地区/类型/导演/主演/时长/集数
- 分项得分明细与否决项命中原因
8.2 单条操作
- 确认 Top1 并更新(可勾选“同时锁定豆瓣ID”)
- 选择其它候选(Top2–Top5)
- 手动输入豆瓣ID(强校验,冲突需二次确认)
- 替换并锁定(旧ID vs 新ID 差异对比弹窗)
- 标记忽略(30天/180天/永久)
- 锁定/解锁豆瓣ID、锁定/解锁简介
8.3 批量操作
- 批量“确认Top1并锁定”(仅对高置信且无否决项生效)
- 批量确认不锁定
- 批量忽略
- 批量解锁(高权限)
- 批量重跑匹配(规则/搜索源更新后)
全部批量操作要求:
- 异步队列执行
- 展示进度与失败原因
- 写入日志,支持回滚
9. 其它治理页面
9.1 无豆瓣ID(NOT_FOUND)
- 按近期更新/人气排序
- 一键生成候选(入队 MATCH)
- 手动搜索/手动输入豆瓣ID(同样走强校验)
9.2 豆瓣ID重复
- 按
vod_douban_id分组展示 - 一键保留最匹配/最早绑定/最高人气,其它清空并入待核查或无豆瓣队列
- 支持批量处理
9.3 已锁定豆瓣ID
- 展示锁定来源/时间/原因
- 支持筛选与批量解锁(高权限)
- 锁定条目默认不参与自动“改ID”,可配置是否参与“同步其他字段”
10. 简介 AI 优化:触发、写入与锁定
10.1 触发条件
intro_locked=0- 本次豆瓣同步成功(至少标题/类型等可用于约束)
- 距离上次 AI 处理超过最小间隔(例如 30 天)
- 未触发 AI 限速/熔断
10.2 输入输出规范
输入包含:标题(含年份)、豆瓣ID/URL、豆瓣简介(若有)、类型/地区/导演/主演。
输出要求:纯文本简介、长度范围可配置(如 120–300字)、不带外链、不杜撰关键事实。
写入规则:
- 成功写入后:
intro_locked=1,记录来源与时间 - 手动触发“重新AI简介”:允许覆盖,但覆盖后仍锁定
11. 可观测性与回滚
- 任务监控页:按 task_type/status 展示队列长度、成功率、失败率、平均耗时
- 日志查询:按 vod_id 查询操作链路(谁改了什么、依据、评分)
- 回滚:单条支持回退到上一版本(用
mac_douban_log.old_values) - 告警(可选):连续失败率过高、触发风控/登录墙时提示管理员
12. 实施步骤(按先后顺序)
- 数据库变更:新增字段、建索引、创建任务表与日志表
- 后台菜单与页面骨架:豆瓣数据入口 + 7个Tab(列表、筛选、操作占位)
- Scheduler:按
douban_next_sync_at拉取应处理记录并入队(去重) - Douban Worker:实现
SYNC_Douban(回填、日志、next_sync、退避) - 候选生成:接入搜索 API 抽取候选豆瓣ID(缓存与限速)
- 打分与决策:实现否决项、分项得分、CONFIRMED/REVIEW/NOT_FOUND 流转
- 待核查联动:候选卡片、分项解释、一键确认/替换/锁定/忽略
- 批量处理:批量确认/忽略/解锁/重跑匹配全部队列化
- AI Worker:接入 OpenRouter/DeepSeek,限速、退避、成功即锁定简介
- 监控与回滚:任务监控、日志审计、单条回滚
- 灰度上线:先小范围分类/时间段启用,观察命中率与待核查比例,再逐步扩大
进度截图
在后台“视频”菜单里新增一个入口 「豆瓣数据」

第 3 步(小步落地):仅“配置保存 + 页面展示配置”。

第 4 步(只读统计面板):在“豆瓣数据”页面顶部增加一块统计区,展示关键数量(无豆瓣ID、重复豆瓣ID、待核查、锁定等)。

第 5 步(只读列表页 + 分页 + 基础筛选)。

第 6 步(单条操作:核查/确认/忽略/锁定)。

第 7 步:批量操作(最小可用版)。目标是:在列表页勾选多条记录后,一次性执行 核查 / 确认 / 忽略 / 锁定ID / 锁定简介 等操作。

第 8 步:后台“手动触发同步(单条/批量)”——真正去调用 douban.php 并回填字段,但依旧不做定时任务。


