Files
MediaCrawler/mac-agent-os-main/05_tools/07_matrix/ARCHITECTURE.md
T
butubb 2112c1a870
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
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 带网关原话 / 正常返回)。
2026-10-10 17:12:25 +08:00

213 lines
8.6 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.
# Douyin 全模拟运营系统架构 v1.0
> 最后更新: 2026-05-15
> 基于 AgentOS 多机协同体系
---
## 设计原则
1. **代码走同步,数据留本机** — 脚本在 agent-sync (Gitee),工作区在 agent-local (不参与同步)
2. **四层隔离** — 管理层/流水线层/操作层/数据层,每层独立演进
3. **每账号独立工作区** — 身份、素材、文案、发布记录各自独立
4. **原子操作可组合** — 所有操作遵循 `前置锚点 → 执行 → 后置锚点` 模式
5. **与现有养号系统共存** — 新架构不破坏 `matrix.py` / `nurture_blueprint.py`
---
## 目录结构
```
~/workbuddy-agent-os/
├── agent-sync/ ← Gitee 同步(代码/技能)
│ └── 05_tools/
│ └── 07_matrix/
│ ├── ARCHITECTURE.md ← 本文档
│ ├── scripts/
│ │ ├── matrix.py ← 统一 CLI
│ │ ├── atom_ops.py ← 原子操作库
│ │ ├── nurture_blueprint.py ← 养号蓝图
│ │ ├── douyin_ops.py ← 抖音专用操作
│ │ ├── matrix_modules/ ← 子模块
│ │ ├── pipelines/ ← [新] 流水线脚本
│ │ │ ├── __init__.py
│ │ │ ├── content_publish.py ← 内容发布流水线(待建)
│ │ │ ├── ecom_pipeline.py ← 电商流水线(待建)
│ │ │ └── live_pipeline.py ← 直播流水线(待建)
│ │ └── ops/ ← 平台操作
│ └── blueprints/
│
├── agent-local/ ← 本机数据(不参与同步)
│ └── tools/
│ └── matrix/
│ ├── config/
│ │ └── accounts.yaml ← 账号注册信息
│ ├── identities/ ← 浏览器指纹+登录态
│ │ ├── douyin_01_camo/
│ │ ├── douyin_02_camo/
│ │ └── douyin_camo01/
│ └── accounts/ ← [新] 每账号工作区
│ ├── douyin_01/
│ │ ├── content/ ← 视频素材
│ │ │ ├── raw/ ← 原始素材
│ │ │ ├── edited/ ← 剪辑成品
│ │ │ └── thumbnails/ ← 封面图
│ │ ├── scripts/ ← 脚本文案
│ │ │ ├── drafts/ ← 草稿
│ │ │ └── posted/ ← 已发布
│ │ ├── publish/ ← 发布记录
│ │ │ ├── queue/ ← 待发布队列
│ │ │ └── published/ ← 已发布记录
│ │ └── backup/
│ │ └── cookies/ ← 登录态备份
│ ├── douyin_02/ ← 同上
│ └── douyin_camo01/ ← 同上
```
---
## 四层架构
### Layer 1: 数据层(本机,不同步)
每个账号独立文件夹,包含四类数据:
| 子目录 | 用途 | 数据来源 |
|:-------|:-----|:---------|
| `content/` | 视频素材(raw/edited/thumbnails) | 外部导入 / 剪辑 |
| `scripts/` | 脚本文案(drafts/posted) | AI 生成 / 手动编写 |
| `publish/` | 发布队列 + 历史记录 | 系统自动写入 |
| `backup/` | 登录态备份 | 系统自动备份 |
### Layer 2: 操作层(代码,Gitee 同步)
原子操作(`atom_ops.py` 和 `douyin_ops.py`):
| 操作 | 状态 | 说明 |
|:-----|:----:|:-----|
| browse | ✅ | 浏览视频流 |
| like / collect | ✅ | 点赞/收藏 |
| comment path a | ✅ | 弹窗覆盖层评论(KeyX+Enter) |
| comment path b | ✅ | 全屏视频页评论(scroll+click+Enter) |
| search | ✅ | 搜索并打开 |
| follow | ✅ | 关注 |
| **publish_video** | ⏳ 待建 | 上传视频+文案+封面 |
| **live_sim** | ⏳ 待建 | 直播推流模拟 |
| **product_select** | ⏳ 待建 | 选品/带货 |
| **dm_send** | ⏳ 待建 | 私信 |
### Layer 3: 流水线层(代码,Gitee 同步)
| 流水线 | 状态 | 操作链 |
|:-------|:----:|:-------|
| 养号流水线 (nurture) | ✅ | browse → interact → comment → loop |
| 内容发布流水线 | ⏳ 待建 | 读取队列 → 登录 → 上传 → 发布 → 记录 |
| 电商流水线 | ⏳ 待建 | 选品 → 添加到橱窗 → 推广 |
| 直播流水线 | ⏳ 待建 | OBS推流 → 直播间互动 → 带货 |
### Layer 4: 管理层(规划中)
- 内容排期日历
- 多账号任务调度
- 发布成功率 KPI
---
## 两条评论路径对比
| 维度 | Path A: 弹窗覆盖层 | Path B: 全屏视频页 |
|:-----|:------------------|:------------------|
| 入口 | 精选页→点卡片→弹窗播放器 | 直接打开 `/video/xxx` URL |
| 评论区状态 | 隐藏 (hasCL=false) → KeyX 打开 | 页面上方直接可见 |
| 激活方式 | 坐标 (479,687) 双击 | scrollIntoView + click 输入容器 |
| 输入方式 | pbcopy + Meta+V (共享) | pbcopy + Meta+V (共享) |
| 发送方式 | Enter | Enter |
| 适用场景 | 养号流程中的日常操作 | 收到指定链接后自动评论 |
Route 规则:
```
if URL 包含 /video/ → Path B
else → 检查 hasCL
if hasCL=true → 直接聚焦
if hasCL=false → KeyX 打开 → Path A
```
---
## 并行执行架构(v4.3 新增)
`mc run` 使用 `mc/engine.py` 的 `BatchEngine` 执行批量任务。v4.3 改为并行模式:
### 设计要点
1. **单账号全轮复用浏览器** — 每个账号创建一个 Camoufox 连接,跑完全部轮次再关闭
2. **多账号 `asyncio.gather`** — 所有账号的 `_run_account_all_rounds()` 同时启动
3. **浏览器启停优化** — N 个账号 × R 轮 → 启停 N 次(而非 N×R 次)
### 新旧对比
```
旧版 (v1.1):
轮1: A→B→C (串行) → 间隔 → 轮2: A→B→C → 间隔 → 轮3: A→B→C
# 每步开闭一次浏览器,后一个等前一个完成
# 3账号×3轮 ≈ 500-900s/批次
新版 (v1.2):
A的全部3轮 ──┐
B的全部3轮 ──┼── asyncio.gather ── 同时完成
C的全部3轮 ──┘
# 每账号一个持久浏览器,互不等待
# 3账号×3轮 ≈ 150-350s/批次(约节省 60%)
```
### 代码入口
| 方法 | 职责 |
|:-----|:-----|
| `BatchEngine.run()` | prepare 所有账号 → gather 子任务 → 汇总 |
| `_run_account_all_rounds()` | 每账号:resolve → cookie 检查 → 启动浏览器 → 循环轮次 → 关闭 |
| `run_single(conn=...)` | 单轮单账号:导航到首页 → 执行蓝图步骤 → 返回报告 |
| `_pick_blueprint(round_idx)` | 混合随机/顺序选择本轮蓝图 |
### 共享连接模式
`run_single()` 新增 `conn` 参数:
- `conn=None` → 自行创建并关闭浏览器(独立使用场景)
- `conn=CDPConnector` → 复用已有连接(`_run_account_all_rounds` 内部调用)
### 注意事项
- 并行启动多个 Camoufox 实例时,不要同时调用 `connect()` 中的 `pkill -f camoufox`
(目前 connect() 中始终执行,但因同步调用在 async 之前完成,3 个并行的 connect() 的
pkill 都在任何 Camoufox 启动前执行完毕,因此不会跨进程误杀)
- `BrowserManager.prepare()` 在 `run()` 中统一调用,不在子任务中重复执行
2026-05-15 修改:窗口位置固定从 `accounts.yaml` 读取,不再保存回写。
Camoufox 启动偏移会导致保存的坐标偏离预期(如 0→652, 400→2169)。
| 账号 | 固定位置 |
|:-----|:--------:|
| douyin_01 | (0, 0) |
| douyin_02 | (400, 0) |
| douyin_camo01 | (750, 0) |
---
## 评论故障修复记录(2026-05-15)
### 问题
养号流程中评论发送失败,日志显示 `页面出现评论: ❌`
### 根因
1. **输入方式错误**: `keyboard.type()` 被 Draft.js 忽略,execCommand fallback 不触发 React 状态
2. **发送键错误**: Alt+Enter 不再被新版抖音支持
### 修复
| 文件 | 行 | 改前 | 改后 |
|:-----|:---|:-----|:-----|
| `runner.py` | Step 2 输入 | keyboard.type → execCommand | **pbcopy + Meta+V** |
| `runner.py` | Step 3 发送 | Ctrl+Enter → Alt+Enter | **Enter** |
| `runner.py` | 坐标保存 | 每次结束回写 accounts.yaml | **删除,固定读配置** |
| `nurture_blueprint.py` | op_comment 发送 | Alt+Enter (osascript) | **keyboard.press Enter** |