feat(monitor): 抖音 Web 接口客户端 —— 绕开爬虫子进程,直接发 HTTP
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s

移植自 mac-agent-os 的 mediacrawler_adapter:不起子进程、不开页面,用浏览器里那份
登录态直接调抖音 Web 接口。产物键名照抄 store/douyin,所以 ingest 那条链路一个字不用改。

**目前能用的(真环境实测,非推断)**:

    profile/other : 200, 7075 字节  —— 博主主页指标(粉丝/获赞/作品数/昵称)
    aweme/detail  : 200, 45425 字节 —— 单条作品详情(含点赞/评论/收藏/分享)

**目前不能用的:作品列表 `aweme/post`。** 两个互相独立的原因:
  1. 这个接口被抖音单独升级成了真校验:不带 x-tt-argus 回 403「Uifid Not Found」,
     带上 dummy 值回 200 + **空 body**。也就是说「头在不在」骗得过,「真校验」过不了。
     同一套头打 profile/other 和 aweme/detail 都是通的 —— 抖音是挑着接口加保护的,
     挑中的恰好是「批量拉作品列表」这个最敏感的动作。
  2. 改走页面截获也不行:CDP 浏览器打开博主主页会落到「验证码中间页」(当天大量探测的
     代价,过几小时要重测)。

  所以现在的边界是:**已知作品的指标刷新能做,自动发现新作品做不了**。

**排查中控住变量后得到的两条事实**(都写进注释了):
  · `Accept` / `Accept-Language` / `Referer` 才是主页接口能返回真数据的原因 —— 只有
    UA+client hints+Cookie 时是 200 但仅 121 字节的空壳,补上这三个头变 7074 字节。
    (我先前猜的 sec-ch-ua 不是关键。)
  · 因此 UA 与 client hints 必须**成套地取自同一个浏览器**,所以 BrowserIdentity 一次
    从 CDP 取齐 cookie + UA + hints,而不是各自写死。

「200 + 空 body 必须当场报错」也是刻意写死的:放过去它会在下游变成「这个博主没作品」,
把一次失败伪装成一条正常结果 —— 爬虫那条路正是这么栽的,还被翻译成「账号被封」。

测试 +11:cookie 解析、请求头成套性(含 uifid 缺失/回退)、产物键名与 store 对齐、
以及 _get 的三条失败路径(空 body / 403 带网关原话 / 正常返回)。
This commit is contained in:
2026-10-10 17:12:25 +08:00
parent 21ff01b894
commit 2112c1a870
1601 changed files with 367425 additions and 0 deletions
+288
View File
@@ -0,0 +1,288 @@
# 导演工作室 · 重新设计方案
> 2026-10-10 · 基于**实测盘查**(非推测)
> 目标:止住"越改越乱",把现有能力重新收进一个**边界清晰**的结构里
---
## 一、现状诊断(全部实测数据)
### 1.1 规模
| 项 | 数量 | 说明 |
|---|---|---|
| 前端视图 | **85 个** | 视频工厂相关 44 个 |
| 后端路由 | **7 文件 / 145 端点 / 7206 行** | ave(33) · ave_project(30) · ave_keyframe(25) · ave_assets(33) · ave_scene(5) · ave_creatives(16) · ave_docs(3) |
| `ave-director.js` | **112 KB** | 单文件注册 **26 个 `window._dir*` 函数** |
| 项目对象顶层键 | **19 个** | 其中 3 个是从没写过的死键 |
| 运行目录 | **15 个** | 含 5 个测试残留 |
### 1.2 五类具体问题(都对齐到了文件)
**① 空壳页面 4 个**(内容是"功能开发中,敬请期待")
```
ave-script.js 409 B 脚本生成
ave-render.js 409 B 渲染任务
ave-templates.js 406 B 模板
ave-materials.js 409 B 素材库
```
**② 新旧两套流程并存 —— 6 个老流程页在菜单里,但跑不通**
```
ave-flow-short-drama.js 744 B ← 菜单「🎭 短剧创作」
ave-flow-dub.js 647 B ← 菜单「🎙️ 基础口播」
ave-flow-beat.js 579 B ← 菜单「🥁 卡点视频」
ave-flow-narrative.js 668 B ← 菜单「📖 角色叙事」
ave-flow-digital-human.js 611 B ← 菜单「👤 数字人口播」
ave-flow-hybrid.js 564 B ← 菜单「🎞️ 口播+混剪」
```
它们自己的注释写着:**"TTS / 素材 / BGM / 字幕的执行实现仍在接入中(运行时会明确跳过)"**
→ 而工作室那条链路**是通的** → 两套并存,用户不知道点哪个
**③ 工作室内部职责重叠(用户已经混了)**
```
🌍 设定 vs 📋 创意卡 —— 都在"想清楚这个故事"
🖼 关键帧设计 vs 🖼 关键帧 —— 用户明确问过"这两个有什么区别"
```
实测:用户上次说的"关键帧工作台里面是空的",我查了两轮才确认他指的是哪个页面。
**④ 单文件职责超载**
`ave-director.js`(112 KB)一个文件里塞了:
```
<!-- ② 全局设置 --> 导演系统 / 风格 / 演员表 / 场景 / seed
<!-- 📐 全片设定 --> 空间/角色/镜头语言(原故事板并入)
<!-- ③ 按场次生成 --> 段落列表 + 场记板 + 生成
<!-- ④ 分镜表 --> 镜头卡片编辑
<!-- 出片进度 --> 渲染任务轮询
```
**⑤ 数据源分散 + 死键**
```
projects/{pid}/project.json ← 项目对象(19 键,其中 3 键为空)
storyboards/{pid}.json ← 分镜(不在 project.json)
keyframes/{pid}/{sid}/ ← 关键帧图
keyframe_videos/{pid}/{sid}/ ← 视频片段
```
`project.json` 里的 `keyframes` / `videos` / `assets` **三个键从没被写过**(空)。
实测 `_enrich()` 每次读项目时**临时合并**前三个 → 说明它们本该在项目对象里。
---
## 二、根因(为什么会"越改越乱")
三条,都不是技术问题:
1. **加功能时"就地扩"** —— 没有"这一层该管什么"的边界。分镜页越塞越多(演员表、锚点、全片设定、出片设置),因为"顺手就放这了"。
2. **新旧并存不删** —— 怕删坏,就留着旧的。结果路由/菜单/文档里同时存在两套,用户选错就在所难免。
3. **数据"能跑就行"** —— 写在哪方便就写哪。分镜写 `storyboards/`、关键帧写 `keyframes/`,导致"上游带入"读不到(这是之前修过的真 bug)。
---
## 三、新架构
### 3.1 三条主线(先分清楚"这是哪一类东西")
```
📦 原料 🎬 生产 📊 交付
(可复用资产) (做一条片子) (看结果)
角色/场景/道具 导演工作室 生产记录
音色/BGM/音效 (唯一主流程) 费用统计
产品/服装/素材 产物中心
```
**判断规则**:
- 能被**多条片子复用** → 进「原料」
- 属于**某一条片子**的 → 进「生产」
- 是**结果/统计** → 进「交付」
### 3.2 生产主线:工作室 9 阶段 → **7 阶段**
**合并的关键点**:`🖼 关键帧设计` + `🖼 关键帧` → **一个页面**(左设计右图)
```
① 💡 创意 ┐
② 🌍 设定 ├→ ③ 📋 创意卡 → ④ 📝 剧本 → ⑤ 🎬 分镜 → ⑥ 🖼 关键帧 → ⑦ 🎞 视频 → ⑧ 🎙 后期
┘
(素材层) (汇聚层) (执行层)
```
**为什么保留 ①②(不合并)**:
- `💡 创意` = **这个故事的种子**(候选 → 选一句)
- `🌍 设定` = **世界的规则**(可跨故事复用,比如"肛肠科病房"这个世界的规则)
- `📋 创意卡` = **两者合起来 → 这个故事的具体构想**
- 职责不同,但**界面上必须写明各自管什么**(现在的毛病是没写)
**为什么合并 ⑥**:
- 用户已经混了。而且"设计"(决定拆几张)和"生成"(出图)本来就是**同一步的两半** —— 设计完立刻要看图对不对。
### 3.3 每阶段一张「职责卡」(界面顶部固定显示)
这是**治乱的核心**:每个页面顶部明确写三行 —— **我从哪来 / 我产出什么 / 下一步用它做什么**。
| # | 阶段 | 输入(从哪来) | 产出 | 下一步用它做什么 |
|---|---|---|---|---|
| ① | 💡 创意 | 参数选型 | 选中的 1 句话 | 扩写成创意卡 |
| ② | 🌍 设定 | — | 世界规则(可复用) | 创意卡 + 剧本的世界观依据 |
| ③ | 📋 创意卡 | 创意 + 设定 | 故事卡(12 字段) | 剧本的**最高依据** |
| ④ | 📝 剧本 | 创意卡 | 按**段落**的剧本 | 分镜按段落做 |
| ⑤ | 🎬 分镜 | 剧本段落 + 导演/风格/演员 | 镜头(Shot) | 关键帧按镜头做 |
| ⑥ | 🖼 关键帧 | 镜头 + 角色/场景资产 | 帧设计(A/B/C)+ 帧图 | 视频的首/尾帧 |
| ⑦ | 🎞 视频 | 关键帧(选首尾)+ 引擎 | 视频片段(可多段) | 后期拼接 |
| ⑧ | 🎙 后期 | 采用的片段 | 成片(拼接/字幕/混音) | 交付 |
### 3.4 菜单重排
**现状 → 目标**
```
🎬 视频工厂 · 资源库(16 项) → 📦 原料(12 项)
摘掉:素材库(空壳)
保留:创意库 / 角色库 / 角色生成器 / 场景生成器 / 场景库 /
真实场地 / 道具库 / 产品库 / 服装库 / 音色库 / BGM / 音效
说明:环境音库、特效预设 暂藏在「更多」里(当前为空,用不上)
🎬 视频工厂 · 流程(12 项) → 🎬 生产(3 项)
★ 导演工作室(唯一主流程)
🔄 人物置换(独立工具,不属主流程)
🔀 流程画布(高级功能,折叠在「高级」下)
隐藏:6 个 ave-flow-*(老流程,跑不通)+ 新建流程(与流程画布重复)
🎬 视频工厂 · 能力中心(4 项) → 🧩 配置(4 项,不变)
🎬 视频工厂 · 运营(2+ 项) → 📊 交付(3 项)
生产记录 / 费用统计 / 产物中心
```
**净减**:`16+12+4+2 = 34 项` → `12+3+4+3 = 22 项`(-35%)
### 3.5 拆掉 112 KB 的巨物
`ave-director.js` 拆成 3 个视图(各管一件事):
```
views/studio-shots-config.js ← 导演系统 / 风格 / 演员表 / 场景 / 锚点(配置)
views/studio-shots-generate.js ← 段落列表(场记板)/ 生成 / 全片设定(动作)
views/studio-shots-table.js ← 分镜表(结果,可编辑)
```
用户在一个页面里**从上到下**走,但代码是三块,各自可独立改。
---
## 四、数据模型(单一真相源)
### 4.1 原则
> **一条片子的全部数据,只在一个文件里。** 媒体文件(图/视频/音频)仍各自落盘,但**路径登记在项目对象里**。
### 4.2 目标结构
**⚠️ 权衡说明(我先自我修正)**:
我最初想把 `shots`(含关键帧和视频的全部版本记录)塞进 `project.json`。
**但那样会让它膨胀到几百 KB** —— 20 个镜头 × 每镜 3 帧 × 每帧多版本,
每次改一个字都要重写整个文件,性能和并发都会出问题。
**所以改成:单一真相源 = 单一目录,而不是单一文件。**
```
projects/{pid}/ ← ★ 一个项目 = 一个目录(这就是"真相源")
│
├ project.json ← 轻量(< 20 KB):元数据 + 各阶段摘要
│ { "project_id","title","stage","created_at","updated_at",
│ "creative":{...}, // 💡 创意(选中的候选)
│ "setting":{...}, // 🌍 设定
│ "creative_card":{...}, // 📋 创意卡
│ "script":{...}, // 📝 剧本(段落)
│ "film_settings":{...}, // 📐 全片设定
│ "post":{...}, // 🎙 后期(时间线 + 成片路径)
│ "cast":[...], // 演员表
│ "history":[...] } // 变更记录
│
├ shots.json ← 重数据:分镜 + 关键帧 + 视频(可单独读写)
│ [ { "shot_id","paragraph_no","visual","duration","cast_slots", ...,
│ "keyframes": { "plan":{A/B/C 提示词},
│ "frames":[{seq,role,versions:[{v,file,adopted}]}] },
│ "videos": [ {segment, versions:[{v,file,adopted,has_voice}]} ] } ]
│
└ media/ ← 媒体文件(或继续用现目录,只登记路径)
keyframes/shot_01/v1.png
videos/shot_01/s1_v1.mp4
```
**为什么这样更好**:
- `project.json` 保持轻量 → 改设定/剧本不会牵动分镜
- `shots.json` 单独读写 → 生成一镜只改这个文件
- **但对外只有"一个项目目录"** → 备份/复制/迁移 = 复制目录
**取消**:
- `project.json` 里的 `keyframes` / `videos` / `assets` 三个死键 → 删除
- `storyboards/{pid}.json` **作为独立位置** → 并入 `projects/{pid}/shots.json`
(同级目录,不再是"另一个地方")
- 前端不再需要 `_enrich()` 跨目录临时合并
### 4.3 收益
- 「上游带入」天然正确(同一目录,不用跨目录捞)
- 「这个项目的全部产物」一个请求拿到
- 备份/迁移/复制项目 = 复制一个目录
- 删项目 = 删一个目录(现在要删 4 个地方)
---
## 五、迁移路径(现有内容怎么落位)
**原则:不删数据,只搬家。每一步都可回滚(先备份)。**
| 现有 | 去向 |
|---|---|
| `storyboards/{pid}.json` 的 shots | 搬进 `project.json` 的 `shots[]` |
| `keyframes/{pid}/{sid}/*.png` | 文件不动;路径登记到 `shots[i].keyframes` |
| `keyframe_videos/{pid}/{sid}/*.mp4` | 文件不动;路径登记到 `shots[i].videos` |
| `storyboard_paper` | 已是 `film_settings` 的来源(已迁移)→ 删键 |
| 6 个 `ave-flow-*` 视图 | **文件保留**(不删仓库文件),仅从菜单摘掉 |
| 4 个空壳视图 | 同上,从菜单摘掉(它们本来也不在菜单里) |
| 运行目录里的测试残留 | 移到 `_trash/` |
---
## 六、实施顺序(4 批,每批独立可验证)
**批 1 · 止血(零风险,先让界面不乱)**
- 菜单重排(摘掉老流程 + 空壳)
- 每个阶段页顶部加「职责卡」(我产出什么 / 下一步用它做什么)
- 合并 `🖼 关键帧设计` + `🖼 关键帧` 为一个页面
**批 2 · 数据归一(动后端,但只加不改)**
- `shots` 搬进 `project.json`(写入时双写,读取时优先新位置)
- 关键帧/视频的路径登记进 `shots[i]`
- 前端改读新位置(保留旧路径兜底)
**批 3 · 拆巨物**
- `ave-director.js` 拆 3 块(配置 / 生成 / 表)
- 每块独立测试
**批 4 · 清理**
- 删死键、废弃 `_enrich()`、测试残留移 `_trash/`
- 更新文档
---
## 七、待你确认的 5 件事
1. **要不要合并「关键帧设计」和「关键帧」?**(我倾向合并 —— 你已经混过一次)
2. **`ave-flow-*` 那 6 个老流程页,从菜单摘掉可以吗?**(文件保留)
3. **「环境音库」当前是空的 —— 先藏起来,还是保留在菜单里?**
4. **「流程画布」(`workflow`) 你还用吗?**(它是另一套编排方式,功能与工作室重叠)
5. **批 1 的菜单重排,要不要现在就做?**(零风险,改完立刻清爽)
---
## 附:本次盘查用到的命令(可复现)
```bash
# 视图规模
ls -la frontend/src/views/*.js | awk '{print $5, $NF}' | sort -rn
# 空壳识别
for f in views/*.js; do [ $(wc -c < $f) -lt 1000 ] && echo "$f"; done
# 后端端点分布
for f in routes/ave*.py; do echo "$(basename $f) $(grep -cE '^@router\.' $f)"; done
# 项目对象结构
python3 -c "import json;d=json.load(open('.../project.json'));print(list(d.keys()))"
```