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
@@ -0,0 +1,421 @@
# Matrix 养号系统 — 账号登录管理标准化 SOP
> **最后更新**:2026-05-01 15:35
> **版本**:v1.0.0
> **适用场景**:所有抖音账号的登录状态检查、重新登录、登录验证、账号切换
> **核心原则**:每一步可执行、可复现、可验证
---
## 目录
1. [前置环境检查](#1-前置环境检查)
2. [账号登录状态查看](#2-账号登录状态查看)
3. [Cookie 过期账号重登录流程](#3-cookie-过期账号重登录流程)
4. [从未登录账号首次登录流程](#4-从未登录账号首次登录流程)
5. [Camoufox 账号登录流程](#5-camoufox-账号登录流程)
6. [登录验证标准流程](#6-登录验证标准流程)
7. [账号切换标准流程](#7-账号切换标准流程)
8. [故障排查表](#8-故障排查表)
---
## 1. 前置环境检查
**每次做任何操作前,先执行以下检查。** 只有全部通过才继续。
### 1.1 Python 环境
```bash
# 确认 python 可用
/Users/chengzige/.workbuddy/binaries/python/envs/agent-os/bin/python3 --version
# 确认 patchright 安装(核心依赖,缺少则所有操作不可用)
/Users/chengzige/.workbuddy/binaries/python/envs/agent-os/bin/python3 -c "from patchright import __version__; print('patchright', __version__)"
```
**预期输出**:`patchright X.Y.Z`
### 1.2 Chrome 浏览器
```bash
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version
```
**预期输出**:`Google Chrome X.Y.Z`
**当前版本**(2026-04-30):Google Chrome 147.0.7727.138
### 1.3 账号配置文件完整性
```bash
cat ~/workbuddy-agent-os/agent-local/tools/matrix/config/accounts.yaml
```
确认:
- 所有账号配置的 `enabled` 字段与实际需求一致
- 每个账号的 `profile_dir` 字段正确
- 每个账号的 `port` 端口不冲突(Chrome: 9222-9225 / Camoufox: 9301-9302)
### 1.4 Profile 目录存在性
```bash
ls -d ~/workbuddy-agent-os/agent-local/tools/matrix/profiles/*/
```
**预期**:至少 `account_01`、`douyin_02`、`camoufox_01`、`camoufox_02` 存在。
### 1.5 工具脚本路径
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
ls $TOOL/scripts/switch_account.py
ls $TOOL/scripts/task_engine.py
ls $TOOL/blueprints/douyin_browse_v2.json
```
**预期**:文件均存在。
---
## 2. 账号登录状态查看
### 2.1 查看 DB 缓存状态
```bash
cd ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
/Users/chengzige/.workbuddy/binaries/python/envs/agent-os/bin/python3 scripts/switch_account.py --status
```
输出解读:
| DB 状态 | 含义 | 下一步 |
|---------|------|--------|
| `active` | 上次登录已验证有效 | → 执行 [2.2](#22-实时登录验证) 确认实际有效 |
| `cookie_expired` | Cookie 已过期 | → 跳转 [3. Cookie 过期重登录](#3-cookie-过期账号重登录流程) |
| `needs_login` | 该账号从未成功登录 | → 跳转 [4. 首次登录](#4-从未登录账号首次登录流程) |
| `inactive` | 账号已禁用 | 跳过,除非手动启用 |
### 2.2 实时登录验证
DB 状态只是缓存,**必须实时验证**。对每个要使用的账号执行:
```bash
# 1. 启动浏览器(如果未运行)
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
python $TOOL/scripts/switch_account.py --method profile --target douyin_01 --port 9222
```
**这一步会**:
1. 关闭端口上的旧 Chrome
2. 启动新 Chrome(指定 Profile)
3. 注入浏览器指纹
4. 自动导航到 `douyin.com`
5. 检查 `[data-e2e="user-avatar"]` 是否存在
6. 更新 DB 状态
**关键输出**:
```
[4/4] 验证登录状态...
✅ 已登录(检测到头像)
```
或
```
⚠️ 未登录,需要手动登录或 Cookie 注入
```
---
## 3. Cookie 过期账号重登录流程
**适用账号**:`douyin_01`(当前状态:cookie_expired)
**适用账号**:任何显示 `cookie_expired` 的 Chrome 账号
### 3.1 启动浏览器
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
# 关闭端口残留进程
kill -9 $(lsof -ti :9222) 2>/dev/null
# 以手动登录模式启动 Chrome(不关闭窗口)
python $TOOL/scripts/launch_chrome.sh douyin_01 9222
```
### 3.2 手动登录
浏览器窗口打开后:
1. **不要关闭窗口**
2. 导航到 `https://www.douyin.com/`
3. 用手机扫码或验证码登录
4. 等待登录成功(首页显示个人头像)
### 3.3 验证并导出 Cookie
**不要关闭浏览器**,在新终端执行:
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
# 验证登录
python $TOOL/scripts/switch_account.py --status
# 导出 Cookie
python $TOOL/scripts/switch_account.py --export-cookies --target douyin_01 --port 9222
```
**预期输出**:
```
✅ 导出 10+ 个 Cookie → ~/.../data/cookies/douyin_01_cookies.json
```
### 3.4 更新 DB 状态
导出 Cookie 后,手动确认 DB 已更新为 `active`:
```bash
python $TOOL/scripts/switch_account.py --status | grep douyin_01
```
**预期**:`DB状态: active`
### 3.5 留窗验证 5 分钟
浏览器不关闭,5 分钟后刷新 `douyin.com`,确认:
- 头像仍显示
- 页面可正常滚动
- 退出登录不被踢
---
## 4. 从未登录账号首次登录流程
**适用账号**:`douyin_02`(当前状态:needs_login)
### 4.1 启动浏览器
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
# 关闭端口残留
kill -9 $(lsof -ti :9223) 2>/dev/null
# 启动(指定该账号的 port 和 profile)
python $TOOL/scripts/launch_chrome.sh douyin_02 9223
```
### 4.2 手动登录
1. 浏览器窗口打开后,导航到 `https://www.douyin.com/`
2. 使用账号 `18500003366` 手机验证码登录
3. 注意:**抖音可能要求短信验证**,需要手机验证码
### 4.3 验证并导出 Cookie
同 [3.3](#33-验证并导出-cookie),将 `douyin_01` 替换为 `douyin_02`,`9222` 替换为 `9223`。
### 4.4 首次登录特别验证
由于是首次登录(needs_login),额外验证:
1. 确认 Profile 目录已生成登录数据
2. 关闭浏览器,重新用 switch_account.py 启动
3. 确认自动登录成功
```bash
# 关闭浏览器后,重新用标准化流程启动
python $TOOL/scripts/switch_account.py --method profile --target douyin_02 --port 9223
```
**预期**:
```
[4/4] 验证登录状态...
✅ 已登录(检测到头像)
✅ 切换完成: douyin_02
CDP: http://localhost:9223
登录: ✅
```
---
## 5. Camoufox 账号登录流程
**适用账号**:`douyin_camo01`、`douyin_camo02`
### 5.1 检查 Camoufox 环境
```bash
# 检查二进制
ls -la ~/Library/Caches/camoufox/Camoufox.app/Contents/MacOS/camoufox
# 检查 Python 包
/Users/chengzige/.workbuddy/binaries/python/envs/agent-os/bin/python3 -c "import camoufox; print(camoufox.__version__)"
```
### 5.2 启动 Camoufox 浏览器
```bash
# 注意:Camoufox 启动方式不同于 Chrome
# 使用 camoufox_manager 或直接运行的 camoufox_server
/Users/chengzige/.workbuddy/binaries/python/envs/agent-os/bin/python3 \
~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/camoufox_server.py \
--account douyin_camo01
```
### 5.3 手动登录
同 [3.2](#32-手动登录),导航到 `douyin.com` 并登录。
### 5.4 验证
Camoufox 是 Firefox 内核,验证方式:
```bash
# 检查 Cookie 文件是否存在
ls -la ~/workbuddy-agent-os/agent-local/tools/matrix/data/cookies/*camoufox*
```
### 5.5 注意事项
- Camoufox 当前的 DB 状态显示 `active` 但浏览器未运行,这是**缓存数据失准**
- Camoufox 登录后需在浏览器打开状态下验证
- Camoufox 与 Chrome 的 Cookie 不互通
---
## 6. 登录验证标准流程
无论哪种方式登录后,三步验证:
### 6.1 Cookie 验证
```python
# 从 DB 确认状态
python scripts/switch_account.py --status | grep <account_id>
```
### 6.2 功能验证
执行蓝图前 2 步,确认操作正常:
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
# 只测试 goto_home + wait_watch(不涉及点赞等)
python $TOOL/scripts/task_engine.py \
--blueprint douyin_browse_v2 \
--account douyin_01 \
--steps 2
```
### 6.3 持久性验证
**关键检验**:关闭浏览器后重新启动,确认能自动登录。
```bash
# 关闭浏览器
kill -9 $(lsof -ti :9222) 2>/dev/null
sleep 2
# 重新启动
python $TOOL/scripts/switch_account.py --method profile --target douyin_01 --port 9222
```
---
## 7. 账号切换标准流程
### 7.1 Profile 切换(推荐,稳定)
```bash
# 从 douyin_01 切换到 douyin_02
python $TOOL/scripts/switch_account.py --method profile --target douyin_02 --port 9223
```
**流程**:关闭旧 Chrome → 启动新 Profile → 注入指纹 → 验证登录
### 7.2 Cookie 注入切换(实验性,快速)
```bash
# 前提:Cookie 文件已导出且未过期
python $TOOL/scripts/switch_account.py --method cookie --target douyin_01 --port 9222
```
**注意**:Cookie 切换要求浏览器已运行。如果 Cookie 过期,自动恢复原 Cookie。
---
## 8. 故障排查表
### 8.1 DB 状态与实际情况对应指南
| DB 状态 | 实际可能性 | 操作 |
|---------|-----------|------|
| `active` | ✅ Cookie 有效,浏览器可运行 | 直接使用 |
| `active` | ❌ Cookie 已过期,缓存未更新 | 执行 [2.2 实时验证](#22-实时登录验证) |
| `cookie_expired` | ✅ 确实过期 | 执行 [3. 重登录](#3-cookie-过期账号重登录流程) |
| `needs_login` | ✅ 从未登录过 | 执行 [4. 首次登录](#4-从未登录账号首次登录流程) |
| `needs_login` | ⚠️ 可能已经手动登录过但 DB 未更新 | 执行 [2.2 实时验证](#22-实时登录验证) |
| 端口无进程 | — | 执行 [2.2 实时验证](#22-实时登录验证) (自动启动浏览器) |
### 8.2 常见错误处理
| 错误 | 根因 | 修复 |
|------|------|------|
| `patchright 未安装` | Python venv 缺少包 | `/Users/chengzige/.workbuddy/binaries/python/envs/agent-os/bin/pip install patchright` |
| `DouyinOps.like() got an unexpected keyword argument 'probability'` | like()/collect() API 参数不兼容 | 更新蓝图去掉 probability 参数,或用旧版 API |
| `连接失败 / 端口无响应` | Chrome 未启动或端口错误 | 先检查端口占用、再启动 |
| `视频加载超时` | 网络问题或页面元素变更 | 增加等待时间或检查 douyin.com 是否可访问 |
| `SEARCH_KEYWORD 未替换` | 蓝图模板变量未配置 | 在 accounts.yaml 中为账号配置 search_keywords 字段 |
### 8.3 当前系统状态基线(2026-05-01)
| 账号 | Profile | 端口 | DB 状态 | 浏览器 | 上次活跃 |
|------|---------|------|---------|--------|---------|
| douyin_01 | account_01 | 9222 | cookie_expired | ⏹ 未运行 | 2026-04-30 14:50 |
| douyin_02 | douyin_02 | 9223 | needs_login | ⏹ 未运行 | 2026-04-30 09:02 |
| douyin_camo01 | camoufox_01 | 9301 | active(缓存) | ⏹ 未运行 | None |
| douyin_camo02 | camoufox_02 | 9302 | active(缓存) | ⏹ 未运行 | None |
---
## 附录 A:标准化检查清单
每次操作前后,对照此清单确认:
- [ ] 前置环境检查通过(patchright、Chrome、配置)
- [ ] 账号当前 DB 状态已记录
- [ ] 浏览器进程状态已记录(运行/未运行)
- [ ] 操作前备份了当前状态
- [ ] 每一步的标准命令已执行
- [ ] 操作后状态已重新验证
- [ ] DB 状态已更新到最新
- [ ] 工作日志已记录(`└ 04_memory/logs/`)
---
## 附录 B:可用命令速查
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
# 查看状态
python $TOOL/scripts/switch_account.py --list # 列出所有账号
python $TOOL/scripts/switch_account.py --status # 查看实时状态
# 启动 + 验证
python $TOOL/scripts/switch_account.py --method profile --target douyin_01 --port 9222
# Cookie 管理
python $TOOL/scripts/switch_account.py --export-cookies --target douyin_01 --port 9222
python $TOOL/scripts/switch_account.py --method cookie --target douyin_01 --port 9222
# 执行蓝图
python $TOOL/scripts/task_engine.py --blueprint douyin_browse_v2 --account douyin_01
# 全面测试
python $TOOL/scripts/full_test.py --skip-launch --account-only douyin_01
# 手动启动浏览器
bash $TOOL/scripts/launch_chrome.sh douyin_01 9222
```
@@ -0,0 +1,119 @@
# AgentOS Matrix 反检测技术方案 v1.0.0
> 最后更新:2026-05-01
> 基于旧版完整手册 + 当前系统现状分析
## 一、当前系统缺失的反检测措施
| 层级 | 缺失项 | 风险 | 旧版实施方案 |
|------|--------|------|-------------|
| **浏览器层** | CDP调试标记 (`--remote-debugging-port`) | 🔴 高 | 已修复:改 Playwright 原生启动 |
| **浏览器层** | `navigator.webdriver` 未覆写 | 🔴 高 | CDP注入 `Object.defineProperty(navigator, 'webdriver', {get:()=>false})` |
| **浏览器层** | `navigator.platform` 与 UA 不一致 | 🟠 中 | 覆写为 `Linux armv8l` (Android) 或 `iPad` |
| **浏览器层** | `navigator.standalone` 未覆写 | 🟠 中 | 强制返回 `false` |
| **浏览器层** | 触摸事件模拟 | 🟠 中 | `Emulation.setTouchEmulationOverride` |
| **行为层** | 无随机动作延迟 | 🔴 高 | `action_delay: [200, 5000]ms` 随机延迟 |
| **行为层** | 无行为画像配置 | 🟠 中 | 每小时点赞上限、评论上限、每日总量 |
| **行为层** | 无观看时长随机化 | 🟠 中 | `view_duration: [5000, 60000]ms` |
| **行为层** | 无打字速度模拟 | 🟠 中 | `typing_speed: [50, 200]ms/char` |
| **网络层** | 无独立IP代理 | 🔴 高 | 静态住宅IP + socks5 代理 |
| **网络层** | 无账号熔断机制 | 🟠 中 | 连续失败3次自动冷却30分钟 |
| **DOM层** | App跳转拦截未完整 | 🟠 中 | 已实现 Fetch 拦截 |
| **DOM层** | 弹窗清理选择器不全 | 🟠 中 | 需要扩展覆盖更多弹窗类型 |
| **操作层** | 搜索后跳转到 `so.douyin.com` | 🟠 中 | 已修复:用首页搜索栏 |
## 二、浏览器启动配置(已修复)
```python
# ✅ 正确:不使用 --remote-debugging-port
# ✅ 正确:在 launch_persistent_context 中设置平板参数
ctx = await pw.chromium.launch_persistent_context(
user_data_dir=profile,
channel="chrome",
headless=False,
viewport={"width": 702, "height": 783},
device_scale_factor=2,
is_mobile=True,
has_touch=True,
user_agent="Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X)...",
locale="zh-CN",
args=["--no-first-run", "--no-default-browser-check"],
)
```
## 三、需要新增的反检测措施
### 3.1 浏览器指纹覆写
```python
async def apply_fingerprint_protection(page):
"""覆写浏览器指纹特征"""
await page.evaluate("""() => {
// 隐藏自动化标记
Object.defineProperty(navigator, 'webdriver', { get: () => false });
// 平台一致性
Object.defineProperty(navigator, 'platform', { get: () => 'iPad' });
// 隐藏PWA能力
Object.defineProperty(navigator, 'standalone', { get: () => false });
// 修改插件检测
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
// 修改语言
Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh'] });
}""")
```
### 3.2 拟人化行为参数
```python
BEHAVIOR_PROFILE = {
"douyin_01": {
"action_delay": [0.5, 3.0], # 动作间隔秒
"view_duration": [5, 60], # 浏览停留秒
"typing_speed": [50, 200], # 打字 ms/字符
"max_likes_per_hour": 15,
"max_comments_per_hour": 3,
"max_collects_per_hour": 5,
"active_hours": [8, 23],
"max_daily_actions": 150,
}
}
```
### 3.3 账号熔断器
```python
class CircuitBreaker:
def __init__(self, account_id):
self.failures = 0
self.max_failures = 3
self.cooldown = 1800 # 30分钟
```
### 3.4 IP代理配置
需要为每个账号配置独立静态住宅 IP(当前未配置)。
## 四、实施优先级
| 优先级 | 措施 | 预估工时 |
|--------|------|---------|
| P0 | 浏览器指纹覆写 (webdriver/platform/standalone) | 30分钟 |
| P0 | 随机动作延迟 (action_delay) | 30分钟 |
| P1 | 行为画像配置 (每小时上限/每日上限) | 30分钟 |
| P1 | 弹窗清理扩展 | 15分钟 |
| P1 | 账号熔断器 | 30分钟 |
| P2 | IP代理接入 | 需要外部采购 |
| P2 | 多浏览器内核轮换 | 后续阶段 |
## 五、与旧版对比总结
旧版完整方案包含:隐身容器 (Colima+Docker) → 反检测浏览器 (Camoufox/Patchright) → 移动端伪装 → 拟人化行为 → IP代理 → 熔断机制
当前系统只实现了基础的浏览器控制和页面交互,**反检测层几乎全部缺失**。
需要在后续迭代中逐步补齐。
@@ -0,0 +1,76 @@
# Matrix 基础操作约定 v1.0
> **最后更新**:2026-05-10
> **适用范围**:所有原子操作
---
## 约定 1:窗口焦点(每次操作前必做)
**规则**:所有键盘操作、鼠标操作、页面交互前,必须先确保浏览器窗口在最前端。
**实现**:每个原子操作的 pre-check 阶段,先执行 `_activate_window()`。
```python
async def _activate_window():
"""AppleScript 将 Camoufox 窗口置顶"""
script = 'tell application "System Events" to set frontmost of every process whose name contains "camoufox" to true'
subprocess.run(['osascript', '-e', script], capture_output=True, timeout=3)
```
这个调用放在每个原子操作执行函数的最前面。
---
## 约定 2:双击模式
**规则**:所有"点击进入"类操作使用双击模式。
- 第1次点击:获取焦点 / 激活窗口 / 使元素可交互
- 等待 1 秒
- 第2次点击:真正的操作
---
## 约定 3:优雅关闭浏览器
**规则**:每次脚本启动前:
1. 检测是否有运行的 Camoufox 进程
2. 如有 → `pkill -f camoufox` → 等待 3 秒
3. 删除 `.parentlock` 锁文件
4. 再启动
**实现**:在 `CDPConnector.connect()` 中自动执行。
---
## 约定 4:错误报告格式
当任何一个原子操作失败时,**必须**按以下格式报告,不自行修复:
```
❌ 操作失败报告
═══════════════════
当前状态: [状态码]
当前页面: [URL]
当前页面标题: [title]
失败操作: [操作名]
失败阶段: [pre/execute/post]
失败原因: [具体错误信息/不符合的特征码]
预期后置状态: [状态码]
实际检测状态: [状态码]
环境: 窗口[W×H] 位置([X],[Y]) 激活态[Y/N]
═══════════════════
等待指示...
```
---
## 约定 5:交互测试流程
每个原子操作测试流程:
1. 我启动浏览器到起始状态
2. 我报告当前状态的特征码,你确认
3. 你指示 "可以执行"
4. 我执行该原子操作
5. 我报告结果(成功/失败+特征码变化)
6. 你指示下一步
@@ -0,0 +1,111 @@
# Blueprint 蓝图设计规范 v1.0.0
> 最后更新:2026-05-01
> 设计目标:可组合、可验证、可追踪的脚本化操作流
## 一、设计原则
每个蓝图由若干 **原子操作步骤** 组成,每个步骤必须记录:
```
前置条件 (pre-condition) → 执行操作 → 后置条件 (post-condition)
↑ ↓
状态验证锚点 状态验证锚点
```
## 二、步骤格式
```json
{
"step_id": 1,
"op": "goto_home",
"args": {},
"pre": {
"page_mode": ["unknown", "grid", "player", "search"],
"login_required": false,
"check": []
},
"post": {
"page_mode": "grid",
"anchors": ["alink-item"],
"video_found": false,
"description": "到达首页卡片列表"
},
"verify": {"mode": "anchor", "anchor": "grid_page", "timeout": 15},
"on_fail": "retry(2)"
}
```
## 三、预定义页面模式
| 模式 | 锚点元素 | 描述 |
|------|---------|------|
| `unknown` | 无 | 未知/空白页 |
| `grid` | `[data-e2e="alink-item"]` | 首页卡片列表 |
| `player` | `[data-e2e="video-player-digg"]` | 视频播放器(带点赞/收藏按钮) |
| `search` | `[data-e2e="searchbar-input"]` | 搜索页 |
| `login_popup` | `[class*="login"]` 覆盖层 | 登录弹窗 |
## 四、预定义的原子操作
| 操作 | 前置条件 | 后置条件 | 说明 |
|------|---------|---------|------|
| `goto_home` | 任意 | `grid` | 导航到首页,激活手机模式 |
| `search(keyword)` | `grid` | `search` | 输入关键词搜索 |
| `open_video` | `grid` / `search` | `player` | 打开第一个找到的视频 |
| `like` | `player` | `player`(已赞) | 点赞当前视频 |
| `collect` | `player` | `player`(已收藏) | 收藏当前视频 |
| `next_video` | `player` | `player`(新视频) | 切换下一条(手机模式:全局流) |
| `comment(text)` | `player` + 登录 | `player`(已评论) | 输入评论并发送 |
| `go_back` | `player` | `grid` / `search` | 返回上一页 |
| `wait_watch(seconds)` | `player` | `player` | 观看一段时间 |
| `scroll_feed` | `grid` | `grid` | 首页滚动 |
## 五、完整流程示例:douyin_nurture_v1
```
start: unknown
│
├── goto_home (pre:unknown → post:grid)
│ └── 激活手机模式:设UA→导航→刷新→等渲染
│
├── search(关键词) (pre:grid → post:search)
│
├── open_video (pre:search → post:player)
│
├── like (pre:player → post:player)
├── wait_watch(8s) (pre:player → post:player)
│
├── next_video (pre:player → post:player) ← 手机模式全局流
│
├── like (pre:player → post:player)
├── collect (pre:player → post:player)
│
├── go_back (pre:player → post:search)
│
└── open_video (pre:search → post:player)
├── like
├── collect
└── next_video
```
## 六、状态持久化
每次养号运行后,生成 `.state.json` 记录:
```json
{
"run_id": "20260501_093000",
"account": "douyin_01",
"blueprint": "douyin_nurture_v1",
"steps": [
{"step": 1, "op": "goto_home", "pre": "unknown", "post": "grid", "success": true}
],
"login_method": "profile",
"device_mode": "mobile_tablet",
"duration_sec": 76
}
```
@@ -0,0 +1,294 @@
# Camoufox 登录管理方案
## 背景
当前矩阵养号系统基于 Chrome + Profile 切换(方案A)与 Cookie 注入(方案B)。为提升反检测能力、支持多浏览器内核,计划引入 **Camoufox**(Firefox 内核反检测浏览器)作为新的登录管理方式。
## 目标
1. 提供 Camoufox 的安装、配置指南
2. 扩展账号配置,支持 `browser_type` 字段(`chrome` / `camoufox`)
3. 实现 Camoufox 实例的启动、指纹注入、CDP 连接
4. 提供登录状态验证与 Cookie 管理
5. 演示如何添加两个新账号并尝试 Camoufox 登录
## 1. 安装与配置
### 1.1 安装 Python 包
```bash
# 已安装的 camoufox 包可能需要更新
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 -m pip install -U camoufox[geoip]
```
### 1.2 下载 Camoufox 浏览器
```bash
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 -m camoufox fetch
```
这将把浏览器可执行文件下载到 `~/Library/Caches/camoufox/`。
### 1.3 验证安装
```bash
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 -m camoufox path
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 -c "import camoufox; print(camoufox.__version__)"
```
## 2. 账号配置扩展
### 2.1 修改 `config/accounts.yaml`
在现有配置基础上增加 `browser_type` 字段,并为 Camoufox 账号指定独立端口(建议从 9300 开始,避免与 Chrome 端口冲突)。
```yaml
# 新增全局配置块
camoufox:
# Camoufox 可执行文件路径(自动检测)
executable: ~/Library/Caches/camoufox/camoufox
# 默认视口(Camoufox 使用 screen 参数)
screen:
width: 702
height: 783
# 默认地理设置
geo:
timezone: Asia/Shanghai
locale: zh-CN
# 视口配置(Chrome 用)
viewport:
width: 702
height: 783
mobile: false
accounts:
# ── 原有 Chrome 账号 ──
- id: douyin_01
platform: douyin
phone: "18513308610"
port: 9222
browser_type: chrome
profile_dir: account_01
enabled: true
notes: 抖音主号(Chrome)
# ── 新增 Camoufox 账号(两个) ──
- id: douyin_camo01
platform: douyin
phone: "" # 待填写
port: 9301
browser_type: camoufox
profile_dir: camoufox_01 # 对应 ~/matrix/profiles/camoufox_01
proxy: socks5://127.0.0.1:7890 # 可选,推荐使用静态住宅IP
screen: # 覆盖全局 screen 设置
width: 702
height: 783
geo:
timezone: Asia/Shanghai
locale: zh-CN
enabled: true
notes: Camoufox 测试账号1
- id: douyin_camo02
platform: douyin
phone: ""
port: 9302
browser_type: camoufox
profile_dir: camoufox_02
proxy: null
screen:
width: 702
height: 783
geo:
timezone: Asia/Shanghai
locale: zh-CN
enabled: true
notes: Camoufox 测试账号2
```
### 2.2 创建 Profile 目录
```bash
mkdir -p ~/matrix/profiles/camoufox_01
mkdir -p ~/matrix/profiles/camoufox_02
```
## 3. Camoufox 管理器脚本
创建 `~/matrix/scripts/camoufox_manager.py`,提供以下功能:
- 启动/停止 Camoufox 实例(指定端口、Profile、代理)
- 通过 CDP 连接已启动的实例
- 注入指纹(Camoufox 内置,无需额外注入)
- 验证登录状态
- 导出/导入 Cookie
### 3.1 脚本骨架
```python
#!/usr/bin/env python3
"""
Camoufox 管理器 — 启动、连接、登录验证
"""
import asyncio
import json
import os
import subprocess
import time
from pathlib import Path
from camoufox import AsyncCamoufox
PROJECT_DIR = Path(__file__).parent.parent
CONFIG_PATH = PROJECT_DIR / "config" / "accounts.yaml"
PROFILES_DIR = PROJECT_DIR / "profiles"
COOKIE_STORAGE = PROJECT_DIR / "data" / "cookies"
def load_account_config(account_id: str) -> dict:
"""从 accounts.yaml 加载指定账号配置"""
# 实现略,参考 switch_account.py 的 get_account
pass
async def launch_camoufox(account: dict) -> bool:
"""启动 Camoufox 实例"""
profile_dir = PROFILES_DIR / account.get('profile_dir', account['id'])
profile_dir.mkdir(parents=True, exist_ok=True)
# 从配置中读取 screen 和 geo
screen = account.get('screen', {'width': 702, 'height': 783})
geo = account.get('geo', {'timezone': 'Asia/Shanghai', 'locale': 'zh-CN'})
proxy = account.get('proxy') # 如 "socks5://127.0.0.1:7890"
print(f"🚀 启动 Camoufox(账号: {account['id']},端口: {account['port']})")
try:
async with AsyncCamoufox(
headless=False,
proxy=proxy,
user_data_dir=str(profile_dir),
screen=screen,
geo=geo,
# 以下参数为 Camoufox 特有
cdp_port=account['port'], # 指定 CDP 端口
disable_automation=True, # 隐藏自动化标记
disable_webdriver=True,
disable_canvas_noise=False, # 启用 Canvas 噪声
disable_font_fingerprinting=False, # 启用字体指纹伪造
disable_webgl_fingerprinting=False,
disable_audio_fingerprinting=False,
) as browser:
# 保持浏览器运行(实际会阻塞,需要多进程)
# 这里我们只启动,然后通过 CDP 连接
pass
except Exception as e:
print(f"❌ Camoufox 启动失败: {e}")
return False
return True
async def connect_via_cdp(port: int):
"""通过 CDP 连接已启动的 Camoufox 实例"""
import urllib.request
import json
from patchright.async_api import async_playwright
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(f"http://localhost:{port}/json/version", timeout=5) as r:
info = json.loads(r.read())
ws_url = info["webSocketDebuggerUrl"]
pw = await async_playwright().start()
try:
# 注意:Camoufox 是 Firefox 内核,需使用 p.firefox 连接
browser = await pw.firefox.connect_over_cdp(ws_url)
context = browser.contexts[0]
page = context.pages[0] if context.pages else await context.new_page()
return page
finally:
await pw.stop()
async def verify_login(page) -> bool:
"""验证抖音登录状态"""
await page.goto('https://www.douyin.com/', wait_until='domcontentloaded', timeout=15000)
await asyncio.sleep(3)
avatar = page.locator("[data-e2e='user-avatar']")
logged_in = await avatar.count() > 0
return logged_in
async def export_cookies(account_id: str, port: int):
"""导出 Cookie 到文件"""
page = await connect_via_cdp(port)
cookies = await page.context.cookies()
cookie_file = COOKIE_STORAGE / f"{account_id}_camoufox_cookies.json"
cookie_file.parent.mkdir(parents=True, exist_ok=True)
with open(cookie_file, 'w') as f:
json.dump(cookies, f, indent=2)
print(f"✅ 导出 {len(cookies)} 个 Cookie → {cookie_file}")
async def main():
"""CLI 入口"""
import argparse
parser = argparse.ArgumentParser(description='Camoufox 管理器')
parser.add_argument('--launch', type=str, help='启动指定账号')
parser.add_argument('--connect', type=int, help='连接指定端口')
parser.add_argument('--export', type=str, help='导出 Cookie(账号ID)')
args = parser.parse_args()
if args.launch:
account = load_account_config(args.launch)
await launch_camoufox(account)
elif args.connect:
page = await connect_via_cdp(args.connect)
print(f"✅ 已连接页面: {page.url}")
elif args.export:
account = load_account_config(args.export)
await export_cookies(args.export, account['port'])
if __name__ == '__main__':
asyncio.run(main())
```
### 3.2 集成到现有切换器
修改 `switch_account.py`,增加 `--browser` 参数,支持 `chrome`(默认)和 `camoufox`。或者,直接创建一个新的入口脚本 `switch_browser.py`,统一管理两种浏览器。
## 4. 登录流程
### 4.1 首次登录
1. 启动 Camoufox 账号:
```bash
python scripts/camoufox_manager.py --launch douyin_camo01
```
2. 手动登录抖音(扫码/密码)。
3. 登录后导出 Cookie:
```bash
python scripts/camoufox_manager.py --export douyin_camo01
```
4. 关闭浏览器(Ctrl+C),后续可通过 CDP 连接使用 Cookie 恢复登录。
### 4.2 后续切换
使用 Cookie 注入方式快速切换(类似方案B):
- 读取该账号的 Cookie 文件
- 通过 CDP 注入到已启动的 Camoufox 实例
- 刷新页面验证登录
## 5. 踩坑经验(预判)
1. **CDP 端口冲突**:Camoufox 默认 CDP 端口可能与 Chrome 冲突,建议使用 9300+ 端口范围。
2. **Firefox CDP 协议差异**:Playwright 连接 Firefox CDP 时,部分 API 与 Chromium 不同,需测试兼容性。
3. **代理配置**:Camoufox 的 `proxy` 参数格式需为 `socks5://ip:port` 或 `http://ip:port`。
4. **Profile 目录权限**:确保 `~/matrix/profiles/` 目录 Camoufox 可写。
5. **浏览器下载失败**:网络问题可能导致 `camoufox fetch` 失败,可手动下载并放置到缓存目录。
## 6. 后续优化方向
1. **Docker 容器化**:将 Camoufox 打包为 Docker 镜像,实现更彻底的隔离。
2. **多账号并行**:使用多个 Camoufox 容器同时运行多个账号。
3. **指纹池**:为每个账号配置不同的指纹模板(屏幕尺寸、时区、语言等)。
4. **自动化登录**:结合短信验证码平台实现全自动登录。
## 7. 快速开始(两个新账号)
1. **编辑配置**:在 `accounts.yaml` 中添加 `douyin_camo01` 和 `douyin_camo02`(如上所示)。
2. **创建目录**:执行 `mkdir -p ~/matrix/profiles/camoufox_{01,02}`。
3. **启动第一个账号**:
```bash
cd ~/matrix
python scripts/camoufox_manager.py --launch douyin_camo01
```
4. **手动登录**,然后导出 Cookie。
5. **关闭浏览器**,测试 Cookie 注入恢复登录。
6. **重复**步骤3-5 用于第二个账号。
---
**下一步**:确认方案后,可开始实施脚本编写与配置更新。
@@ -0,0 +1,100 @@
# 主页信息采集规则 (Collect Batch Runner)
> 适用于所有机器,联邦统一规则。
> 脚本路径: `~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/collect_batch_runner.py`
---
## 核心规则
### 1. 最大并发浏览器数: 3
同一时间最多打开 3 个 Camoufox (Firefox) 浏览器窗口。超过会卡顿。
### 2. 启动间隔: 15 秒 + 窗口错位
同一批次内的 3 个身份,每个间隔 15 秒启动浏览器。避免同时加载导致系统卡顿。
同时窗口 X 轴位置错开 150px,不会互相遮挡:
```
身份0 → 位置 (0, 30)
身份1 → 位置 (150, 30)
身份2 → 位置 (300, 30)
```
```
时间线:
0s 身份 A 启动浏览器 → 抖音 → 小红书 → 关闭
15s 身份 B 启动浏览器 → 抖音 → 小红书 → 关闭
30s 身份 C 启动浏览器 → 抖音 → 小红书 → 关闭
```
### 3. 同身份同浏览器
同一个手机号身份下的两个平台(抖音 + 小红书)使用**同一个浏览器窗口**。浏览器打开后先导航到抖音读取主页,再导航到小红书读取主页,最后关闭。不换浏览器。
```
身份(苏州胃肠小曹) → 打开 1 个浏览器:
1. 导航到 douyin.com → 读取主页信息
2. 导航到 xiaohongshu.com → 读取主页信息
3. 关闭浏览器
```
### 4. 分批执行
11 个身份分 4 批:
| 批次 | 身份数 | 预计耗时 |
|------|--------|----------|
| 第1批 | 3 个 | ~2 分钟 |
| 第2批 | 3 个 | ~2 分钟 |
| 第3批 | 3 个 | ~2 分钟 |
| 第4批 | 2 个 | ~1.5 分钟 |
总计约 8-10 分钟(含浏览器启动、页面加载、间隔时间)。
---
## 数据输出
| 文件 | 说明 |
|------|------|
| `data/homepage_info.json` | 最终结果,含 `collected_at` 时间戳 |
| `data/collect_progress.json` | 实时进度(供 Dashboard 轮询) |
| `screenshots/homepage/` | 每个身份的抖音/小红书截屏 |
### 判断数据新旧
`homepage_info.json` 顶部的 `collected_at` 字段记录了本次采集时间:
```json
{
"collected_at": "2026-06-14T15:18:01",
"results": [...]
}
```
Dashboard 中会根据这个时间显示相对时间("刚刚"、"3小时前"、"1天前"),
绿色 = 刚刚采集 / 黄色 = 超过1天。
---
## 前置条件
1. **oMLX**(如需向量库更新):localhost:8000 运行中
2. **账号已登录**:Camoufox 持久化 profile 中有 session cookie
3. **Python 环境**:matrix venv(含 chromadb、numpy、playwright)
---
## 手动执行
```bash
cd ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts
# 全部采集(分批并行)
python collect_batch_runner.py
# 采集单个身份(顺序执行)
python collect_homepage_info.py --single 手机号
```
@@ -0,0 +1,62 @@
# 采集与展示分离方案
## 当前问题
身份与账号页面的每个平台行都有 👤 采集按钮,点击后打开浏览器实际采集。但:
- 采集和展示耦合在一起,用户分不清是"重新读取文件"还是"重新打开浏览器"
- 每次想看最新状态都得重新采集(打开浏览器耗时几十秒)
- 展示数据已经在 profiles.json 中,但缺少"仅刷新展示"的入口
## 当前数据流
```
采集(打开浏览器)
│
douyin_ops 执行
│
▼
profiles.json
│
▼
api_sms_accounts 读取
│
▼
loadSmsAccounts 展示
```
## 目标:采集与展示分离
```
命令与任务 身份与账号
───────────────────── ─────────────────────
信息采集 TAB 卡片展示
├─ 选账号(单/多/全部) ├─ 状态:登录/昵称/同步 ✅❌
├─ 执行采集(开浏览器) ├─ 昵称名称
└─ 查看采集状态 └─ [🔄 刷新展示] 按钮
│
▼
只重新调 API,不打开浏览器
从 profiles.json 读最新数据
```
## 改动点
### 1. 身份与账号页面
- 去掉每行平台的 👤 采集按钮(移到命令与任务)
- 卡片底部保留「👤 全部采集」(批量操作)
- 增加「🔄 刷新展示」按钮(只重调 API,不打开浏览器)
- 增加「🔄 刷新状态」按钮在页面右上角(已有)
### 2. 命令与任务页面
- 新增「信息采集」TAB(第四个 TAB)
- 内容:账号下拉选择 + 执行采集 + 采集状态日志
### 3. 后端 API
- 现有 `POST /api/matrix/accounts/{id}/collect-profile` — 保留(采集用)
- 现有 `GET /api/matrix/sms/accounts` — 保留(展示用)
- 不需要新增 API
## 好处
- 展示秒级刷新(只读文件,不开浏览器)
- 采集集中管理(在命令与任务统一操作)
- 用户不混淆"刷新展示"和"重新采集"
@@ -0,0 +1,288 @@
# 评论发送 — 原子操作详细规格书 v1.0
> 版本:1.0 | 最后更新:2026-05-10
> 状态:已通过全自动测试验证
> 适用窗口:702×783 | 窗口位置:(652, 0)
---
## 0. 前置条件(所有操作前必检)
| 检查项 | 方式 | 失败处理 |
|--------|------|---------|
| Camoufox 进程不冲突 | `pkill -f camoufox` + 删 `.parentlock` | 重试 |
| 窗口在最前端 | AppleScript `set frontmost` | 重试3次 |
| 窗口大小正确 | `window.innerWidth===702 && window.innerHeight===783` | 调整后再继续 |
| 页面已完全加载 | `document.readyState === 'complete'` | 等待3秒重试 |
---
## 1. 原子操作:enter_video(精选页 → 视频播放页)
### 前置状态:H_JINGXUAN
```
特征码:
- URL: douyin.com/jingxuan
- cards: document.querySelectorAll('.discover-video-card-item').length >= 3
- vc: document.querySelectorAll('video').length === 1
```
### 操作步骤
```
1. _activate_window() # 窗口置前
2. card = locator('.discover-video-card-item').first
3. card.click(force=True) # 第1次点卡片 → 触发预览播放
4. sleep(1.5) # 等预览播放出现
5. vid = locator('video').first
6. vid.click() # 第2次点 video → 进入弹窗播放器
7. sleep(1)
8. vid.click() # 第3次点 video → 确保播放
9. sleep(4) # 等播放器完全加载
```
### 后置状态:P_FULL
```
特征码:
- vc: >= 2 (弹窗播放器有多个 video)
- URL: douyin.com/jingxuan?modal_id=xxx (弹窗模式)
- 或 URL: douyin.com/video/xxx (全屏模式)
```
### 失败恢复
```
重试3次 → 每次回到 H_JINGXUAN (goto douyin.com) → 滚动画廊选不同卡片
3次全失败 → 截图 + 报告
```
---
## 2. 原子操作:open_comments(视频播放页 → 评论区打开)
### 前置状态:P_FULL
```
特征码: vc>=2 + (URL含/video/或modal_id)
```
### 操作步骤
```
1. 前置锚点检测: [data-e2e="comment-list"] 是否存在?
→ 已存在:跳过(评论区已开)
→ 不存在:继续
2. _activate_window()
3. vid = locator('video').first
4. box = vid.bounding_box()
5. mouse.click(box.center) # 点视频中心获得焦点
6. sleep(0.5)
7. keyboard.press('x') # x 键开评论
8. sleep(2)
9. 验证: [data-e2e="comment-list"] 是否存在
10. 如果不存在 → DOM 点评论图标兜底
```
### 后置状态:C_PANEL
```
特征码:
- [data-e2e="comment-list"] 可见
- 输入框尚未加载(懒加载)
```
---
## 2b. 原子操作:close_comments(评论区 → 关闭)
### 前置状态:C_PANEL
```
特征码: [data-e2e="comment-list"] 可见
```
### 操作步骤
```
1. keyboard.press('Escape') 或 keyboard.press('x')
2. sleep(1)
3. 验证: [data-e2e="comment-list"] 不可见
```
### 后置状态:P_FULL
---
## 3. 原子操作:focus_editor(评论区 → 输入框聚焦)
### 前置状态:C_PANEL
```
特征码: [data-e2e="comment-list"] 可见
```
### 操作步骤(核心——模拟真人鼠标轨迹)
```
1. _activate_window()
2. 目标坐标: (479, 687) — 窗口 702×783
3. 鼠标轨迹模拟(8步缓慢移动,总耗时约1.3s):
for step in 0..7:
px = 10 + (479-10) * (step+1) / 8
py = 10 + (687-10) * (step+1) / 8
mouse.move(px, py)
sleep(0.1)
4. sleep(0.5) # 停顿让 hover 触发
5. mouse.click(479, 687) # 第1次单击 → 加载编辑器 + 获得焦点
6. sleep(1) # 等待编辑器渲染
7. 验证: activeElement 是否是 .public-DraftEditor-content
8. 如果没激活 → mouse.click(479, 687) 第2次 → sleep(1) → 再验证
9. 仍未激活 → 多点尝试:
[(350,747), (300,747), (400,747), (250,747)]
每个位置: mouse.move → sleep(0.3) → click → sleep(0.5) → click → sleep(0.5) → 验证
```
### 后置状态:INPUT_FOCUSED
```
特征码:
- document.activeElement === .public-DraftEditor-content
- activeElement.isContentEditable === true
- 编辑器存在(懒加载完成)
```
### 关键参数
```
编辑器选择器: .public-DraftEditor-content
元素类名: notranslate public-DraftEditor-content
角色: combobox
内容可编辑: true
```
---
## 4. 原子操作:type_comment(输入框 → 文字输入)
### 前置状态:INPUT_FOCUSED
```
特征码: activeElement 是 Draft.js 编辑器
```
### 操作步骤
```
1. osascript -e 'set the clipboard to "评论内容"' # 设置剪贴板
2. sleep(0.3)
3. keyboard.press('Meta+v') # Playwright Cmd+V 粘贴
4. sleep(2) # 等待 Draft.js 处理
5. 验证: .public-DraftEditor-content.textContent.length > 0
```
### 后置状态:TEXT_ENTERED
```
特征码:
- .public-DraftEditor-content.textContent.trim().length > 0
- 内容为刚输入的文本
```
### 注意
```
不能使用:
- keyboard.type() → Draft.js 不处理 CDP 键盘事件
- execCommand('insertText') → Draft.js 覆盖 DOM 更改
- pyautogui.write() → 中文 IME 问题
只能用: 剪贴板粘贴(系统级复制 + 浏览器内 Cmd+V)
```
---
## 5. 原子操作:send_comment(文字已输入 → 发送)
### 前置状态:TEXT_ENTERED
```
特征码: editor 有内容
```
### 操作步骤
```
方案A(优先): 找发送按钮
1. 查询 button 含 text"发送"/class含"send"/"submit"/"arrow"
2. 找到后 click
3. 验证
方案B: Alt+Enter(系统级)
1. osascript key code 36 using option down
2. sleep(3)
3. 验证验证码弹窗
方案C: Ctrl+Enter(兜底)
1. keyboard.press('Control+Enter')
2. sleep(1)
3. keyboard.press('Alt+Enter')
4. 验证
```
### 后置状态:COMMENT_SENT 或 VERIFY_CODE
```
COMMENT_SENT:
- [data-e2e="comment-list"] 含刚发的文本内容
VERIFY_CODE:
- input[placeholder*="验证码"] 可见
- 或 .second-verify-panel 可见
```
---
## 6. 异常处理:验证码弹窗
### 触发条件
```
send_comment 后检测到:
- input[placeholder*="验证码"]
- .second-verify-panel
```
### 自动处理
```
1. ApiSMSHandler 启动轮询(每3秒)
- API: wx.tyhtak.com/api/biz/msg/messages
- api_key: gtmsg2026
- phone: 15370103682
2. 提取最新短信中的验证码(4-6位数字)
3. 回填到 input[placeholder*="验证码"]
4. 点击确认按钮
5. 超时120秒 → 手动输入兜底
```
---
## 7. 完整蓝图链(执行顺序)
```
┌─────────────────────────────────────────────────────┐
│ 蓝图:douyin_comment_send │
├─────────────────────────────────────────────────────┤
│ ① launch_browser → 状态: H_JINGXUAN │
│ ② enter_video → 状态: P_FULL │
│ ③ open_comments → 状态: C_PANEL │
│ ④ focus_editor → 状态: INPUT_FOCUSED │
│ ⑤ type_comment → 状态: TEXT_ENTERED │
│ ⑥ send_comment → 状态: COMMENT_SENT │
│ ⑦ 如果是 VERIFY_CODE → 自动获取+回填验证码 │
│ ⑧ goto_home → 回到 H_JINGXUAN(下一轮) │
└─────────────────────────────────────────────────────┘
```
---
## 8. 调试信息记录格式
每个原子操作执行时必须记录:
```
[步骤名] 前置状态: X → 执行中 → 后置状态: Y | 耗时: Ns | 结果: OK/FAIL
```
失败时额外记录:
```
当前URL: xxx
当前vc: N
activeElement: tag/class
错误信息: xxx
```
@@ -0,0 +1,235 @@
# 语料库架构升级方案 v2.0
> 作者: ghai | 日期: 2026-06-13 | 状态: 方案阶段
---
## 一、当前架构(v1.x)的问题
```
语料库 = 分类 → 评论列表
│
├─ 赞美: ["讲得太好了", "学到了"]
├─ 搞笑: ["哈哈哈哈哈"]
└─ 提问: ["请问这是哪里?"]
```
**局限:**
1. 只有**态度语气**维度(赞美/搞笑/客观),没有内容维度(科技/美食/养生)
2. 没有**人设/身份**维度(以什么"人"在说话)
3. 没有**多轮交互**(第一次说啥、第二次说啥)
4. 没有**故事线/IP**绑定(围绕某个话题展开)
5. 无法表达复杂评论(带逻辑的、有结构的)
---
## 二、目标架构(v2.0)
### 三维语料模型
```
语料 = 身份(Persona) × 场景(Scene) × 内容(Content)
身份(Persona): 谁在说话
├─ 养生爱好者 → "小美养生茶" 这个号的人设
├─ 科技极客 → 说话带专业术语
├─ 美食探店达人 → 语气活泼,爱用表情
└─ 情感博主 → 感性、共情
场景(Scene): 在什么情境下说话
├─ 初次评论 → 第一印象
├─ 跟帖回复 → 对别人评论的回应
├─ 作者回复 → 博主回复粉丝
└─ 故事线对话 → 多轮剧情互动
内容(Content): 具体说什么
├─ 模板句 → "这个{keyword}太棒了"
├─ 故事片段 → "我曾经也遇到过..."
├─ 知识补充 → "其实{keyword}的原理是..."
└─ IP台词 → 某个IP的经典语录
```
### 数据结构
```yaml
# corpus/douyin.yaml v2.0
version: "2.0"
# ── 身份配置 ──
personas:
health_lover:
name: "养生爱好者"
tags: ["养生", "健康", "中医"]
style: "温和、专业"
rules:
- "不提西医西药"
- "多用'调理'代替'治疗'"
tech_geek:
name: "科技极客"
tags: ["数码", "AI", "编程"]
style: "理性、数据驱动"
# ── 场景配置 ──
scenes:
first_comment: # 首次评论
label: "首次评论"
rounds: 1 # 1轮
rules:
- "不要提到自己"
- "与视频内容相关"
follow_up: # 跟帖回复
label: "跟帖回复"
rounds: 2 # 最多2轮
rules:
- "先认同对方观点"
- "再补充自己的看法"
story_arc: # 故事线
label: "故事线对话"
rounds: 3-5 # 3-5轮
rules:
- "每轮推进剧情"
- "保持角色一致性"
author_reply: # 作者回复粉丝
label: "作者回复"
rules:
- "先感谢评论"
- "针对评论内容回应"
# ── 内容库 ──
content:
health_lover:
first_comment:
- "这个养生方法很实用,我也在坚持"
- "中医讲究辨证论治,不能一概而论"
- "食疗比药疗更温和,推荐大家试试"
- "{keyword}这个方法确实有效,亲测"
follow_up:
round_1:
- "确实如此,{keyword}我也是这么认为的"
- "同意,不过还要注意个体差异"
- "你说得对,我再补充一点..."
round_2:
- "对,我之前也遇到过同样的情况"
- "是的,后来我换了一种方法就好了"
author_reply:
- "谢谢你的评论,{keyword}确实是个好问题"
- "感谢支持,我会继续分享更多{keyword}的内容"
tech_geek:
first_comment:
- "这个技术方案在性能上还有优化空间"
- "从架构角度看,{keyword}这个方案更合理"
- "数据支撑不够,建议补充A/B测试结果"
# ── IP/故事线 ──
ip_stories:
douyin_hot_series:
name: "抖音热评系列"
scenes:
intro:
- "最近{keyword}这个事我也关注了"
- "看了好几遍,说说我的看法"
develop:
- "其实这个事情要从几个方面看..."
- "很多人忽略了最关键的一点..."
climax:
- "最让我震撼的是..."
- "这个案例告诉我们..."
ending:
- "总的来说,{keyword}给了我们很多启示"
- "希望以后能看到更多这样的内容"
```
---
## 三、执行链路
```
用户输入:评论指令
│
├─ 1. 解析上下文
│ ├─ 当前账号 → 获取 Persona(养生爱好者/科技极客)
│ ├─ 当前场景 → 首次评论/跟帖回复/作者回复
│ └─ 视频标题 → 提取关键词
│
├─ 2. 选择语料
│ ├─ content.{persona}.{scene} → 匹配模板
│ ├─ 关键词替换 {keyword}
│ ├─ 多轮计数(round_1/round_2)
│ └─ fallback → AI 生成
│
├─ 3. 发送评论
│ └─ pbcopy + Meta+V + Enter
│
└─ 4. 记录上下文
├─ 已发送评论内容
├─ 当前轮次(用于多轮)
└─ 下次回复策略
```
---
## 四、Dashboard 语料管理
矩阵设置 → 语料库 页面升级:
```
┌──────────────────────────────────────────┐
│ 📚 语料库管理 │
│ │
│ [🎵 抖音] [📕 小红书] │
│ │
│ ┌─ TAB: 按身份 ───────────────────────┐ │
│ │ 🧑 养生爱好者 🧑 科技极客 │ │
│ │ 🧑 美食探店 🧑 情感博主 │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌─ TAB: 按场景 ───────────────────────┐ │
│ │ 💬 首次评论 💬 跟帖回复 │ │
│ │ 💬 作者回复 💬 故事线 │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌─ TAB: 按IP/故事线 ──────────────────┐ │
│ │ 📖 抖音热评系列 📖 美食探店系列 │ │
│ └──────────────────────────────────────┘ │
│ │
│ 内容编辑区(按选中的身份+场景显示): │
│ ┌──────────────────────────────────────┐ │
│ │ ✏️ 评论模板1 [编辑] [删除] │ │
│ │ ✏️ 这个{keyword}方法确实有效 │ │
│ │ ✏️ 评论模板2 [编辑] [删除] │ │
│ │ ✏️ 中医讲究辨证论治 │ │
│ │ [+ 新增评论] │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘
```
---
## 五、实施路线
| 阶段 | 内容 | 工作量 |
|------|------|--------|
| Phase 1 | 语料库 YAML 格式升级(v1→v2) | 2天 |
| Phase 2 | CorpusManager 升级(支持身份/场景/多轮) | 2天 |
| Phase 3 | Dashboard 语料管理页面升级(三维管理) | 3天 |
| Phase 4 | 执行链路接入(engine.py 上下文传递) | 1天 |
| Phase 5 | 语料内容填充(用户工作) | 持续 |
**当前可以先开始的:** Phase 1(格式设计)+ 少量示例数据,验证可行性后再继续。
---
## 六、与当前系统的兼容
- 旧版 YAML 格式(v1)自动兼容,可正常读取
- 新版格式新增字段,不破坏现有功能
- `@corpus` 占位符继续保持可用
- 新增 `@persona`、`@scene` 占位符支持
- CorpusManager.get_comment_for_video() 参数不变,内部升级
@@ -0,0 +1,96 @@
# AgentOS 联邦系统 — 开发规则 v1.0
> 所有新功能/修复必须遵守以下规则,防止代码混乱。
---
## 一、代码分层与职责
| 层 | 文件 | 职责 | 谁写 |
|:---|:-----|:------|:------|
| **蓝图 JSON** | `blueprints/*.json` | **你和我交互的命名模板**,只定义参数,不写逻辑 | AI 写参数定义,你用看板填参数 |
| **原子操作** | `douyin_ops.py` / `ops/xhs_ops.py` / `matrix_modules/account/*.py` | 被 `mc run` 引擎调用的最小操作单元 | AI 写,封装到已有类里 |
| **引擎** | `mc/engine.py` / `mc/task.py` | 执行蓝图 + 状态机钩子(登录检测/验证处理/冷却) | AI |
| **独立工具** | `create_identity.py` / `login_identity.py` / `publish_video.py` | CLI 可直接调的系统维护脚本 | AI |
| **一次脚本** | `/tmp/*.py` | 临时探索/诊断,用完即删 | AI |
| **看板视图** | `frontend/src/views/*.js` | Dashboard 前端视图 | AI |
| **API 路由** | `routes/*.py` | 看板调用的后端接口 | AI |
---
## 二、三原则(必须遵守)
### 原则 1:功能加到已有类里,不建新文件
- 抖音的新操作 → 加 `DouyinOps` 类(`douyin_ops.py`)
- 小红书的新操作 → 加 `XhsOps` 类(`ops/xhs_ops.py`)
- SMS 登录 → 已有 `sms_login.py`,改它
- 登录状态机 → 已有 `login_state_machine.py`
**禁止**:为每个小功能创建独立脚本。除非它是一个**完整的、可独立 CLI 调用的工具**(如 `create_identity.py`)。
### 原则 2:蓝图只定义参数,不写执行逻辑
```json
{
"id": "douyin_daily",
"name": "抖音-日常养号",
"params": { "duration": {"type": "int", "default": 600} },
"steps": [{"step_id": 1, "op": "goto_home", "args": {}}]
}
```
- 蓝图文件只描述「有什么参数」「分几步」
- 真正的执行逻辑在 `mc/engine.py` + 原子操作里
**禁止**:在蓝图 JSON 里写条件/循环/异常处理。
### 原则 3:`/tmp/*.py` 用完即删
- `/tmp/douyin_login_explore.py` — 用完即删
- `/tmp/test_xxx.py` — 用完即删
**禁止**:把 `/tmp/` 脚本作为正式功能提交到 Git。
---
## 三、新增功能的标准化流程
```
你提需求 → 我评估归属层 → 写入对应文件 → 你测试 → 我修正
例: 抖音SMS登录
你: "douyin_133 登录过期了, 需要SMS重新登录"
我: 归属 → DouyinOps 类
→ douyin_ops.py 加 sms_login() 方法
→ 调已有 sms_login.py 的 SMS 轮询逻辑
→ 蓝图的 needs_login 字段自动触发
→ 不建新文件
```
---
## 四、什么算"独立工具"
只有满足以下**全部**条件的才可以建独立脚本:
1. ✅ 可从 CLI 直接调用(有 `main()` + `argparse`)
2. ✅ 不依赖 `mc run` 引擎
3. ✅ 功能完整闭环(如 `create_identity.py` 创建身份→生成目录→写配置)
4. ❌ 如果只是被引擎或原子操作调用的功能,必须写在对应类里
---
## 五、历史遗留处理
| 文件 | 状态 | 原因 |
|:-----|:------|:------|
| `collect_batch_runner.py` | ✅ 已归档 | 功能已被蓝图 `douyin_read_profile` 替代 |
| `collect_homepage_info.py` | ✅ 已归档 | 同上 |
| `matrix.py` | ✅ 已归档 | 功能被 `mc` CLI 替代 |
| `ops/douyin/` | ✅ 已归档 | 功能被 `douyin_ops.py` 替代 |
| `atom_ops.py` | ✅ 已归档 | 废弃 |
| `yanghao_runner.py` | ✅ 已归档 | 废弃 |
| `step_by_step_v*.py` | ✅ 已归档 | 废弃 |
**新功能必须直接写入活跃文件,不建新独立脚本。**
@@ -0,0 +1,285 @@
# 抖音 Web 端选择器手册
> 扫描时间:2026-04-27 | 浏览器窗口:702×681 | CDP 端口:9222
> 连接方式:Patchright `connect_over_cdp` | 代理需绕过:`no_proxy=localhost,127.0.0.1`
---
## 键盘快捷键
| 键 | 功能 | 备注 |
|----|------|------|
| **Z** | 点赞 | 需英文输入法 |
| **X** | 打开/关闭评论 | |
| **F** | 关注作者 / 进直播间 | |
| **B** | 弹幕开关 | |
| **空格** | 播放/暂停 | |
| **↑** | 上一个视频 | |
| **↓** | 下一个视频 | |
| **Enter** | 搜索确认 / 评论发送 | |
---
## 全局元素
| 元素 | 选择器 | 备注 |
|------|--------|------|
| 搜索框 | `[data-e2e="searchbar-input"]` | type=input |
| 搜索按钮 | `[data-e2e="searchbar-button"]` | |
| 右上角头像 | `[data-e2e="live-avatar"]` | hover 弹出菜单 |
| 左侧导航 | `[data-e2e="douyin-navigation"]` | |
### 左侧导航栏
| 项目 | 大致位置(y) |
|------|-----------|
| 精选 | 64 |
| 推荐 | 124 |
| 搜索 | 184 |
| 关注 | 244 |
| 朋友 | 304 |
| 我的 | 364 |
| 直播 | 441 |
| 放映厅 | 501 |
| 短剧 | 561 |
---
## 视频播放页(推荐/关注)
### 互动按钮
| 操作 | 键盘 | 选择器 | 状态判断 |
|------|------|--------|---------|
| 点赞 | **Z** | `[data-e2e="video-player-digg"]` | `data-e2e-state`: `video-player-no-digged`(未赞) / `video-player-digged`(已赞) |
| 评论(打开) | **X** | `[data-e2e="feed-comment-icon"]` | |
| 收藏 | - | `[data-e2e="video-player-collect"]` | |
| 分享 | - | `[data-e2e="video-player-share"]` | |
| 关注/进直播 | **F** | `[data-e2e="feed-follow-icon"]` | |
| 更多 | - | `[data-e2e="video-play-more"]` | |
### 翻页
| 操作 | 键盘 | 选择器 |
|------|------|--------|
| 上翻视频 | **↑** | `[data-e2e="video-switch-prev-arrow"]` |
| 下翻视频 | **↓** | `[data-e2e="video-switch-next-arrow"]` |
### 播放控制
| 操作 | 键盘 | 备注 |
|------|------|------|
| 播放/暂停 | **空格** | |
| 弹幕开关 | **B** | |
---
## 评论区
> 按 X 或点击评论图标后,右侧弹出评论面板,视频从 570px 缩窄到 234px
| 元素 | 选择器 | 备注 |
|------|--------|------|
| 评论列表 | `[data-e2e="comment-list"]` | |
| 评论输入框 | `.public-DraftEditor-content` | Draft.js 富文本编辑器,contentEditable |
| 输入容器(聚焦态) | `.comment-input-container-focus` | 激活后添加此 class |
| 发送按钮 | `.commentInput-right-ct .WFB7wUOX` | 向上箭头图标 |
| 表情按钮 | `.commentInput-right-ct .BVMl8WNl` 第2个 | |
| 图片上传 | `.commentInput-right-ct .BVMl8WNl` 第1个 | |
| @提及 | `.commentInput-right-ct .BVMl8WNl` 第3个 | |
| 评论更多 | `[data-e2e="video-comment-more"]` | |
| 弹幕输入框 | `input[placeholder="发一条弹幕吧"]` | 非评论框,注意区分 |
### 评论操作流程
```
1. 按 X 打开评论面板
2. 点击 .public-DraftEditor-content 激活输入
3. 输入文字(type_into Draft.js 编辑器)
4. Enter 发送 / 点击发送按钮
5. 可能触发验证码弹窗
```
---
## 搜索结果页
**URL 格式**:`https://www.douyin.com/root/search/{关键词}?type=general`
### Tab 分类栏
| Tab | 选择器 | 备注 |
|-----|--------|------|
| 综合(激活) | `.NiqGqBbw` | 不同 class 表示激活态 |
| 视频/用户/直播 | `.t3OsOj2N` | |
### 子分类筛选
| 选择器 | 示例 |
|--------|------|
| `.neT9xRMd.vZeqhI2r` | 直播间、胃肠镜、中医院… |
| 激活态额外 class | `.Bp4xnVjA` |
### 显示模式
| 模式 | 选择器 |
|------|--------|
| 多列 | `.cSG1ckL8` |
| 单列 | `.cSG1ckL8.ltQEgur_` |
### 结果卡片
| 元素 | 选择器 | 备注 |
|------|--------|------|
| 卡片容器 | `.search-result-card` | 整卡可点击(cursor:pointer),无 `<a>` 标签 |
| 视频封面 | `.videoImage` | 219×292 |
| 标题 | `.wSEoiOKC` | |
| 作者 | `.YebDknhI` | @username |
| 点赞数 | `.LUd60iMQ` | |
| 布局 | 3列网格 | x=31, x=276, x=521 |
### 点击第 N 个搜索结果
```python
cards = page.query_selector_all('.search-result-card')
cards[0].click() # 第1个
```
---
## 关注页
**URL**:`https://www.douyin.com/follow`
### 左侧关注列表
| 元素 | 选择器 | 备注 |
|------|--------|------|
| 列表头 | `.mZ1rdREf` | "列表综合排序" |
| 我的关注 | `.IMOte1q1.mZ1rdREf` | "我的关注(N)" |
| 关注用户项 | `.ILK2RAD5` | 点击切换到该用户视频 |
### 右侧视频流
与推荐页结构相同,互动按钮选择器通用。
---
## 个人主页
**URL**:`https://www.douyin.com/user/self?showTab=post`
### 用户信息
| 元素 | data-e2e |
|------|----------|
| 用户详情 | `user-detail` |
| 用户信息 | `user-info` |
| 关注数 | `user-info-follow` |
| 粉丝数 | `user-info-fans` |
| 获赞数 | `user-info-like` |
### Tab 栏
| Tab | 位置(y) |
|-----|---------|
| 作品 | 221 |
| 推荐 | 221 |
| 喜欢 | 221 |
| 收藏 | 221 |
| 观看历史 | 221 |
| 稍后再看 | 221 |
| 我的预约 | 221 |
| AI 笔记 | 221 |
### 作品列表
| 元素 | data-e2e |
|------|----------|
| 作品列表 | `user-post-list` |
| 滚动列表 | `scroll-list` |
---
## 头像悬停菜单
**触发**:hover `[data-e2e="live-avatar"]`
**面板**:`.userMenuPanelShadowAnimation`(x≈360, 宽328)
| 菜单项 | 选择器 | 链接 |
|--------|--------|------|
| 用户名 | `.uz1VJwFY.e6huIECy` | → 个人主页 |
| 我的喜欢 | `.uz1VJwFY.espXX7re` 第1个 | → 喜欢 |
| 我的收藏 | `.uz1VJwFY.espXX7re` 第2个 | → 收藏 |
| 观看历史 | `.uz1VJwFY.espXX7re` 第3个 | → 历史 |
| 稍后再看 | `.uz1VJwFY.espXX7re` 第4个 | → 稍后再看 |
| 我的作品 | `.uz1VJwFY.espXX7re` 第5个 | → 作品 |
| 我的订单 | `.uz1VJwFY.VSHeqy7Q` | 无链接 |
---
## 验证码弹窗
> 评论后可能触发短信验证码
| 元素 | 选择器 | 备注 |
|------|--------|------|
| 遮罩 | `.second-verify-mask` | 全屏遮罩 |
| 面板 | `.second-verify-panel` | |
| 标题 | `.uc-ui-verify-new_header-title` | "接收短信验证码" |
| 关闭 | `.uc-ui-verify-new_header-close` | |
| 提示 | `.uc-ui-verify_sms-verify_content_desc` | 手机号脱敏 |
| 输入框 | `.uc-ui-verify_sms-verify_input` | |
| 获取验证码 | 面板内按钮 | |
| 验证按钮 | `.uc-ui-verify_sms-verify_button.primary` | 非 disabled 时可点 |
| 取消按钮 | `.uc-ui-verify_sms-verify_button.second` | |
---
## 技术备忘
### CDP 连接
```python
import os
os.environ['no_proxy'] = 'localhost,127.0.0.1'
os.environ['NO_PROXY'] = 'localhost,127.0.0.1'
from patchright.async_api import async_playwright
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp('http://localhost:9222')
context = browser.contexts[0]
page = context.pages[0]
```
### Draft.js 输入
评论框是 Draft.js 富文本编辑器,不能用普通 `fill()`,需用 `type_into()` 或 `press_sequentially()`:
```python
editor = page.locator('.public-DraftEditor-content')
await editor.click()
await editor.press_sequentially('评论内容')
```
### 起始页固定
每次操作从固定 URL 开始:`https://www.douyin.com/?recommend=1`
### SVG className
SVG 元素的 `className` 是 `SVGAnimatedString` 对象,非字符串。JS 中需用 `el.className.baseVal`。
### 点赞状态判断
```python
digg = page.locator('[data-e2e="video-player-digg"]')
state = await digg.get_attribute('data-e2e-state')
# "video-player-no-digged" = 未点赞
# "video-player-digged" = 已点赞
```
### 搜索卡片无 `<a>` 标签
搜索结果卡片通过 JS 事件跳转,需用 `card.click()` 而非导航链接。
@@ -0,0 +1,357 @@
# 🦀 身份工厂:反检测 + 持久化标准化方案
> 基于 Camoufox 的「反检测伪装」+「持久化上下文」构建的多账号身份管理系统
> 版本: v1.0 | 日期: 2026-05-03
---
## 一、核心认知纠偏
### 1.1 反检测 ≠ 反持久化
| 概念 | 定义 | 关系 |
|---|---|---|
| **反检测 (Anti-Detection)** | 让网站无法识别你是自动化工具/不同身份 | Camoufox 的**设计目标** |
| **反持久化 (Anti-Persistence)** | 每次关闭后清空所有状态 | Camoufox 的**默认行为**(不是设计目标) |
| **持久化 (Persistence)** | 保存并复用登录态、浏览数据 | Camoufox **完全支持**,只是需要正确配置 |
**关键结论**:Camoufox 的默认行为(临时 profile + 随机指纹)是为了开发测试方便,**不代表它不支持持久化**。其 Playwright API 天然支持 `launch_persistent_context` + `user_data_dir`。
### 1.2 Camoufox 的真正优势
| 维度 | Chrome Profiles | Camoufox 持久化 |
|---|---|---|
| 指纹隔离 | ❌ 同一台机器的真实指纹 | ✅ 每身份独立伪造指纹 |
| 反检测深度 | ❌ JS 层修补(易被检测) | ✅ C++ 源码级修改(底层注入噪声) |
| 内核优势 | ❌ Chrome 被重点监控 | ✅ Firefox 冷门,避开重点关照 |
| Cloudflare 通过率 | ~50% | **88.58%**(2026 评测) |
| 身份隔离 | ❌ 数据隔离 ≠ 身份隔离 | ✅ 真正的"不同人用不同电脑" |
| 持久化能力 | ✅ user-data-dir 原生支持 | ✅ persistent_context + user_data_dir |
### 1.3 当前代码的三个致命缺陷
#### 缺陷一:没有使用持久化上下文
**位置**:`cdp_connector.py:69-104` — `_launch_camoufox()`
```python
cf = AsyncCamoufox(**kwargs)
self._camoufox_browser = await cf.start()
```
`start()` → `AsyncNewBrowser(..., persistent_context=False)` → `playwright.firefox.launch()`(临时 profile)
**影响**:每次启动生成 `/tmp/playwright_xxx/` 临时目录,所有登录态丢失。
#### 缺陷二:没有固化指纹
**位置**:`cdp_connector.py:73-85`
```python
kwargs = {
'os': 'windows', # 只约束了范围(Windows 池)
'humanize': 1.5,
# ← 没有 fingerprint 参数
}
```
**链路**:`launch_options()` → `if fingerprint is None` → `generate_fingerprint(random)` → 每次指纹不同
**影响**:抖音服务端检测到「同一 session_id 对应不同设备指纹」→ 强制短信验证。
#### 缺陷三:Cookie 注入是错误做法
**位置**:`yanghao_runner.py:149-164`
```python
cookie_file = ... / f"{acct_id}_cookies.json"
cookies = json.loads(cookie_file.read_text())
# 注入 Cookie
for c in cookies:
await conn.context.add_cookies([{...}])
```
**问题**:抖音风控的校验逻辑是「session_id + 设备指纹」双重校验。Cookie 注入只传了 session_id,但当前设备的随机指纹与当初登录时的指纹不匹配 → 风控拦截 → 强制短信验证。
---
## 二、正确架构:身份工厂(Identity Factory)
### 2.1 核心理念
把「反检测伪装」和「持久化登录」变成标准化工程组件:
```
┌──────────────────────────────────────────────────────────┐
│ 身份工厂 (Identity Factory) │
├───────────────┬────────────────┬─────────────────────────┤
│ create_identity │ login_identity │ run_task │
│ (BrowserForge │ (手动登录一次 │ (以登录态自动执行) │
│ 生成固化指纹) │ state 保存) │ │
├───────────────┴────────────────┴─────────────────────────┤
│ identities/ │
│ ├── douyin_01/ ← 第一个身份 │
│ │ ├── config.yaml ← 固化指纹 + 代理配置 │
│ │ └── user_data/ ← Firefox 持久化 Profile │
│ ├── douyin_02/ ← 第二个身份 │
│ │ ├── config.yaml │
│ │ └── user_data/ │
│ └── ... │
└──────────────────────────────────────────────────────────┘
```
### 2.2 核心规则:一手机号 = 一身份
**同手机号的抖音和小红书账号必须共用同一个 identity_dir。**
这是经过实践验证的结论:
| ❌ 错误做法 | ✅ 正确做法 |
|:-----------|:-----------|
| douyin_01 → `identities/douyin_01_camo` | douyin_01 → `identities/douyin_01_camo` |
| xhs_01 → `identities/xhs_01`(新建身份) | xhs_01 → `identities/douyin_01_camo`(**复用**) |
| 结果:两个浏览器指纹,两个登录态 | 结果:一个浏览器指纹,一份登录态,抖音和小红书 cookie 共存 |
**为什么必须相同?**
- 一个手机号绑定一个实际的"人",反检测的本质是让平台认为你是"同一个人在不同网站活动"
- 抖音和小红书分别存储 session cookie,互不冲突
- 只需用 `CookieGuard` 保护对方平台的 cookie(删除时别误删)
**代码中如何配置(accounts.override.yaml)**:
```yaml
- id: douyin_04
phone: '18550099083'
enabled: true
- id: xhs_04
phone: '18550099083' # 同手机号
enabled: true
```
`MatrixManager` 的 `list_accounts()` 方法会自动为同手机号的 xhs 账号分配与 douyin 相同的 identity_dir。
**验证方法**:
```bash
cd scripts && python -c "
from mc.engine import resolve_account
a = resolve_account('xhs_04')
print(a.get('identity_hint')) # 输出: douyin_04(与 douyin_04 相同)
"
```
### 2.2 目录结构
```
agent-local/tools/matrix/
├── identities/ # 所有身份集中存放
│ ├── douyin_01/ # 抖音主号
│ │ ├── config.yaml # 指纹 + 代理配置(可版本控制)
│ │ └── user_data/ # 持久化 Profile(不提交 Git)
│ ├── douyin_02/ # 抖音副号
│ │ ├── config.yaml
│ │ └── user_data/
│ └── douyin_camo01/ # 第三个号(Chrome 或 Camoufox)
│ ├── config.yaml
│ └── user_data/
├── scripts/ # 已有脚本,需改造
│ ├── cdp_connector.py # → 新增: launch_camoufox_persistent()
│ ├── yanghao_runner.py # → 新增: --identity 模式
│ ├── camoufox_manager.py # → 改造: 支持 persistent_context
│ └── ... (新建)
│ ├── create_identity.py # 生成新身份(指纹固化)
│ ├── login_identity.py # 首次手动登录
│ └── run_task.py # 以已登录身份执行任务
└── config/
└── accounts.yaml # → 重构: 指向 identities/ 目录
```
### 2.3 配置文件格式 `config.yaml`
```yaml
# identities/douyin_01/config.yaml
identity:
name: "douyin_01"
platform: "douyin"
proxy: null # 可选:固定代理
notes: "抖音主号"
# 由 BrowserForge 生成并固化的指纹
fingerprint:
navigator:
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0"
platform: "Win32"
hardwareConcurrency: 8
deviceMemory: 8
language: "zh-CN"
screen:
width: 1920
height: 1080
availWidth: 1920
availHeight: 1040
colorDepth: 24
webgl:
vendor: "Google Inc. (Intel)"
renderer: "ANGLE (Intel, Intel(R) UHD Graphics (0x00009BC4) Direct3D11 vs_5_0 ps_5_0)"
# ... 完整指纹由 BrowserForge 生成
```
### 2.4 关键 API:持久化上下文
```
AsyncCamoufox(persistent_context=True, user_data_dir=path, fingerprint=fixed_fp)
↓
AsyncNewBrowser(..., persistent_context=True)
↓
playwright.firefox.launch_persistent_context(user_data_dir=path, ...)
✅ 所有状态保存到 path 下
✅ 下次启动直接复用
```
对应 Chrome 的方案:
```
Google Chrome --user-data-dir=path --remote-debugging-port=port
↓
CDP connect
✅ 所有状态保存在 path 下
✅ 浏览器不退出,状态持久
```
**两者本质相同**,差异在于:
- Chrome:浏览器进程常驻 + CDP 连接
- Camoufox:`launch_persistent_context` 管理生命周期
---
## 三、标准化流程
### 3.1 创建身份 → `create_identity`
```
BrowserForge.generate()
↓
生成完整设备指纹(固定)
↓
写入 identities/{name}/config.yaml
↓
✅ 身份创建完成
```
### 3.2 首次登录 → `login_identity`
```
启动 Camoufox(headless=False, persistent_context=True, user_data_dir=path, fingerprint=fixed)
↓
打开目标网站(抖音首页)
↓
弹窗等待 → 用户手动登录
↓
用户按 Enter 确认
↓
所有登录态保存到 user_data/
↓
✅ 身份激活完成
```
**这是唯一一次手动操作**。之后所有任务复用这个 state。
### 3.3 日常任务 → `run_task`
```
启动 Camoufox(headless=True, persistent_context=True, user_data_dir=path, fingerprint=fixed)
↓
直接以已登录状态访问抖音
↓
执行蓝图操作(浏览/点赞/收藏等)
↓
保持浏览器不退出,继续下一轮
↓
✅ 循环执行
```
### 3.4 多账号并行
```
for identity in [douyin_01, douyin_02, douyin_camo01]:
创建身份目录
首次登录(手动)
# 日常运行
asyncio.gather(
run_loop("douyin_01"),
run_loop("douyin_02"),
run_loop("douyin_camo01"),
)
```
---
## 四、与现有代码的关系
### 4.1 保留的部分
| 文件 | 用途 | 保留 |
|---|---|---|
| `blueprints/` | 蓝图定义 | ✅ 不变 |
| `douyin_ops.py` | 原子操作 | ✅ 不变 |
| `atom_ops.py` | 原子操作 | ✅ 不变 |
| `auth_manager.py` | 登录状态检测 | ✅ 改进 |
| `task_engine.py` | 蓝图执行引擎 | ✅ 改进加载方式 |
| `anti_detection.py` | 反检测 | ✅ 改进 |
| `accounts.yaml` | 账号配置 | 🛠 重构格式 |
### 4.2 需要改造的文件
| 文件 | 改造内容 | 优先级 |
|---|---|---|
| `cdp_connector.py` | 新增 `_launch_camoufox_persistent()`,支持 `persistent_context` + `user_data_dir` + 固化指纹 | **P0** |
| `yanghao_runner.py` | 新增 `--identity` 模式,通过身份目录启动 | **P0** |
| `camoufox_manager.py` | 改造 `--launch` 使用持久化上下文 | **P1** |
| `accounts.yaml` | 新增 `identity_dir` 字段指向 identities/ | P1 |
| `config_template/` | 更新模板 | P2 |
### 4.3 新增文件
| 文件 | 功能 | 优先级 |
|---|---|---|
| `scripts/create_identity.py` | 用 BrowserForge 生成指纹并创建身份目录 | **P0** |
| `scripts/login_identity.py` | 标准化首次登录流程 | **P0** |
| `scripts/run_task.py` | 以已登录身份执行自动化任务 | P1 |
| `docs/IDENTITY_FACTORY.md` | 本文档 | ✅ 已完成 |
---
## 五、关键注意事项
### 5.1 user_data 不可共享
- 每个身份独立的 `user_data/` 目录
- 禁止混用,否则身份关联
- 禁止提交到 Git(包含登录态)
### 5.2 指纹固化后不可变
- 一旦身份创建完成,`config.yaml` 中的指纹不可修改
- 修改指纹 = 换了一台电脑 → 掉登录
- 如果需要新指纹 → 创建新身份,重新登录
### 5.3 代理一致性
- 一个身份最好绑定固定代理
- 代理变更会触发 IP 地理位置突变 → 风控
- 在 `config.yaml` 的 `identity.proxy` 中指定
### 5.4 浏览器常驻模式
- 登录后浏览器不退出(当前循环模式正确)
- `persistent_context` 确保状态即时刷新
- 避免频繁启停(减少指纹比对次数)
### 5.5 检测结论
根据 [2026 反检测浏览器评测](https://www.browserling.com/blog/2026-antidetect-browsers-review),Camoufox 在以下维度表现:
| 指标 | Camoufox | Chrome stealth | Chrome 原生 |
|---|---|---|---|
| Cloudflare 通过率 | **88.58%** | 11.21% (patchright) | ~50% |
| Canvas 指纹伪装 | ✅ C++ 级 | ⚠️ JS 级 | ❌ |
| WebGL 伪装 | ✅ | ⚠️ | ❌ |
| 字体伪装 | ✅ | ⚠️ | ❌ |
| 多身份隔离 | ✅ 身份级 | ⚠️ Profile 级 | Profile 级 |
Camoufox + 持久化上下文 + BrowserForge 固化指纹 + 固定代理 = 当前最优方案。
@@ -0,0 +1,420 @@
# 本地化适配说明
**基于当前环境的落地调整方案**
**当前机器**:macOS 26.4,Apple M1,8GB RAM,约 164GB 可用
**文档日期**:2026-04-27
---
## 一、环境现状扫描结果
| 检查项 | 手册要求 | 当前状态 | 差距 |
|:---|:---|:---|:---|
| **Homebrew** | 用于安装 Colima/Docker | ❌ 未安装 | 需先装 Homebrew |
| **Colima** | 容器运行时 | ❌ 未安装 | 依赖 Homebrew |
| **Docker** | 容器管理 | ❌ 未安装 | 依赖 Homebrew |
| **Python** | 3.10+(脚本运行) | ✅ 3.13.12(managed) | 已满足 |
| **Playwright** | 登录脚本(过渡期) | ✅ 已安装 | 已满足 |
| **Camoufox** | 主力反检测浏览器 | ❌ 未安装 | pip install |
| **Patchright** | 备用反检测浏览器 | ❌ 未安装 | pip install |
| **MLX** | 视觉定位后备 | ❌ 未安装 | pip install |
| **Chrome** | 真实浏览器(CDP 直连) | ❌ 未安装 | 需手动安装 |
| **代理IP** | 每账号独立静态住宅IP | ❌ 未配置 | 需向服务商购买 |
| **账号** | 5-6 个养号账号 | ⚠️ 仅确认1个手机号 | 需补充 |
---
## 二、关键技术路径调整
### 2.1 Docker 方案的现实问题
手册的 Docker + Colima 方案在当前机器上有以下制约:
| 问题 | 说明 | 影响 |
|:---|:---|:---|
| **无 Homebrew** | Colima/Docker 需要 Homebrew 安装 | 安装路径变长 |
| **8GB RAM** | 5个 Docker 容器 + 宿主系统,8GB 会吃紧 | 同时运行容器数有上限 |
| **Camoufox ARM 适配** | Camoufox 的 ARM64 Docker 镜像需要编译,社区还不稳定 | 有编译失败风险 |
| **无 X Display** | macOS Docker 容器跑有头浏览器需要 XQuartz,配置麻烦 | 增加配置难度 |
### 2.2 推荐:分两阶段走
```
阶段 A(现在,1-2周):CDP 直连本机真实 Chrome
↓ 先跑通核心逻辑(登录→养号→蓝图)
阶段 B(稳定后):切换 Colima + Camoufox/Patchright 容器
↓ 上完整反检测方案,扩规模
```
**阶段 A 的优点**:
- 立刻可以开始,不依赖 Docker/Brew
- 真实 Chrome 无指纹问题
- 人工登录流程最自然(你直接在 Chrome 里操作)
**阶段 A 的限制**:
- Chrome 指纹是真实的(反检测弱),适合初期少量账号
- 每次只能跑 1 个 Chrome 实例(不支持多账号并发)
---
## 三、阶段 A:CDP 直连 Chrome 方案(当前可立即执行)
### 3.1 整体架构
```
你的 Mac(本机)
│
├── Chrome 实例 1(账号A)
│ ├── 调试端口 9222
│ └── 独立 User Data Dir:~/matrix/profiles/account_01/
│
├── Chrome 实例 2(账号B)
│ ├── 调试端口 9223
│ └── 独立 User Data Dir:~/matrix/profiles/account_02/
│
└── Python 调度器
└── 通过 CDP connect_over_cdp() 连接各 Chrome 实例
```
### 3.2 安装步骤(顺序执行)
```bash
# Step 1: 安装 Chrome
# 从 https://www.google.com/chrome/ 下载,安装到 /Applications/
# Step 2: 安装 Python 依赖
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 \
-m pip install patchright camoufox aiohttp aiosqlite
# Step 3: 安装 Patchright 浏览器核心
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 \
-m patchright install chromium
# Step 4: 验证
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 \
-c "from patchright.sync_api import sync_playwright; print('OK')"
```
### 3.3 启动 Chrome 的标准脚本
创建 `~/matrix/scripts/launch_chrome.sh`:
```bash
#!/bin/bash
# 用法: ./launch_chrome.sh <账号ID> <调试端口>
# 示例: ./launch_chrome.sh account_01 9222
ACCOUNT_ID=${1:-account_01}
DEBUG_PORT=${2:-9222}
PROFILE_DIR="$HOME/matrix/profiles/$ACCOUNT_ID"
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
mkdir -p "$PROFILE_DIR"
"$CHROME" \
--remote-debugging-port="$DEBUG_PORT" \
--user-data-dir="$PROFILE_DIR" \
--window-size=390,844 \
--no-first-run \
--no-default-browser-check \
--disable-extensions \
2>/dev/null &
echo "Chrome 已启动,账号: $ACCOUNT_ID,调试端口: $DEBUG_PORT"
echo "验证: curl http://localhost:$DEBUG_PORT/json/version"
```
### 3.4 核心 CDP 连接器
文件:`~/matrix/scripts/cdp_connector.py`
```python
#!/usr/bin/env python3
"""
CDP 连接器 - 连接本机 Chrome 实例
"""
import asyncio
import json
from patchright.async_api import async_playwright
class CDPConnector:
def __init__(self, port: int = 9222):
self.port = port
self.browser = None
self.context = None
self.page = None
async def connect(self):
"""连接到已运行的 Chrome"""
p = await async_playwright().start()
self.browser = await p.chromium.connect_over_cdp(
f"http://localhost:{self.port}"
)
self.context = self.browser.contexts[0]
if self.context.pages:
self.page = self.context.pages[0]
else:
self.page = await self.context.new_page()
print(f"已连接 Chrome(端口 {self.port}),页面数:{len(self.context.pages)}")
return self.page
async def new_page(self):
"""新建标签页"""
self.page = await self.context.new_page()
return self.page
async def goto(self, url: str, wait_until: str = "networkidle"):
"""导航到 URL"""
await self.page.goto(url, wait_until=wait_until, timeout=30000)
return self.page
async def setup_anti_jump(self):
"""设置 App 跳转拦截"""
cdp = await self.context.new_cdp_session(self.page)
blocked_schemes = [
"xhdsdiscover://*", "snssdk1128://*", "snssdk1233://*",
"kuaishou://*", "zhihu://*", "weixin://*",
"alipays://*", "taobao://*", "intent://*",
]
await cdp.send("Fetch.enable", {
"patterns": [
{"urlPattern": s, "requestStage": "Request"}
for s in blocked_schemes
]
})
async def handle_paused(event):
try:
await cdp.send("Fetch.failRequest", {
"requestId": event["requestId"],
"errorReason": "Aborted"
})
except:
pass
cdp.on("Fetch.requestPaused", handle_paused)
return cdp
async def setup_mobile_viewport(self, cdp_session):
"""设置移动端视口(iPhone 15 Pro)"""
await cdp_session.send("Emulation.setDeviceMetricsOverride", {
"width": 390, "height": 844,
"deviceScaleFactor": 3, "mobile": True,
"screenOrientation": {"type": "portrait", "angle": 0}
})
await cdp_session.send("Emulation.setTouchEmulationOverride", {
"enabled": True, "maxTouchPoints": 5
})
async def remove_overlays(self):
"""清理 App 跳转弹窗"""
script = """
() => {
const selectors = [
'[class*="download"]','[class*="open-app"]','[class*="app-guide"]',
'.open-in-app','.app-launch-mask','.download-tip','.bottom-bar',
'.download-banner','.open-app-btn','.app-download-tip',
'.open-app-layer','.download-guide-mask',
'.open-in-app-bar','.app-open-button',
'#app-launch-dialog','#open-app-modal',
];
let count = 0;
selectors.forEach(s => {
document.querySelectorAll(s).forEach(el => { el.remove(); count++; });
});
document.body.style.overflow = '';
document.body.style.overflowY = 'auto';
return count;
}
"""
removed = await self.page.evaluate(script)
if removed > 0:
print(f" 已清理 {removed} 个弹窗/遮罩")
return removed
# 使用示例
async def main():
conn = CDPConnector(port=9222)
page = await conn.connect()
cdp = await conn.setup_anti_jump()
await conn.setup_mobile_viewport(cdp)
await page.goto("https://www.douyin.com")
await asyncio.sleep(2)
await conn.remove_overlays()
print("当前URL:", page.url)
print("页面标题:", await page.title())
if __name__ == "__main__":
asyncio.run(main())
```
### 3.5 登录流程(阶段 A)
```
1. 运行 launch_chrome.sh account_01 9222
2. Chrome 弹出(390×844 手机比例)
3. 你在 Chrome 里手动导航到各平台,手机号登录
4. 登录成功后,Cookie 自动保存在 ~/matrix/profiles/account_01/
5. 关闭 Chrome 窗口
6. 下次用同一个 profile_dir 启动,Cookie 自动复用
```
### 3.6 阶段 A 的内存估算
| 组件 | 内存占用 |
|:---|:---|
| macOS 系统 | ~3GB |
| Chrome 实例 × 2(同时跑) | ~1.5GB × 2 = 3GB |
| Python 调度器 | ~200MB |
| **总计** | **~6.2GB(8GB 机器刚好可以跑 2 个实例)** |
**结论**:阶段 A 最多同时运行 2 个账号,轮换操作其他账号。
---
## 四、阶段 B:Colima + 容器方案(扩展期)
### 4.1 安装 Homebrew(先决条件)
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Apple Silicon 需要添加到 PATH
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
```
### 4.2 安装 Colima + Docker
```bash
brew install colima docker docker-compose
# 启动(给足资源,8GB 机器建议 6GB 给 Colima)
colima start --cpu 3 --memory 6 --disk 50 --arch aarch64 --vm-type vz
# 验证
docker ps
```
### 4.3 8GB 机器的容器资源分配建议
| 资源 | Colima VM | 宿主系统(macOS) |
|:---|:---|:---|
| CPU | 3核 | 1核(低负载) |
| 内存 | 6GB | 2GB(保底) |
| 磁盘 | 50GB | 剩余 |
**每容器内存限制**(避免 OOM):
```yaml
# docker-compose.yml 加上限制
services:
account_01:
mem_limit: 1.5g
memswap_limit: 2g
```
**8GB 机器最多稳定运行 3 个容器**(3 × 1.5GB = 4.5GB,留 1.5GB 给系统开销)
### 4.4 Camoufox ARM64 适配
Camoufox 官方 Docker 镜像以 x86 为主,ARM 需要特殊处理:
```dockerfile
# 使用官方提供的 ARM 兼容镜像(如有),或者用 x86 + Rosetta 模拟
FROM --platform=linux/amd64 ubuntu:22.04
```
在 colima 启动时加 Rosetta:
```bash
colima start --cpu 3 --memory 6 --disk 50 --arch x86_64 --vm-type vz --vz-rosetta
```
> **注意**:Rosetta 模拟会有约 20-30% 的性能损耗,但换来更好的兼容性。
---
## 五、现在可以立即做的事(今天)
按优先级排序:
### P0:安装 Chrome(5 分钟)
从 [google.com/chrome](https://www.google.com/chrome/) 下载,安装到 `/Applications/`
### P1:测试 CDP 连接(30 分钟)
```bash
# 启动 Chrome(带调试端口)
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/matrix/profiles/test" \
--window-size=390,844 &
# 验证 CDP 可用
curl http://localhost:9222/json/version
# 运行连接测试
/Users/7kecheng/.workbuddy/binaries/python/versions/3.13.12/bin/python3 \
~/matrix/scripts/cdp_connector.py
```
### P2:登录第一个账号(1 小时)
```bash
# 启动账号 account_01 的 Chrome
bash ~/matrix/scripts/launch_chrome.sh account_01 9222
# 在弹出的 Chrome 里手动登录抖音(手机号 18513308610)
# 成功后关闭 Chrome
# 重新启动,验证 Cookie 是否持久化
bash ~/matrix/scripts/launch_chrome.sh account_01 9222
# 直接打开 https://www.douyin.com,看是否还在登录态
```
### P3:安装 Homebrew(背景任务,不紧急)
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
---
## 六、与手册原文的差异对照表
| 手册原文 | 本地适配版本 | 原因 |
|:---|:---|:---|
| Docker + Colima(全程) | 阶段 A 用原生 Chrome + CDP | 优先跑通核心逻辑,降低初期复杂度 |
| Camoufox 容器 | 阶段 A 用真实 Chrome(指纹原生) | ARM Docker 兼容性风险 |
| 5容器并发 | 阶段 A 最多 2 个账号并发 | 8GB 内存限制 |
| 视口 390×844(容器内) | `--window-size=390,844` 启动参数 | Chrome 直接支持 |
| MLX 本地视觉(同步部署) | 阶段 B 再集成 | 优先走通主流程,MLX 是兜底方案 |
---
## 七、文件清单
```
~/matrix/
├── docs/
│ ├── IMPLEMENTATION_GUIDE.md ✅ 已创建(完整落地手册)
│ └── LOCAL_ADAPTATION.md ✅ 本文件
├── scripts/
│ ├── launch_chrome.sh ← 待创建(Chrome 启动脚本)
│ ├── cdp_connector.py ← 待创建(CDP 连接器)
│ ├── blueprint_engine.py ← 待创建(蓝图回放引擎)
│ ├── anti_jump.py ← 待创建(防跳转模块)
│ └── scheduler.py ← 待创建(总调度器)
├── profiles/ ✅ 已创建(账号 Cookie 目录)
├── data/matrix.db ← 待创建(首次运行自动建)
├── blueprints/ ✅ 已创建
├── corpus/ ✅ 已创建
└── docker-compose.yml ← 阶段 B 再写
```
@@ -0,0 +1,221 @@
# Matrix 养号系统 · 操作手册
> 本手册定义所有任务的标准化创建流程。
---
## 一、文件结构
```
~/matrix/
├── config/
│ ├── accounts.yaml ← 账号配置(手机/端口/Profile路径)
│ └── tasks.yaml ← 任务调度配置(蓝图+账号+开关)
├── blueprints/ ← 所有蓝图放这里
│ ├── douyin_browse.json
│ ├── xiaohongshu_like.json
│ └── zhihu_read.json
├── scripts/
│ ├── launch_chrome.sh ← 启动 Chrome 调试模式
│ ├── cdp_connector.py ← CDP 连接器(连接 Chrome + 反检测)
│ ├── task_engine.py ← 蓝图执行引擎
│ ├── task_scheduler.py ← 定时调度器
│ └── init_db.py ← 数据库初始化
├── data/
│ └── matrix.db ← SQLite 数据库
└── profiles/
└── account_01/ ← 账号 Cookie(自动保存)
└── Default/ ← Chrome Profile 目录
```
---
## 二、日常操作(每日标准流程)
### 1. 启动 Chrome 调试模式
```bash
bash ~/matrix/scripts/launch_chrome.sh account_01 9222
```
### 2. 手动登录(如需新账号)
在 Chrome 里登录平台 → Cookie 自动保存到 `~/matrix/profiles/<账号>/`
### 3. 执行单个蓝图
```bash
python3 ~/matrix/scripts/task_engine.py <蓝图名> --account <账号ID> --port <端口>
# 示例:执行抖音浏览蓝图
python3 ~/matrix/scripts/task_engine.py douyin_browse --account account_01
# 示例:执行小红书点赞蓝图
python3 ~/matrix/scripts/task_engine.py xiaohongshu_like --account account_01
```
### 4. 预览蓝图(不连接浏览器)
```bash
python3 ~/matrix/scripts/task_engine.py <蓝图名> --dry-run
```
### 5. 定时自动跑
```bash
# 前台运行
python3 ~/matrix/scripts/task_scheduler.py
# 或用 cron/launchd 定时触发
python3 ~/matrix/scripts/task_scheduler.py --once
```
---
## 三、标准化:新增一个完整任务
假设要新增「小红书点赞养号」任务,需要修改/新增以下 3 个文件:
### Step 1 → 新增蓝图文件
**文件**:`~/matrix/blueprints/xiaohongshu_like.json`
```json
{
"id": "xiaohongshu_like",
"name": "小红书点赞互动",
"platform": "xiaohongshu",
"version": "1.0.0",
"description": "浏览发现页,随机点赞收藏评论",
"steps": [
{
"op": "goto",
"args": { "url": "https://www.xiaohongshu.com" },
"wait_after": 3,
"retry": 2
},
{
"op": "remove_overlays",
"args": {},
"wait_after": 1
},
{
"op": "swipe_up",
"args": { "distance": 600, "duration_ms": 500 },
"wait_after": 5,
"wait_jitter": 1
},
{
"op": "evaluate",
"args": {
"script": "() => { const btn = document.querySelector('[class*=like]'); if(btn) btn.click(); }"
},
"wait_after": 2
},
{
"op": "swipe_up",
"args": { "distance": 600, "duration_ms": 450 },
"wait_after": 10,
"wait_jitter": 2
}
]
}
```
### Step 2 → 注册到任务配置
**文件**:`~/matrix/config/tasks.yaml`
```yaml
tasks:
- id: daily_xhs_like
name: 小红书点赞互动
blueprint: xiaohongshu_like # ← 对应 blueprints/*.json 的文件名
accounts:
- account_01
enabled: true # true=启用,false=禁用
```
### Step 3 → 如果是新平台,添加账号配置
**文件**:`~/matrix/config/accounts.yaml`
```yaml
accounts:
- id: account_02
platform: xiaohongshu
phone: "13800001111"
port: 9223 # 每个账号用不同端口
profile_dir: profiles/xhs_02
enabled: true
```
---
## 四、蓝图操作参考
### 支持的操作(op 字段)
| op | 说明 | 关键参数 |
|---|---|---|
| `goto` | 导航到 URL | `url` |
| `wait` | 等待秒数 | `seconds` |
| `swipe_up` | 向上滑动 | `distance`(px), `duration_ms` |
| `swipe_down` | 向下滑动 | `distance`(px), `duration_ms` |
| `touch_tap` | 触摸点击坐标 | `x`, `y` |
| `evaluate` | 执行 JS | `script`(返回 `()=>{...}` 函数) |
| `click` | 点击元素 | `selector` |
| `fill` | 填写输入框 | `selector`, `value` |
| `press` | 按键 | `selector`, `key` |
| `screenshot` | 截图 | `path`(可选) |
| `remove_overlays` | 清理 App 弹窗 | 无参数 |
| `wait_for` | 等待元素出现 | `selector`, `timeout` |
### 步骤级配置
每个步骤可额外设置:
```json
{
"op": "goto",
"args": { "url": "https://..." },
"wait_after": 3, // 步骤执行后等待 3 秒
"wait_jitter": 0.5, // 等待时间随机抖动 ±0.5 秒
"retry": 2 // 失败重试 2 次
}
```
---
## 五、进阶配置
### 多账号并发
每个账号用独立 Chrome 调试端口,示例:
```bash
# 账号1 → 端口9222
bash ~/matrix/scripts/launch_chrome.sh account_01 9222
# 账号2 → 端口9223
bash ~/matrix/scripts/launch_chrome.sh account_02 9223
```
### 熔断机制(自动)
连续失败 3 次自动暂停 30 分钟,人工介入后自动恢复。
### 数据库执行记录
所有执行自动写入 `~/matrix/data/matrix.db`,可用 sqlite3 查看:
```bash
sqlite3 ~/matrix/data/matrix.db "SELECT * FROM executions ORDER BY created_at DESC LIMIT 10;"
```
---
## 六、故障排查
| 症状 | 原因 | 处理 |
|---|---|---|
| `HTTP Error 502` | urllib 走了代理 | 检查 `cdp_connector.py` 是否用 `ProxyHandler({})` |
| `Emulation.setTouchEmulationOverride` 报错 | Chrome 147 移除了此 API | 升级 cdp_connector.py |
| `webSocketDebuggerUrl` 获取失败 | Chrome 未启动调试模式 | 先运行 `launch_chrome.sh` |
| 步骤执行正常但页面空白 | 窗口尺寸太小 | 修改 `launch_chrome.sh` 的 `--window-size` |
| Cookie 丢失 | Profile 路径不一致 | 确认 `accounts.yaml` 中 `profile_dir` 正确 |
@@ -0,0 +1,323 @@
# Matrix 矩阵养号系统 v5.0 使用文档
> 最后更新: 2026-06-11
> 适用版本: v5.0(PlatformOps 架构)
> 多机同步: Git + 坚果云
---
## 一、总体架构
```
mc(统一 CLI 入口)
│
├─ mc run → mc/engine.py(纯调度层,~300行)
│ └─ PlatformOps.execute()
│ ├── DouyinOps(抖音,20+ 操作,platform_ops 接口)
│ └── XhsOps(小红书,16 操作,ops/xhs_ops.py)
│
├─ mc account → matrix_mgmt.py(账号管理)
├─ mc blueprint → matrix_mgmt.py(蓝图管理)
├─ mc corpus → mc/corpus.py(语料库)
└─ mc status → cli.py(状态查询)
```
### 代码 vs 数据
| | 路径 | 共享方式 |
|---|---|---|
| 代码/蓝图 | `agent-sync/05_tools/07_matrix/` | Git 同步 |
| 账号配置 | `agent-local/tools/matrix/config/accounts.yaml` | 本机独立 |
| 浏览器指纹 | `agent-local/tools/matrix/identities/{name}/` | 本机独立 |
| 登录态 | `agent-local/tools/matrix/identities/{name}/user_data/` | 本机独立 |
| 联邦状态 | `cross_machine/data/matrix/{uid}.json` | Git 同步 |
---
## 二、新机部署
```bash
# 1. 拉取代码
cd ~/workbuddy-agent-os/agent-sync
git pull
# 2. 运行安装脚本(建目录 + 装依赖 + 配置)
bash 05_tools/07_matrix/install.sh
# 3. 确认 Python 环境
# mc 会自动检测 Python,或设置环境变量:
export AGENTOS_PYTHON=~/.workbuddy/binaries/python/envs/agent-os/bin/python3
# 4. 检查可用账号
cd 05_tools/07_matrix && ./mc account list
```
---
## 三、账号管理
### 新建账号身份
```bash
# 创建新身份(生成 BrowserForge 指纹)
python scripts/create_identity.py --name douyin_new --platform douyin
# 首次登录(浏览器打开,扫码/短信登录)
./mc account login douyin_new
```
### 同步已有账号(跨机迁移)
```bash
# 旧机器导出
./mc account export # → 生成压缩包
# 新机器导入
./mc account import <path_to_export.tar.gz>
```
---
## 四、cli 命令参考
### 养号执行
```bash
# 单账号单轮(测试用)
./mc run --accounts douyin_test --blueprints douyin_active_v1 --rounds 1
# 多账号并行
./mc run --accounts douyin_test,douyin_133 --blueprints douyin_active_v1 --rounds 3
# 混合多蓝图(每轮随机选)
./mc run --accounts douyin_test --blueprints douyin_active_v1,douyin_browse_v2 --rounds 3 --mix
# 指定小红书
./mc run --accounts xhs_01 --blueprints xhs_active_v1 --rounds 2
# 完整参数
./mc run --accounts A,B --blueprints X,Y --rounds 3 --mix --interval 45-90 --engine auto
```
### 账号管理
```bash
./mc account list # 列表(含登录态)
./mc account login <name> # 首次登录
./mc account status [name] # 查看状态
./mc account export # 导出
./mc account import <path> # 导入
```
### 蓝图管理
```bash
./mc blueprint list # 列表
./mc blueprint show <name> # 详情
```
### 状态查询
```bash
./mc status all # 全局状态
./mc status accounts # 账号列表
./mc status browsers # 浏览器进程
```
### 旧版兼容
旧 `matrix.py` 仍可用但标记 deprecated。对照表:
| 旧命令(matrix.py) | 新命令(mc) |
|---|---|
| `python matrix.py account list` | `./mc account list` |
| `python matrix.py account login X` | `./mc account login X` |
| `python matrix.py nurture run -a X -r 10` | `./mc run --accounts X --rounds 10 ...` |
| `python matrix.py status all` | `./mc status all` |
| `python matrix.py config blueprint list` | `./mc blueprint list` |
---
## 五、蓝图说明
### 抖音蓝图
| 蓝图 | 步骤 | 说明 |
|------|------|------|
| `douyin_active_v1` | 27步 | 浏览×6 + 点赞×4 + 评论×2 + 收藏×1 + 搜索×2 |
| `douyin_browse_v2` | 11步 | 轻量浏览+随机点赞 |
| `douyin_nurture_v1` | 8步 | 搜索→浏览→点赞→收藏→切换 |
| `douyin_search_browse` | 7步 | 搜索→浏览→点赞 |
| `douyin_comment_interact` | 8步 | 浏览→打开评论区→发评论 |
### 小红书蓝图
| 蓝图 | 步骤 | 说明 |
|------|------|------|
| `xhs_active_v1` | 26步 | 浏览×5 + 点赞×5 + 搜索×2 + 收藏×2 + 评论×1 + 关注×1 |
| `xiaohongshu_nurture_v2` | 11步 | 浏览→点击→点赞→收藏→搜索→关注 |
### 蓝图格式
```json
{
"id": "蓝图唯一ID",
"name": "显示名称",
"platform": "douyin 或 xiaohongshu",
"description": "说明",
"steps": [
{"step_id": 1, "op": "操作名", "args": {}},
...
]
}
```
**抖音可用操作**: goto_home, like, collect, follow, open_comments, close_comments, post_comment, next_video, prev_video, search, wait_watch, scroll_feed, open_video, wait, goto_url, go_back
**小红书可用操作**: xhs_goto_home, xhs_browse, xhs_scroll_feed, xhs_click_note, xhs_like, xhs_collect, xhs_comment, xhs_follow, xhs_search, xhs_goto_profile, xhs_read_nickname, xhs_read_user_id, xhs_read_following, xhs_read_fans, xhs_read_likes, xhs_read_bio
---
## 六、自动化任务
### 每日养号
```bash
# 完整执行
bash scripts/nurture_daily.sh
# 仅查看命令(不执行)
bash scripts/nurture_daily.sh --dry
# 仅跑第1组
bash scripts/nurture_daily.sh --group 1
```
### 定时执行(cron)
```cron
# 每天 9:00 和 21:00 执行
0 9,21 * * * cd ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix && bash scripts/nurture_daily.sh >> /tmp/nurture_daily.log 2>&1
```
### 定时调度器(旧版兼容)
```bash
python scripts/task_scheduler.py
```
---
## 七、多账号并行
`mc run` 原生支持多账号并行。每个账号启动独立 Camoufox 浏览器,窗口自动错位:
| 账号索引 | 窗口位置 | 屏幕区域 |
|----------|---------|---------|
| 0 | (30, 30) | 左上 |
| 1 | (400, 30) | 中上 |
| 2 | (800, 30) | 右上 |
| 3 | (30, 450) | 左下 |
| 4 | (400, 450) | 中下 |
| 5 | (800, 870) | 右下 |
```bash
# 3 个抖音同时跑
./mc run --accounts douyin_test,douyin_133,douyin_133_2 --blueprints douyin_active_v1 --rounds 3
```
---
## 八、跨机器同步流程
```bash
# 主机器修改后
cd ~/workbuddy-agent-os/agent-sync
git add 05_tools/07_matrix/
git commit -m "Matrix v5.0 更新"
git push
# 其他机器
cd ~/workbuddy-agent-os/agent-sync
git pull
bash 05_tools/07_matrix/install.sh # 如需要重建本地目录
```
**不同步的内容**(各机器独立):
- `agent-local/tools/matrix/config/accounts.yaml` — 账号配置
- `agent-local/tools/matrix/identities/` — 浏览器指纹 + 登录态
- `agent-local/tools/matrix/logs/` — 运行日志
---
## 九、v5.0 变更说明
### 新增
- `mc` 统一 CLI(替代 `matrix.py`)
- `ops/_base.py` — PlatformOps 基类 + OpResult 结构化结果
- `ops/xhs_ops.py` — 小红书 16 个原子操作
- `mc/runlog.py` — 结构化运行日志(JSONL)
- `mc/engine.py` — 重构引擎(~300行,原 775行)
- `blueprints/douyin_active_v1.json` — 抖音高活跃蓝图
- `blueprints/xhs_active_v1.json` — 小红书高活跃蓝图
- `docs/MC_COMMAND_REFERENCE.md` — 命令参考手册
### 改进
- `douyin_ops.py` 实现 PlatformOps 接口,每个操作返回 OpResult
- `_click_selector` 增加 JS evaluate fallback(穿透 Shadow DOM)
- `goto_home` 增加 commit 模式 + 强制继续(不因 video 未就绪阻塞)
- 进入视频模式:从卡片 `data-aweme-id` 提取视频 ID 直接导航(/video/{id})
- 视频页双击 video 获取焦点(原代码约定)
- post_comment 用 pbcopy + Meta+V(Draft.js 兼容)
- 所有 `force=True` 跳过 Playwright 可点击性检查
- 硬编码路径修复(Python、用户名)
- `.gitignore` 排除临时蓝图和一次性脚本
### 废弃
- `matrix.py` — 标记 deprecated(用 mc 替代)
- `yanghao_runner.py` — 功能合并到 mc
- `task_engine.py` — 功能合并到 mc/engine.py
- `camoufox_manager.py` / `camoufox_server.py` — 被 cdp_connector.py 取代
- `browser_manager.py` / `browser_keepalive.py` — mc/browser.py 取代
- 25+ 调试脚本 — 移入 `_archive/`
---
## 十、故障排查
### 常见问题
| 问题 | 原因 | 解决 |
|------|------|------|
| 页面加载超时 | Camoufox 下 douyin.com 使用 `commit` 模式 | 自动 fallback,强制继续 |
| like/collect 失败 | Shadow DOM 影响 Playwright locator | 自动 fallback 到 JS evaluate |
| post_comment 失败 | 评论区无标准输入框 DOM | 不依赖选择器,pbcopy + Meta+V 硬发 |
| 多账号窗口重叠 | 未设置 window_position | 自动按索引分配错位 |
| "代理"行消失 | 账号 config.yaml 中 proxy 被注释 | 取消注释恢复代理 |
### 调试命令
```bash
# 查看运行日志
cat /tmp/matrix_step_douyin_test.log
# Peekaboo 截图观察
peekaboo image
# 原子操作诊断
python scripts/diagnose_ops.py
```
### 版本信息
当前所有文档统一版本:v5.0.0。文件清单:
- `MODULE.md` — v5.0.0
- `TOOL.md` — v5.0
- `mc/__main__.py` — v1.0.0(mc CLI 版本)
- `docs/MC_COMMAND_REFERENCE.md` — v5.0
- `blueprints/*.json` — 每个蓝图独立版本
- `install.sh` — v4.0(未变)
@@ -0,0 +1,224 @@
# mc 命令参考手册 — Matrix v5.2
> 统一 CLI 入口,替代旧版 matrix.py。
> 智能体通过 mc 命令调用 Matrix,不需要直接操作 Python 脚本。
---
## 安装
```bash
cd ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
chmod +x mc
# 可选:ln -s $(pwd)/mc /usr/local/bin/mc
```
Python 路径自动检测(AGENTOS_PYTHON > agent-os venv > 系统 python3),换机无需修改。
---
## 命令一览
### 录制系统(v5.1 新增)
```bash
# 开始录制
mc record start --account douyin_test
# → 启动 Camoufox,你在浏览器操作,关键节点按数字键 1-8 标记
# 按 0 结束录制
# 查看录制包
mc record list
# 分析录制包
mc record analyze <录制包路径>
# 导出为蓝图+代码
mc record export <录制包路径>
# 删除录制包
mc record delete <录制包路径>
```
### 定向评论(v5.1 新增)
```bash
# 创建一个仅包含 "打开视频→评论" 的蓝图,然后执行:
mc run --accounts douyin_test --blueprints douyin_comment_test --rounds 1 --keep
# 或手动创建自定义蓝图:
# 1. 在 blueprints/ 下创建 JSON 文件
# 2. 包含 goto_url → wait_watch → open_comments → post_comment
# 3. 用 mc run 执行
```
### 养号执行
```bash
# 批量养号(推荐)
mc run --accounts douyin_01,douyin_02 --blueprints nurture_v1 --rounds 3 --mix --interval 45-90
# 指定语料库
mc run --accounts douyin_01 --blueprints nurture_v1 --rounds 3 --corpus daily_hot
# 查看帮助
mc run --help
```
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `--accounts` | 逗号分隔的账号列表 | 必填 |
| `--blueprints` | 逗号分隔的蓝图列表 | 必填 |
| `--rounds` | 每账号执行轮数 | 10 |
| `--mix` | 混合随机模式(每轮随机选蓝图) | 顺序模式 |
| `--interval` | 轮间间隔(秒),支持范围 `45-90` | 30-60 |
| `--corpus` | 逗号分隔的语料库分类 | 无 |
| `--daemon` | 后台运行模式 | 否 |
| `--engine` | 引擎(auto/camoufox) | auto |
| `--keep` | 执行完后保留浏览器(需手动关) | 否 |
| `--json` | 输出 JSON 格式结果 | 否 |
### 账号管理
```bash
mc account list # 列出所有账号及状态
mc account login <name> # 首次手动登录(打开浏览器等用户操作)
mc account status [name] # 查看账号登录状态
mc account export # 导出账号配置
mc account import <path> # 导入账号配置
```
### 注册新账号(Dashboard 推荐)
在 Dashboard 上完成:
1. 启动 Dashboard → 左侧「矩阵」→「短信与管理」
2. 底部「注册新账号」表单
3. 选平台、填手机号、填备注名 → 点确认
4. 浏览器自动打开 → 扫码登录 → 后续可采集昵称
5. 登录后点「采集昵称」获取昵称/粉丝数
也可以通过 CLI:
```bash
python scripts/matrix.py account create --platform douyin --phone 138xxxx
```
### 短信 API(Dashboard 推荐)
Dashboard 短信页面:
1. 左侧「矩阵」→「短信与管理」
2. 下拉选账号 → 点「查短信」
3. 系统自动解析抖音/小红书验证码
API:
```bash
# 查短信
GET /api/matrix/sms/test/{phone_or_account}
# 查看所有账号手机号
GET /api/matrix/sms/accounts
```
### Dashboard 启动
```bash
cd ~/workbuddy-agent-os/agent-sync/05_tools/10_dashboard
nohup ~/.workbuddy/binaries/python/envs/agent-os/bin/uvicorn app:app --host 0.0.0.0 --port 9988 &
# 浏览器打开 http://localhost:9988
# 左侧「矩阵」:短信 / 录制 / 账号管理
```
### 新机部署说明
新机部署后同步账号:
```bash
bash ~/workbuddy-agent-os/agent-sync/00_bootstrap/init.sh
# 自动拉取仓库 → 安装依赖 → 同步本机账号
```
`accounts_registry.yaml` 中 `assigned_machine` 标记为当前机器 UID 的账号会自动同步到本机。
### 蓝图管理
```bash
mc blueprint list # 列出所有蓝图
mc blueprint show <name> # 查看蓝图详情
```
### 语料库
```bash
mc corpus list # 查看语料分类
mc corpus add <平台> "语料" # 添加语料
```
### 状态查询
```bash
mc status all # 全局状态(浏览器+账号)
mc status accounts # 账号登录状态
mc status browsers # 浏览器运行状态
```
### 代理管理
```bash
mc proxy list # 代理列表
mc proxy test <name> # 测试代理
```
---
## 自动化任务
### 每日养号(定时执行)
```bash
# 完整执行
bash ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/nurture_daily.sh
# 仅查看命令(不执行)
bash ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/nurture_daily.sh --dry
# 仅跑第1组
bash ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/nurture_daily.sh --group 1
```
日志:`/tmp/nurture_daily_YYYYMMDD.log`
### 结构化日志
每次 mc run 执行后自动写入:
`agent-local/tools/matrix/logs/run_{date}_{account}.jsonl`
查询:
```python
from mc.runlog import get_today_summary
print(get_today_summary())
# → {"date": "20260609", "accounts": {"douyin_01": {"runs": 3, "last_success": 8, ...}}}
```
---
## 与旧版 matrix.py 对照
| 旧命令 | 新命令 |
|--------|--------|
| `python matrix.py account list` | `mc account list` |
| `python matrix.py account login X` | `mc account login X` |
| `python matrix.py nurture run -a X -r 10` | `mc run --accounts X --rounds 10 ...` |
| `python matrix.py status all` | `mc status all` |
| `python matrix.py config blueprint list` | `mc blueprint list` |
---
## 智能体调用约定
智能体通过 WorkBuddy 技能 `matrix` 触发。SKILL.md 定义了触发词和对应的 mc 命令。
当用户说"养号"、"执行蓝图"、"账号状态"时,智能体:
1. 加载 `02_skills/matrix/SKILL.md`
2. 根据触发词匹配对应的 mc 命令
3. 执行命令并汇报结果
**不需要读源码。不需要知道 engine.py 内部细节。**
@@ -0,0 +1,57 @@
# SMS 验证码 API 使用说明
## 服务地址
`https://wx.tyhtak.com`
## API 接口
```
GET /api/biz/msg/messages
?api_key=gtmsg2026
&receiver_phone={phone}
&page=1&per_page=20
```
## 配置方式
### 1. 全局默认手机号(scripts/config/sms.yaml)
```yaml
sms:
api_key: "gtmsg2026"
phone: "15370103682" # 默认手机号
base_url: "https://wx.tyhtak.com/api/biz/msg/messages"
```
### 2. 账号级手机号(accounts.yaml)
```yaml
accounts:
- id: douyin_01
phone: "18513308610" # 每个账号绑定自己的手机号
- id: douyin_02
phone: "18500003366"
- id: douyin_camo01
phone: "15370103682"
```
### 3. 运行时优先级
`sms_login(account_name='douyin_02')` → 查 accounts.yaml → 拿到 phone=18500003366 → 调用 API 时用 `receiver_phone=18500003366`
### 4. 三个账号的 API 支持情况
所有账号共用同一个 API 平台(wx.tyhtak.com),只需在 API URL 中替换 `receiver_phone` 参数即可查询对应手机号的短信。
| 账号 | 手机号 | API receiver_phone |
|------|--------|-------------------|
| douyin_01 | 18513308610 | 替换即可 |
| douyin_02 | 18500003366 | 替换即可 |
| douyin_camo01 | 15370103682 | 替换即可 |
## 使用方式
```python
# 自动模式:从 accounts.yaml 查手机号
from matrix_modules.account.sms_login import sms_login
ok = await sms_login(page, account_name='douyin_02')
# 手动模式:指定手机号
from matrix_modules.account.sms import ApiSMSHandler
handler = ApiSMSHandler(phone='18500003366')
code = await handler.wait('抖音登录', timeout=60)
```
@@ -0,0 +1,252 @@
# Matrix 统— 多机同步标准化指南
> **版本**: v1.0.0
> **最后更新**: 2026-05-01
> **适用场景**: 本机测试完成后,将修改同步到其他电脑
---
## 1. 同步架构总览
```
主电脑(Redmi-12C)
│
├── agent-sync/ ←────────── 坚果云 / Gitee ──────────→ agent-sync/(其他电脑)
│ ├── 02_skills/matrix/ 代码 + 文档 │
│ ├── 05_tools/07_matrix/ │
│ └── ... │
│ │
└── agent-local/ ←────────── 不同步(每机独立) ────→ agent-local/(其他电脑)
└── tools/matrix/ │
├── config/accounts.yaml (手动配置) │
├── profiles/ (手动登录) │
├── data/cookies/ (重新导出) │
└── data/matrix.db (重新初始化) │
```
### 同步内容清单
| 类别 | 内容 | 同步方式 | 是否同步 |
|------|------|---------|---------|
| **代码脚本** | scripts/*.py, *.sh | Git + 坚果云 | ✅ |
| **蓝图文件** | blueprints/*.json | Git + 坚果云 | ✅ |
| **标准化文档** | docs/*.md | Git + 坚果云 | ✅ |
| **技能定义** | 02_skills/matrix/ | Git + 坚果云 | ✅ |
| **安装脚本** | install.sh | Git + 坚果云 | ✅ |
| **依赖清单** | requirements.txt | Git + 坚果云 | ✅ |
| **配置模板** | config_template/ | Git + 坚果云 | ✅ |
| **local.yaml** | 本机路径配置 | 每机独立生成 | ❌ |
| **accounts.yaml** | 手机号/端口 | 每机手动配置 | ❌ |
| **Chrome Profile** | 浏览器登录数据 | 每机重新登录 | ❌ |
| **Cookie 文件** | data/cookies/ | 每机重新导出 | ❌ |
| **matrix.db** | 执行记录/DB | 每机重新初始化 | ❌ |
| **Python venv** | 安装的包 | 每机重新安装 | ❌ |
| **Playwright Chromium** | 浏览器二进制 | 每机重新安装 | ❌ |
| **Camoufox** | Firefox 浏览器 | 每机重新安装 | ❌ |
---
## 2. 标准化同步流程
### 2.1 在主电脑上提交修改
```bash
# 1. 确认所有修改已保存
cd ~/workbuddy-agent-os/agent-sync
# 2. 查看修改文件
git status
# 预期看到:
# modified: 02_skills/matrix/SKILL.md
# modified: 05_tools/07_matrix/scripts/switch_account.py
# modified: 05_tools/07_matrix/scripts/cdp_connector.py
# new file: 05_tools/07_matrix/scripts/auth_manager.py
# modified: 05_tools/07_matrix/docs/FULL_TEST_REPORT.md
# new file: 05_tools/07_matrix/docs/SYSTEM_ARCHITECTURE.md
# new file: 05_tools/07_matrix/docs/SYNC_GUIDE.md
# 3. 提交并推送
git add -A
git commit -m "Matrix 系统修复: 原子化登录模块 + 稳定性修复"
git push origin main
```
> **注意**: 坚果云会同步 `agent-sync/` 目录下的文件修改,但建议以 Git 为主要版本控制手段,坚果云仅作为文件快速分发。两台电脑都需要安装坚果云并登录同一账号。
### 2.2 在其他电脑上拉取更新
```bash
# 方式A: 通过 Git(推荐,版本可控)
cd ~/workbuddy-agent-os/agent-sync
git pull origin main
# 方式B: 通过坚果云(自动同步)
# 确保两台电脑登录同一坚果云账号,agent-sync/ 目录会自动同步
```
### 2.3 在新电脑上运行安装脚本
```bash
# 1. 运行一键安装(建立目录骨架 + 安装依赖 + 生成配置)
bash ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/install.sh
# 2. 安装 Camoufox(额外的 Firefox 浏览器)
~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/install_camoufox.sh
# 或手动:
# pip install camoufox
# python -m camoufox fetch
```
### 2.4 配置本地账号
```bash
# 编辑本机 accounts.yaml(手机号/端口/备注按本机设置)
vim ~/workbuddy-agent-os/agent-local/tools/matrix/config/accounts.yaml
# 必须修改的字段:
# - phone: 本机对应的手机号
# - port: 确保端口不冲突(Chrome: 9222+, Camoufox: 9301+)
```
### 2.5 初始化数据库(可选)
```bash
# 如果新机没有 matrix.db,初始化一个
cd ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/scripts/init_db.py
```
### 2.6 重新登录各平台
```bash
# 每个账号需要重新手动登录一次
# Chrome 方案:
cd ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
python scripts/switch_account.py --method profile --target douyin_01 --port 9222
# → 浏览器打开 douyin.com,扫码登录
# → 登录后自动检测 Cookie → 自动导出到 data/cookies/
# Camoufox 方案:
python scripts/yanghao_runner.py --account douyin_camo01 --blueprint douyin_browse_v2 --browser camoufox
```
---
## 3. 本地依赖完整清单
以下是在新电脑上需要重新部署的所有本地依赖:
### 3.1 Python 包
```bash
# 使用 agent-os 的 Python 环境
PY=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/install.sh 自动处理
# 或手动安装:
pip install -r ~/workbuddy-agent-os/agent-sync/05_tools/07_matrix/requirements.txt
# 包含: patchright, camoufox, PyYAML
```
### 3.2 Playwright 浏览器
```bash
# Chromium(Chrome 方案需要)
python -m patchright install chromium
# 注意: 不需要 firefox(Camoufox 自带 Firefox)
```
### 3.3 Camoufox 浏览器
```bash
# Camoufox 方案需要
pip install camoufox
python -m camoufox fetch
# 下载到: ~/Library/Caches/camoufox/Camoufox.app/
```
### 3.4 本地目录结构
```
agent-local/tools/matrix/
├── config/
│ └── accounts.yaml # 手动配置(每机不同)
├── data/
│ ├── cookies/ # 登录后自动导出
│ ├── camoufox_pids/ # 运行时自动创建
│ └── matrix.db # init_db.py 初始化
├── profiles/
│ ├── account_01/ # 登录后自动生成
│ ├── douyin_02/
│ ├── camoufox_01/
│ └── camoufox_02/
├── logs/ # 运行时日志
└── screenshots/ # 可选
```
---
## 4. 版本对照表
每次修改后,在主电脑上记录修改范围,方便其他电脑核对自己是否拉取了最新代码。
### v2.1.0 (2026-05-01) 修改清单
| 文件 | 修改类型 | 说明 |
|------|---------|------|
| `scripts/auth_manager.py` | **新增** | 原子化登录模块(Cookie 检测/导出/注入) |
| `scripts/switch_account.py` | 修改 | 登录检测改为 auth_manager,自动导出 Cookie |
| `scripts/cdp_connector.py` | 修改 | 添加 os=windows / 中文字体 / 委托 auth_manager |
| `02_skills/matrix/SKILL.md` | 修改 | 更新正确入口、新增原子模块说明 |
| `docs/FULL_TEST_REPORT.md` | 修改 | 新增修复记录、已验证功能 |
| `docs/SYSTEM_ARCHITECTURE.md` | **新增** | 全链路系统架构文档 |
| `docs/ACCOUNT_LOGIN_SOP.md` | **新增** | 账号登录标准化操作流程 |
| `docs/SYNC_GUIDE.md` | **新增** | 多机同步指南(本文) |
| `blueprints/douyin_browse_v3.json` | DB注册 | 已在主电脑注册到 task_blueprints |
| `blueprints/douyin_nurture_v1.json` | DB注册 | 同上 |
| `profiles/douyin_01/` | 删除 | 冗余目录(11MB) |
**新机注意事项**:
- DB 注册的蓝图(v3/nurture)需在新机重新运行 `init_db.py` 或在首次使用时自动注册
- 删除冗余目录是主电脑操作,新机没有这个目录,无需处理
---
## 5. 常见问题
### Q: 为什么不能直接复制 Chrome Profile?
Chrome Profile 包含设备绑定的加密数据(Cookie 用本机密钥加密),复制到其他电脑会失效。必须在新机重新扫码登录。
### Q: 为什么 accounts.yaml 不能同步?
每个账号绑定的手机号是固定的,但不同电脑上 Chrome Profile 目录路径和端口分配可能不同(例如一台电脑 9222 已占用,需要用 9224)。
### Q: 主电脑上的修改如何确保不丢失?
```bash
# 养成好习惯:每次修改后
cd ~/workbuddy-agent-os/agent-sync
git add -A
git commit -m "描述修改内容"
git push origin main
```
### Q: 新电脑需要安装什么额外软件?
- 坚果云(同步 agent-sync/ 文件)
- Git(拉取代码)
- Chrome 浏览器(养号工具使用)
- agent-os 的 Python 环境(install.sh 自动处理)
---
## 6. 快速检查清单
新机部署完成后,逐项确认:
- [ ] Git pull 或坚果云同步完成
- [ ] install.sh 运行成功
- [ ] Camoufox 安装完成(如需要)
- [ ] accounts.yaml 已配置本机账号
- [ ] matrix.db 已初始化
- [ ] Chrome Profile 已重新登录
- [ ] Cookie 已自动导出
- [ ] `python scripts/switch_account.py --status` 显示 active
- [ ] `python scripts/auth_manager.py 9222` 显示 sessionid ✅
@@ -0,0 +1,181 @@
# Matrix 养号系统 — 系统架构设计
> **版本**:v2.1.0
> **最后更新**:2026-05-01
> **设计原则**:原子化可复用 → 固化不反复 → 全链路串接
---
## 1. 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 用户触发(对话/定时) │
└─────────────────┬───────────────────────────┬───────────────┘
│ │
┌───────▼───────┐ ┌─────────▼────────┐
│ yanghao_runner.py │ │ task_scheduler.py │
│ (手动/交互入口) │ │ (定时任务入口) │
└───────┬───────┘ └─────────┬────────┘
│ │
└──────────┬────────────────┘
│
┌────────▼────────┐
│ CDPConnector │
│ (浏览器连接层) │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───────▼──────┐ ┌────▼────┐ ┌───────▼──────┐
│ Chrome CDP │ │Camoufox │ │ auth_manager │
│ (端口 9222+) │ │原生 API │ │ (登录管理) │
└───────┬──────┘ └────┬────┘ └───────┬──────┘
│ │ │
└──────┬───────┘ │
│ │
┌──────▼──────┐ ┌─────────▼────────┐
│ DouyinOps │ │ Cookie 文件 │
│ (原子操作库) │ │ (持久化存储) │
└──────┬──────┘ └─────────────────┘
│
┌──────▼──────┐
│ 蓝图引擎 │
│ task_engine │
└─────────────┘
```
---
## 2. 原子化模块清单
| 模块 | 文件 | 职责 | 依赖 |
|------|------|------|------|
| **auth_manager** | `scripts/auth_manager.py` | 登录检测、Cookie 导出/注入、等待登录 | 无(纯函数 + Playwright API) |
| **cdp_connector** | `scripts/cdp_connector.py` | 浏览器连接(Chrome CDP / Camoufox 原生) | auth_manager(登录检测) |
| **douyin_ops** | `scripts/douyin_ops.py` | 抖音原子操作(18个:like/collect/comment/scroll等) | Playwright Page |
| **local_paths** | `scripts/local_paths.py` | 统一路径管理 | 无 |
| **page_state** | `scripts/page_state.py` | 页面模式检测、状态建议 | Playwright Page |
| **task_engine** | `scripts/task_engine.py` | 蓝图加载与逐步执行 | douyin_ops |
| **yanghao_runner** | `scripts/yanghao_runner.py` | 完整养号流程入口(交互/参数双模式) | cdp_connector + douyin_ops |
| **switch_account** | `scripts/switch_account.py` | 账号切换、状态查看 | auth_manager |
| **full_test** | `scripts/full_test.py` | 全面测试 + 报告生成 | task_engine + auth_manager |
---
## 3. 登录管理(auth_manager 原子操作)
### 3.1 检测流程
```
get_login_status(context, page)
│
├── Cookie 检测(主方案)
│ ├── 遍历所有 Cookie
│ ├── 检查 sessionid / sid_guard 是否存在
│ └── 返回 cookie_ok + cookie_count + session_id
│
└── DOM 检测(备选,仅移动端视口有效)
├── 检查 [data-e2e='user-avatar']
├── 检查 [data-e2e='user-detail']
└── 返回 dom_ok
└── 综合判断
├── cookie_ok + dom_ok → logged_in=True, method="both"
├── cookie_ok only → logged_in=True, method="cookie"
├── dom_ok only → logged_in=True, method="dom"
└── 均无 → logged_in=False
```
### 3.2 可用 API
| 函数 | 用途 | 返回 |
|------|------|------|
| `check_login_by_cookie(context)` | Cookie 检测 | bool |
| `check_login_by_dom(page, platform)` | DOM 检测 | bool |
| `get_login_status(context, page)` | 多维综合检测 | dict |
| `export_cookies(context, path)` | 导出 Cookie | int(数量) |
| `inject_cookies(context, path)` | 注入 Cookie | int(数量) |
| `wait_for_login(context, page)` | 等待手动登录 | dict |
| `get_session_id(cookies)` | 提取 sessionid | str/None |
### 3.3 持久化策略
```
Chrome 方案:Profile 目录持久化(浏览器关闭重启后 Cookie 自动保留)
↓
首次切换/登录后 → auth_manager.export_cookies() → data/cookies/{id}_cookies.json
↓
Camoufox 启动时 → auth_manager.inject_cookies() → 从 Cookie 文件恢复登录
```
---
## 4. 浏览器方案对比
| 维度 | Chrome CDP | Camoufox (Firefox) |
|------|-----------|-------------------|
| 启动入口 | `switch_account.py --method profile` | `CDPConnector(browser_type="camoufox")` |
| 登录方式 | Chrome Profile 持久化(自动) | Cookie 注入(从文件) |
| 登录检测 | auth_manager Cookie 检测 | auth_manager Cookie 检测 |
| 操作系统指纹 | 实际 macOS(CDP 覆盖 UA) | 固定 Windows(`os='windows'`) |
| 中文字体 | 系统自带 | 需指定 fonts 参数 |
| 反检测 | CDP 指纹注入 + JS 注入 | Camoufox 内置 + humanize |
| 适用场景 | 主力养号(doyin_01/02) | 辅助养号(douyin_camo01/02) |
---
## 5. 蓝图执行链路
```
yanghao_runner.py / task_engine.py
│
├── 1. CDPConnector.connect() → 启动/连接浏览器
├── 2. conn.init_anti_detection() → 指纹注入
├── 3. auth_manager.get_login_status() → 验证登录
├── 4. 加载 blueprint JSON → 读取步骤
├── 5. DouyinOps(page) → 绑定操作对象
├── 6. 逐步骤执行:
│ ├── goto_home()
│ ├── wait_watch(seconds)
│ ├── like(probability)
│ ├── collect(probability)
│ ├── next_video()
│ ├── search(keyword)
│ ├── post_comment(text)
│ └── ...
└── 7. 记录执行结果 → matrix.db
```
---
## 6. 已知问题跟踪
| ID | 状态 | 问题 | 涉及文件 | 修复日期 |
|----|------|------|---------|---------|
| P0 | ✅ 已修复 | DOM 登录检测桌面端误报 | auth_manager.py + switch_account.py | 2026-05-01 |
| P1 | ✅ 已修复 | Camoufox 显示 macOS | cdp_connector.py | 2026-05-01 |
| P1 | ✅ 已修复 | Camoufox 中文乱码 | cdp_connector.py | 2026-05-01 |
| P2 | ✅ 已修复 | douyin_browse_v3/nurture_v1 未注册 DB | DB task_blueprints | 2026-05-01 |
| P3 | ✅ 已修复 | 冗余 profiles/douyin_01 目录(11MB) | 文件系统 | 2026-05-01 |
---
## 7. 快速操作指南
```bash
TOOL=~/workbuddy-agent-os/agent-sync/05_tools/07_matrix
# 查看账号状态
python $TOOL/scripts/switch_account.py --status
python $TOOL/scripts/auth_manager.py 9222 # 独立登录检测
# 启动 Chrome + 切换账号 + 自动导出 Cookie
python $TOOL/scripts/switch_account.py --method profile --target douyin_01 --port 9222
# 启动 Camoufox + Cookie 注入 + 执行蓝图(正确方式)
python $TOOL/scripts/yanghao_runner.py --account douyin_camo01 --blueprint douyin_browse_v2 --browser camoufox
# 完整测试
python $TOOL/scripts/full_test.py --account-only douyin_01
```