feat(monitor): 抖音 Web 接口客户端 —— 绕开爬虫子进程,直接发 HTTP
移植自 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:
@@ -0,0 +1,213 @@
|
||||
# 统一执行管道 v5 — 设计文档
|
||||
|
||||
> 目标:所有操作(nurture/collect/login/logout/comment/like)共享同一个执行管道,
|
||||
> 从预检 → 确认 → 发送 → 启动验证 → 轮询 → 完成,每步都有状态反馈。
|
||||
|
||||
---
|
||||
|
||||
## 一、问题现状
|
||||
|
||||
之前每次都在各个视图里独立写 fetch → 显示结果,没有统一的执行体验:
|
||||
|
||||
| 视图 | 预检 | 确认 | 发送 | 启动验证 | 轮询 | 完成 |
|
||||
|:-----|:----:|:----:|:----:|:--------:|:----:|:----:|
|
||||
| 养号执行 | ❌ | ✅(confirm) | ❌旧端点 | ❌ | ❌旧API | ❌ |
|
||||
| 信息采集 | ❌ | ❌ | ❌旧端点 | ❌ | ❌ | ❌ |
|
||||
| 定向评论 | ❌ | ❌ | ❌旧端点 | ❌ | ❌ | ❌ |
|
||||
| 收藏点赞 | ❌ | ❌ | ❌旧端点 | ❌ | ❌ | ❌ |
|
||||
| 联邦指挥台 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
|
||||
每个视图都自己写了一套 fetch → log 的逻辑,代码重复,体验不一致。
|
||||
|
||||
---
|
||||
|
||||
## 二、统一执行管道设计
|
||||
|
||||
### 2.1 前端组件
|
||||
|
||||
```javascript
|
||||
// 所有执行按钮统一使用这个组件
|
||||
const pipeline = createExecutionPipeline(containerElement, {
|
||||
// ── 必需 ──
|
||||
type: 'nurture', // 操作类型
|
||||
getAccounts: () => [...], // 获取选中账号的回调
|
||||
|
||||
// ── 可选 ──
|
||||
title: '养号执行', // 对话框标题
|
||||
onPlan: (plan) => {}, // 确认前的计划预览(可自定义)
|
||||
commandTemplate: (accounts, params) => {...}, // 自定义命令
|
||||
|
||||
// ── 回调 ──
|
||||
onStatusChange: (phase, data) => {}, // 阶段变化回调
|
||||
});
|
||||
```
|
||||
|
||||
### 2.2 执行管道阶段
|
||||
|
||||
```
|
||||
阶段名 前端显示 后端动作
|
||||
────────────────────────────────────────────────────────────────────
|
||||
IDLE 就绪 —
|
||||
PREFLIGHT 🔍 预检机器状态... 调用 /api/ops/machines
|
||||
PLAN 📋 显示执行计划 + 机器状态 调用 /api/ops/run (dry_run)
|
||||
CONFIRM 🖥️ 弹出确认对话框 用户确认/取消
|
||||
DISPATCHING 📡 正在发送命令到 XX 机器... 调用 /api/ops/run
|
||||
DISPATCHED ✅ 命令已发送 (PID:12345) 记录PID
|
||||
LAUNCH_WAIT ⏳ 等待进程启动... 等待2秒
|
||||
LAUNCH_CHECK 🔍 检查进程是否启动... 查进程表/SSH pgrep
|
||||
RUNNING 🟢 进程运行中 (5/10轮) 轮询 /api/ops/status
|
||||
BROWSER_CHECK 🌐 浏览器已启动 (3进程) 查浏览器进程
|
||||
COMPLETED ✅ 执行完成: 8成功/2失败/120s 读取最终结果
|
||||
FAILED ❌ 执行失败: 错误信息
|
||||
```
|
||||
|
||||
### 2.3 后端 API
|
||||
|
||||
```
|
||||
POST /api/ops/run
|
||||
→ Phase 1-2: dispatch
|
||||
→ Phase 3-5: 通过 /api/ops/status 和 /api/ops/machines 轮询
|
||||
|
||||
GET /api/ops/status?account=xxx
|
||||
→ 返回命令的完整状态 + 阶段
|
||||
|
||||
GET /api/ops/machines
|
||||
→ 返回所有机器的浏览器/命令/槽位状态
|
||||
```
|
||||
|
||||
### 2.4 前端执行流程代码
|
||||
|
||||
```javascript
|
||||
export async function executeCommand(options) {
|
||||
const { type, accounts, params, logEl, statusEl } = options;
|
||||
|
||||
// Phase 0: Preflight
|
||||
setPhase('preflight', '🔍 预检机器状态...');
|
||||
const machines = await api('/api/ops/machines');
|
||||
|
||||
// Phase 1: Plan + Confirm
|
||||
setPhase('plan', '📋 生成执行计划...');
|
||||
const plan = await api('/api/ops/run', {type, accounts, params: {...params, dry_run: true}});
|
||||
const confirmed = await showConfirmDialog(plan, machines);
|
||||
if (!confirmed) { setPhase('cancelled', '⏸ 已取消'); return; }
|
||||
|
||||
// Phase 2: Dispatch
|
||||
setPhase('dispatching', '📡 发送命令...');
|
||||
const result = await api('/api/ops/run', {type, accounts, params});
|
||||
showDispatchResult(result);
|
||||
|
||||
// Phase 3-5: Monitor
|
||||
await monitorExecution(result, (phase, msg) => {
|
||||
setPhase(phase, msg);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、确认对话框标准
|
||||
|
||||
所有执行操作共享同一个确认对话框格式:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 📋 养号执行确认 │
|
||||
│ │
|
||||
│ 🟢 chengzigedeAir (本机) │
|
||||
│ 槽位: 2/3 可用 │
|
||||
│ 浏览器: 1 个运行中 │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ 🖥️ douyin_test → 10轮 │ │
|
||||
│ │ 蓝图: douyin_daily │ │
|
||||
│ │ 窗口: 槽位2 (100,0) │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ │
|
||||
│ 🔴 5kechengdeAir (远程) │
|
||||
│ 不可达(SSH 超时) │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ ❌ xhs_01 → 无法执行 │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ │
|
||||
│ [取消] [确认执行] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、执行状态反馈标准
|
||||
|
||||
每台机器独立显示状态条:
|
||||
|
||||
```
|
||||
📡 养号执行状态
|
||||
────────────────────────────────────────────────
|
||||
🖥️ chengzigedeAir 🟢 运行中 (5/10轮, 3成功/2失败)
|
||||
☁️ 5kechengdeAir 🔴 SSH 不可达 (已跳过)
|
||||
☁️ 7kecheng ⏳ 浏览器启动中... (8s)
|
||||
────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、集成到所有视图
|
||||
|
||||
| 视图 | 按钮 | 替换为统一管道 |
|
||||
|:-----|:-----|:--------------|
|
||||
| 养号执行 | 执行选中 / 全部启用 | executeCommand('nurture', ...) |
|
||||
| 信息采集 | 采集选中 / 全部 | executeCommand('collect', ...) |
|
||||
| 信息采集 | 登录选中 | executeCommand('login', ...) |
|
||||
| 定向评论 | 执行评论 | executeCommand('comment', ...) |
|
||||
| 收藏点赞 | 执行点赞 | executeCommand('like', ...) |
|
||||
| 联邦指挥台 | 批量执行 | executeCommand(selectedType, ...) |
|
||||
|
||||
---
|
||||
|
||||
## 六、v6 队列与强制停止(2026-06-25 新增)
|
||||
|
||||
### 6.1 每台机器一个队列
|
||||
|
||||
```
|
||||
CommandBus 收到新命令
|
||||
↓
|
||||
MachineSession.is_busy?
|
||||
├── 否 → 立即执行(send_local / send_remote)
|
||||
└── 是 → status=queued,加入 queued_cmds 排队
|
||||
↓
|
||||
当前命令完成 → poll() 检测到 terminal
|
||||
↓
|
||||
_start_next() → 出队 → send() → 执行
|
||||
```
|
||||
|
||||
### 6.2 强制停止
|
||||
|
||||
```
|
||||
用户点击"停止"
|
||||
↓
|
||||
POST /api/ops/cancel/{run_id}
|
||||
↓
|
||||
session.cancel(cmd)
|
||||
├── kill(cmd.pid) ← 杀 mc run 进程
|
||||
├── pkill -f {account_id} ← 清 Camoufox 浏览器
|
||||
└── _start_next() ← 启动队列下一条
|
||||
```
|
||||
|
||||
### 6.3 已实现状态
|
||||
|
||||
| 组件 | 状态 |
|
||||
|:-----|:----:|
|
||||
| MachineSession.queue | ✅ v6 已实现 |
|
||||
| cancel() 杀进程+清浏览器 | ✅ v6 已实现 |
|
||||
| poll() 完成自动调度下一条 | ✅ v6 已实现 |
|
||||
| get_queue_info() API | ✅ v6 已实现 |
|
||||
| 前端显示队列/停止按钮 | ⬜ 待实现 |
|
||||
|
||||
## 七、实施步骤
|
||||
|
||||
| 步骤 | 内容 | 文件 |
|
||||
|:-----|:-----|:------|
|
||||
| 1 | 创建 `services/execution_pipeline.py` — 后端管道引擎 | 后端 |
|
||||
| 2 | 创建 `frontend/src/components/execution-pipeline.js` — 前端管道组件 | 前端 |
|
||||
| 3 | 更新 `routes/ops.py` — 对接管道引擎的 phased status | 后端 |
|
||||
| 4 | 更新 inline `index.html` — 所有执行按钮用统一管道 | 前端 |
|
||||
| 5 | 更新 migrated views — 所有执行按钮用统一管道 | 前端 |
|
||||
| 6 | 集成测试 — 所有操作类型通过管道验证 | 测试 |
|
||||
Reference in New Issue
Block a user