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,46 @@
---
id: KB-20260601-017
title: ""
type: method
status: published
nature: method
domain: [general]
subdomain: []
tags: ["待补充"]
confidence: 0.5
source: ""
source_type: unknown
date_created: 2026-06-01
date_modified: 2026-06-01
version: 1
previous_version: ""
superseded_by: ""
summary: ""
collected: true
collected_date: 2026-06-02---
# 2026-05-17_2026-05-16_原子化登录管理模块_auth_manager
> 来源:03_knowledge/20_methods
# 2026-05-16_原子化登录管理模块_auth_manager
# 原子化登录管理模块 auth_manager
# auth_manager — 原子化登录管理模块
## 概述
## 核心函数
### Cookie 检测(主方案)
### DOM 检测(备选,仅移动端视口有效)
### 综合检测
# 返回: logged_in, cookie_ok, dom_ok, cookie_count, session_id, method
### Cookie 持久化
### 等待手动登录
## 设计原则
1. Cookie 检测为主方案(不依赖 UI 布局,桌面端/移动端均可用)
2. DOM 检测为备选(仅在 mobile=True 视口下有效)
3. 所有函数无副作用(不写 DB/文件,除非函数名明确)
4. 统一异常处理(所有异常返回 False/空值)
## 解决的问题
- `switch_account.py` 此前用 `[data-e2e="user-avatar"]` DOM 检测,桌面端视口下该元素不可见,导致已登录账号误报"未登录"
- 所有脚本各自实现登录检测,逻辑分散
@@ -0,0 +1,41 @@
---
id: KB-20260602-003
title: ""
type: method
status: published
nature: method
domain: [general]
subdomain: []
tags: ["待补充"]
confidence: 0.5
source: ""
source_type: unknown
date_created: 2026-06-02
date_modified: 2026-06-02
version: 1
previous_version: ""
superseded_by: ""
summary: ""
collected: true
collected_date: 2026-06-03---
# 2026-06-01_2026-05-17_2026-05-16_Camoufox_集成修复记录
> 来源:03_knowledge/20_methods
# 2026-05-17_2026-05-16_Camoufox_集成修复记录
# 2026-05-16_Camoufox_集成修复记录
# Camoufox 集成修复记录
# Camoufox 集成修复记录
## 正确启动方式
## 修复清单
### 1. 操作系统指纹(P1)
**问题**: Camoufox 默认从 `[windows, macos, linux]` 随机,导致手机端扫码有时显示"登录macOS"。
**修复**: 添加 `os='windows'` 固定参数。
**文件**: `cdp_connector.py:_launch_camoufox()`
### 2. 中文乱码(P1)
**问题**: Camoufox/Firefox 不加载系统字体,抖音页面中文显示乱码。
**修复**: 添加 `fonts=['STHeiti', 'Heiti SC', 'PingFang SC', 'Noto Sans CJK SC']` 和 `humanize=1.5`。
**文件**: `cdp_connector.py:_launch_camoufox()`
### 3. 登录检测(P0)
@@ -0,0 +1,129 @@
---
title: 抖音评论自动化双路径技术
tags: [douyin, comment, automation, draft.js, keyboard]
created: 2026-05-29
updated: 2026-05-29
nature: method
collected: true
collected_date: 2026-06-09
---
## 概述
抖音评论系统基于 Draft.js 富文本编辑器,具有严格的输入验证。通过两条独立路径实现评论自动化。
## 双路径架构
| 路径 | 场景 | 入口操作 | 发送操作 |
|:-----|:-----|:---------|:---------|
| **Path A**: 弹窗覆盖层 | 养号流程中 | KeyX 打开评论区 | Enter 发送 |
| **Path B**: 全屏视频页 | 指定视频链接 | scrollIntoView 定位输入框 | Enter 发送 |
## 输入方式
**唯一可靠方式**:`pbcopy` + `Meta+V`(系统剪贴板粘贴)
Draft.js 富文本编辑器特点:
- 只认系统级键盘事件
- CDP(Chrome DevTools Protocol)注入的键盘事件一律忽略
- `page.fill()` / `page.type()` / `elementHandle.type()` 均不可靠
- 必须通过剪贴板 + 系统快捷键触发输入
```python
# 标准输入流程
import subprocess
subprocess.run(["pbcopy"], input=comment_text.encode(), check=True)
await page.keyboard.press("Meta+v") # 或 Cmd+V
await asyncio.sleep(0.3) # 等待粘贴完成
await page.keyboard.press("Enter") # 发送
```
## 状态机
```
closed → panel_open → input_focused → text_entered → sent / verify_code
```
| 状态 | 说明 | 验证方法 |
|------|------|----------|
| closed | 评论区关闭 | 评论面板不可见 |
| panel_open | 评论区已打开 | 评论列表 DOM 可见 |
| input_focused | 输入框已聚焦 | `document.activeElement` 指向编辑器 |
| text_entered | 文字已粘贴 | 输入框内容非空 |
| sent | 发送成功 | 新评论出现在列表顶部 |
| verify_code | 触发验证码 | 验证码弹窗出现,需人工处理 |
## Draft.js 空格刷新技巧
粘贴文本后,Draft.js 可能不立即更新 React 状态。解决方法:
```python
# 粘贴后按空格刷新 React 状态
await page.keyboard.press("Space")
await asyncio.sleep(0.1)
await page.keyboard.press("Backspace") # 删除多余空格
```
## 视频页评论区定位(Path B 专项)
**问题**:评论区输入框可能在视口外(y ~2212px)
**解决**:
1. `scrollIntoView()` 将输入框滚动到可视区域
2. 等待 DraftEditor 懒加载初始化(关键步骤,不能跳过)
3. 点击容器元素触发编辑器激活
4. 然后才能聚焦、粘贴
```python
input_container = await page.query_selector("评论输入框容器选择器")
await input_container.scroll_into_view_if_needed()
await asyncio.sleep(0.5) # 等待 DraftEditor 懒加载
await input_container.click() # 触发编辑器激活
await asyncio.sleep(0.3)
# 现在可以粘贴了
```
## 抖音快捷键参考
| 快捷键 | 功能 |
|--------|------|
| KeyZ | 点赞 |
| KeyX | 打开/关闭评论 |
| KeyG | 关注 |
| KeyP | 连播切换 |
| KeyM | 静音切换 |
| KeyJ | 清屏/弹幕切换 |
## 评论内容生成
### AI 评论(可选)
```python
from matrix_modules.comment.ai_generator import AICommentGenerator
ig = AICommentGenerator()
note_info = await page.evaluate("""
() => {
const t = document.querySelector('.title, h1');
const c = document.querySelector('.content, .desc');
return { title: t?.textContent?.trim(), content: c?.textContent?.trim() };
}
""")
comment_text = ig.generate_with_context(
note_title=note_info.get("title", ""),
note_content=note_info.get("content", ""),
)
```
### 随机评论池
预置评论列表,`random.choice()` 选取。评论频率建议每 3 轮 1 次,避免被检测。
## 错误处理
| 错误 | 原因 | 处理 |
|------|------|------|
| 粘贴无效 | 系统剪贴板被占用 | 重试 pbcopy |
| Enter 无响应 | 输入框未聚焦 | scrollIntoView + click 重新聚焦 |
| 验证码弹窗 | 频繁操作触发 | 截图保存,跳过本轮 |
| 评论发送失败 | 网络或限流 | 等待后重试,计入 consecutive_failures |
@@ -0,0 +1,353 @@
---
title: 抖音登录流程 — 全链路知识与踩坑记录
tags: [douyin, login, sms, verification, iframe, selector, timing]
created: 2026-06-28
updated: 2026-06-28
nature: reference
---
# 抖音登录流程 — 全链路知识与踩坑记录
> 本文包含 2026-06-28 大规模调试中积累的所有登录经验。
> 适用于 `douyin_ops.py` 的 `_ensure_logged_in()` 和 `sms_login()` 方法。
---
## 一、登录状态检测
### 1.1 判断已登录的方法(按可靠性排序)
| 方法 | 选择器 | 可靠性 | 说明 |
|:-----|:--------|:-------|:------|
| `data-e2e` 用户信息区 | `[data-e2e="user-info"]` | ⭐⭐⭐ 最高 | 抖音官方 e2e 标记,有就必已登录 |
| 头像元素 | `[class*="avatar"]` 或 `[data-e2e*="avatar"]` | ⭐⭐ 中 | 部分页面没有这个选择器 |
| 登录按钮不可见 | `button:has-text("登录")` 不可见 | ⭐ 低 | 仅作兜底,不精确 |
### 1.2 判断未登录的方法
- 右上角可见的**「登录」按钮**
- 选择器:`button:has-text("登录"), span:has-text("登录"), div:has-text("登录")`
- 注意:首页推荐流中这个按钮始终存在,要用 `is_visible()` 确认
### 1.3 关键注意事项
- **首次打开抖音首页加载慢**,至少要等 **6 秒**才能做登录检测(我们试过 2s→3s→5s→6s,6s 最稳定)
- 登录弹窗可能在上次会话的 cookie 有效时不弹出,直接显示已登录状态
- 登录成功后**必须重新导航到 `/user/self`**,因为弹窗处理改变了页面 URL
---
## 二、登录弹窗检测
### 2.1 弹窗位置:iframe
抖音的登录弹窗(passport 护照页)是在 **iframe** 中加载的,iframe URL 模式:
- `passport.douyin.com`
- `sso.douyin.com`
- 域名含 `login`
### 2.2 弹窗选择器
| 用途 | 选择器 | 说明 |
|:-----|:--------|:------|
| 弹窗容器 | `.second-verify-panel, .uc-ui-verify_sms-verify` | 覆盖新旧两版 class |
| 二维码区域 | `div[class*=qrcode], img[class*=qrcode], canvas[class*=qrcode]` | 用于二维码检测 |
### 2.3 ⚠️ iframe 查找顺序(重要!)
**必须先查 iframe,再查主页面!**
```
❌ 错误做法(我们踩过的坑):
1. 主页面查 verify_panel → 匹配到遮罩层(`.second-verify-panel`)
2. login_frame = page(主页面)
3. 在主页面搜手机输入框 → 找不到 ❌(因为输入框在 iframe 内)
✅ 正确做法(修复后):
1. 遍历所有 iframe,找 passport/login 域的 verify_panel
2. 找到 → login_frame = iframe
3. 在 iframe 内搜手机输入框 → 能找到 ✅
4. iframe 没找到 → fallback 到主页面
```
### 2.4 弹窗类型判断
弹窗可能有三种状态:
```
弹窗出现
├─ 一键登录 → 有 "一键登录" 按钮,无手机号输入框
├─ 验证码登录 → 有手机号输入框 + 获取验证码按钮
└─ 二维码登录 → 有二维码图片/画布,需切 tab
```
#### 一键登录特征
- 选择器:`button:has-text("一键登录"), span:has-text("一键登录")`
- 点击后等 5 秒
- 可能直接登录成功,也可能跳到验证码页(继续走 sms)
#### 验证码登录特征
- 手机号输入框:`input[placeholder*="手机"]`(匹配 `placeholder="请输入手机号"`)
- 验证码输入框:`input[placeholder*="验证码"]`(匹配 `placeholder="请输入验证码"`)
- 获取验证码按钮:`button:has-text("获取验证码")`
#### 二维码登录特征
- 检测:`div[class*=qrcode], img[class*=qrcode], canvas[class*=qrcode]`
- 处理:找到「验证码登录」tab 并点击切换
---
## 三、SMS 验证码流程
### 3.1 完整流程
```
1. 导航到登录页(如果不在)
2. 查找 passport iframe(最多 5 次尝试,每次间隔 2 秒)
3. 等 3 秒渲染
4. 检测二维码 → 切「验证码登录」tab
5. 找手机号输入框 → 填入手机号(3 次重试,每次间隔 1 秒)
6. 点击「获取验证码」
7. 轮询 SMS API 获取验证码(最多 6 次,共 60 秒超时)
8. 填入验证码
9. 点击「登录」
10. 判断是否登录成功
```
### 3.2 手机号输入框查找(带重试)
```python
phone_sel = "input[placeholder*='手机'], input[type='tel'], input[name='mobile'], input[id*='phone'], input[id*='mobile']"
for retry in range(3):
for sel in [phone_sel, "input:first-of-type"]:
inp = await login_frame.query_selector(sel)
if inp and await inp.is_visible():
# 填入手机号
break
if phone_filled:
break
await asyncio.sleep(1)
```
**注意**:`input:first-of-type` 会匹配到首页搜索框,所以优先用 `placeholder` 选择器。
### 3.3 验证码输入框查找
```python
code_sel = "input[placeholder*='验证码'], input[maxlength='6'], input[maxlength='4'], input[autocomplete='one-time-code']"
```
### 3.4 登录成功判断
| 判断方法 | 说明 |
|:---------|:------|
| URL 变化 | `"passport" in current_url` → 仍在登录页 / `not` → 登录成功 |
| 右上角头像 | `[data-e2e="user-info"]` 存在且可见 → 登录成功(**兜底方案**)|
**两者都用!** 如果 URL 变了但右上角还没渲染出头像,等 3 秒再查一次。
---
## 四、时序控制(重要)
### 4.1 各步骤等待时间
| 步骤 | 等待时间 | 原因 |
|:-----|:---------|:------|
| 首次打开后检测 | **6 秒** | 抖音首页加载慢,特别是第一打开,推荐流+弹窗需要完整渲染 |
| 切换 tab 后 | **3 秒** | tab 切换动画 + 新内容渲染 |
| 填手机号后 | **1 秒** | 防抖 + 输入框状态同步 |
| 点击获取验证码后 | **2 秒** | 等待 SMS 发送 |
| 填入验证码后 | **1 秒** | 防抖 |
| 点击登录后 | **3-5 秒** | 等待页面跳转 |
| 登录成功后重导航 | **3 秒** | 等待 `/user/self` 渲染 |
### 4.2 超时控制
- SMS 验证码轮询:最长 **60 秒**(6 次 × 10 秒)
- 单次 iframe 查找:最长 **10 秒**(5 次 × 2 秒)
- 页面 goto 超时:**20 秒**
---
## 五、选择器备忘(抖音 Web 版)
### 5.1 官方 `data-e2e` 选择器
这些是抖音官方的自动化测试标记,最稳定:
| 用途 | 选择器 |
|:-----|:--------|
| 用户信息区(已登录标志) | `[data-e2e="user-info"]` |
| 用户头像 | `[data-e2e="user-avatar"]`(不一定存在) |
| 搜索输入框 | `[data-e2e="searchbar-input"]` |
| 点赞按钮 | `[data-e2e="video-player-digg"]` |
| 收藏按钮 | `[data-e2e="video-player-collect"]` |
| 分享按钮 | `[data-e2e="video-player-share"]` |
| 评论列表 | `[data-e2e="comment-list"]` |
| 评论点赞 | `[data-e2e="comment-digg"]` |
| 粉丝数 | `[data-e2e="user-info-fans"]`(值可能混合中文) |
| 关注数 | `[data-e2e="user-info-follow"]` |
| 获赞数 | `[data-e2e="user-info-like"]` |
### 5.2 登录弹窗选择器(不定期变化)
这些 class 名由抖音前端动态生成,**每次发布可能变化**:
| 用途 | 6月28日已确认的 class |
|:-----|:----------------------|
| 手机号输入框 | `yKcGN1NT X5L8kib_` |
| 验证码输入框 | `tnpNAdqe _Yqor1vk` |
| 获取验证码按钮 | `gKXDWaPG KX2Mhb6E` |
| 验证码 tab | `zad3EGHJ` |
**因此,不要依赖 class 名,用 placeholder/text 代替。**
### 5.3 推荐做法:文本/属性匹配
```python
# ✅ 稳定
input[placeholder*="手机"] # 匹配 "请输入手机号"
input[placeholder*="验证码"] # 匹配 "请输入验证码"
button:has-text("获取验证码") # 匹配按钮文字
div:has-text("验证码登录") # 匹配 tab 文字
# ❌ 不稳定(class 会变)
.tnpNAdqe # 下个版本可能就改了
.yKcGN1NT # 同上
```
---
## 六、已踩过的坑(必读)
### 坑 1:logger 未定义导致 executor 闪退
**现象**:任务提交后状态一直 "running",实际已挂,slots 空但 active_tasks 有残留。
**根因**:`executor.py` 中使用了 `logger.info()` 但 `logger = logging.getLogger("guardd.executor")` 未定义,NameError 抛出,executor 闪退但任务状态来不及更新。
**修复**:添加 `logger = logging.getLogger("guardd.executor")`。
### 坑 2:主页面遮罩层匹配导致 iframe 检测失败
**现象**:弹窗出现了但手机号输入框找不到。
**根因**:`verify_panel` 选择器 `.second-verify-panel` 在主页面遮罩层上匹配成功,`login_frame` 设为主页面(不是 iframe),在主页面搜手机输入框当然搜不到。
**修复**:先查 iframe,再查主页面。
### 坑 3:登录后页面 URL 变化导致采集到首页数据
**现象**:采集任务显示完成,但 profiles.json 中昵称/粉丝数据为空或为首页标题。
**根因**:登录弹窗处理后,页面 URL 从 `/user/self` 变成了首页或其他地址,但 `goto_profile()` 继续刮取当前页面内容(首页推荐流),拿不到用户数据。
**修复**:登录成功后重新 `goto("/user/self")`。
### 坑 4:2 秒等待不够
**现象**:浏览器打开后鼠标立即动了,此时页面还没渲染完,弹窗还没出现。
**根因**:抖音首页首次加载~4-5 秒,2 秒时 `_has_avatar()` 判断为未登录(因为没渲染完),然后触发弹窗检测也找不到弹窗,最后点了"登录"按钮导致页面刷新。
**修复**:初始等待时间从 2 秒改为 6 秒。
### 坑 5:`_build_cmd` 按字段拼装导致 command_line 出错
**现象**:采集任务一直失败,executor 日志显示 `--blueprints=` 为空。
**根因**:`executor._build_cmd()` 从 `task.get("blueprint")` 读蓝图名,但 CommandBus 传的 `blueprint` 字段是空字符串(没传对),拼出了 `mc run --blueprints= --rounds=1`。
**修复**:优先使用 `task.command_line`(CommandBus 已渲染好的完整命令),`_build_cmd` 按字段拼装仅作 fallback。
### 坑 6:5kecheng 双 guardd 进程
**现象**:远程机器任务不执行,所有请求都返回空。
**根因**:SSH 部署时 `pkill -9 -f guardd` + `nohup python3 guardd.py` 启动了一个新实例,而 launchd 也管理了一个旧实例。两个 guardd 争抢端口 9090,其中一个没有 HTTP 服务。
**修复**:统一用 launchd 管理,不用 nohup。
### 坑 7:登录后 URL 不变(前端 overlay 形式)
**现象**:登录成功了但 `sms_login` 返回 False。
**根因**:有的登录弹窗是前端 overlay(不在 passport 域名下),URL 不变化。`sms_login` 只检查 URL,误判为失败。
**修复**:加 `_has_avatar()` 兜底判断。
### 坑 8:profiles.json 同步走本地文件违反联邦对等
**现象**:7kecheng 采集了数据,但 Dashboard 看不到。
**根因**:`AccountService._get_profile_for_account()` 直接读本机 `profiles.json`,不走 guardd API。
**修复**:改为统一通过 guardd HTTP API (`/accounts/profiles`) 读取,本机远程无差别。
---
## 七、录制文件分析指南
录制的 JSON 文件位于 `agent-local/tools/matrix/recordings/`。
### 7.1 关键字段含义
| 字段 | 含义 |
|:-----|:------|
| `t` | 事件类型(click/hover/key/scroll) |
| `tag` | HTML 标签 |
| `text` | 元素文本内容 |
| `cls` | 元素的 CSS class(关键!) |
| `placeholder` | input 元素的占位符 |
| `type` | input 类型 |
| `e2e` | data-e2e 属性值 |
| `vis` | 元素是否可见 |
| `before_state.selectors` | 录制前页面状态快照 |
| `before_state.text_snippet` | 页面文本片段 |
### 7.2 如何从录制中提取特征码
```bash
# 找手机号输入框
grep -B5 -A3 "请输入手机号" recording_xxx.json
# 找验证码输入框
grep -B5 -A3 "请输入验证码" recording_xxx.json
# 找登录按钮
grep -B5 -A3 "获取验证码" recording_xxx.json
# 查看页面文本快照(了解页面状态)
grep "text_snippet" recording_xxx.json | head -1 | python3 -c "import json,sys;print(json.load(sys.stdin)['text_snippet'][:500])"
```
---
## 八、蓝图登录检测覆盖范围
当前 `_ensure_logged_in()` 被以下操作调用:
| 蓝图操作 | 在流程中调用位置 |
|:---------|:----------------|
| `goto_home` | `execute_op()` 中 goto 首页后 |
| `dy_goto_profile` | `execute_op()` 中 goto 个人主页后 |
| `goto_profile` | 独立方法中被调用 |
| `_ensure_logged_in` 自身 | 无弹窗时递归调用 |
### 设计原则
所有涉及浏览器导航的操作(`goto_home`、`goto_url`、`goto_profile`)**都应该**调用 `_ensure_logged_in()`。目前 `goto_url` 还没有加,待补。
---
## 九、相关文件索引
| 文件 | 内容 |
|:-----|:------|
| `douyin_ops.py` | 抖音原子操作 + `_ensure_logged_in` + `sms_login` |
| `matrix_modules/account/sms_login.py` | SMS 登录模块 |
| `matrix_modules/account/douyin_login.py` | 抖音登录模块 |
| `matrix_modules/account/login_state_machine.py` | 登录状态机 |
| `03_knowledge/99_system/matrix/matrix-known-pitfalls.md` | 已知坑(浏览器/系统) |
| `03_knowledge/99_system/matrix/matrix-sms-verification.md` | SMS 验证码自动接收配置 |
@@ -0,0 +1,161 @@
---
title: Matrix 养号系统已知坑与解决方案
tags: [matrix, pitfalls, troubleshooting, camoufox, chrome, macos]
created: 2026-05-29
updated: 2026-05-29
nature: method
collected: true
collected_date: 2026-06-09
---
## 浏览器引擎
### Camoufox Profile 锁冲突
**现象**:`TargetClosedError`、页面无法操作、养号快速失败
**根因**:两个浏览器实例争用同一 profile 目录,`.parentlock` 锁文件被占用
**场景**:
- 抖音 daemon 模式浏览器保持运行,小红书复用同 identity_dir 启动新实例
- 上次进程异常退出,锁文件未清理
**解决**:
```bash
# 清理残留锁文件
find identities/ -name ".parentlock" -delete
```
**预防**:Phase 间使用 `--no-daemon` 确保浏览器正常关闭(`conn.close()`)
### Camoufox 启动失败
**现象**:浏览器进程无法启动或立即退出
**排查**:
1. 检查 `.user_data/` 目录锁文件(`.parentlock`)
2. 检查 config.yaml 语法
3. 检查 Camoufox 二进制是否可用
### Chrome 148+ Emulation API 失效
**现象**:`Emulation.setDeviceMetricsOverride` 报错或无效果
**根因**:Chrome 148 移除了该 CDP 命令
**解决**:改用 `set_viewport_size` 方法
## macOS 系统
### 窗口激活限制
**现象**:点击操作不响应,元素未被触发
**根因**:macOS 限制后台进程操控前台窗口
**解决**:AppleScript 激活窗口 + 双击策略
```applescript
tell application "System Events" to set frontmost of process "Firefox" to true
```
### macOS 26.4 .so 签名问题
**现象**:`ModuleNotFoundError` 或 `OSError: dlopen` 加载 .so 失败
**根因**:系统升级后 Team ID 不匹配
**解决**:ad-hoc 签名
```bash
codesign --force --sign - /path/to/file.so
# 批量签名
find /path/to/venv -name "*.so" -exec codesign --force --sign - {} \;
```
### Python stdout 缓冲
**现象**:后台运行日志文件为空或延迟写入
**根因**:Python 后台进程 stdout 默认全缓冲(非终端环境)
**解决**:
```bash
PYTHONUNBUFFERED=1 nohup python3 script.py > log.txt 2>&1 &
```
或检查账号级日志文件而非 phase 级日志。
## XHS 特有
### QR 检测墙卡死
**现象**:弹出大弹窗后无响应,养号停滞
**根因**:XHS 非常用登录检测,弹窗无自动超时
**解决**:JS 检测 QR 墙关键词 → 定位"返回首页"按钮 → L 型鼠标路径点击
**区分**:QR 墙 ≠ 未登录。QR 墙是频控触发,多刷会恢复。
### 黑屏
**现象**:瀑布流页面变黑,卡片不可见
**根因**:SPA 状态异常
**解决**:点击底部导航"发现" tab 刷新页面状态
### AI-layout 布局差异
**现象**:搜索栏消失、首卡位置偏移
**根因**:指纹分辨率 + DPR 触发 A/B 测试
**解决**:
1. 调整指纹(screen ≥ 1920x1080, DPR ≥ 1.25)
2. 代码兼容(三重搜索降级、通用 href 匹配)
### 登录检测误判
**现象**:已登录但被判定为未登录
**根因**:先关闭弹窗再检测 → 弹窗已隐藏 → 无法检测登录提示
**解决**:先检测登录状态 → 再 dismiss_login_modal
## 抖音特有
### Draft.js 输入不可靠
**现象**:评论内容无法输入或粘贴后消失
**根因**:Draft.js 只认系统级键盘事件
**解决**:pbcopy + Meta+V,不用 page.fill/type/keyboard.type
### 视频页评论区输入框在视口外
**现象**:评论发送失败,输入框不可交互
**根因**:输入框 y ~2212px,不在可视区域
**解决**:scrollIntoView → 等待 DraftEditor 懒加载 → click 容器触发激活
## 代码层面
### identity_dir 路径构造
**坑**:仅用 identity_name 拼接路径可能错误(如 `douyin_01_camo` ≠ `douyin_01`)
**正确做法**:从 accounts.yaml 读取 identity_dir 字段
### 窗口位置读取
**坑**:config.yaml 中无窗口位置字段
**正确做法**:从 accounts.yaml 读取,运行结束不回写
### argparse daemon 参数
**坑**:`store_true` + `getattr(args, 'daemon', True)` 当参数为 None 时返回 None 而非 True
**正确做法**:`BooleanOptionalAction` + `args.daemon if args.daemon is not None else True`
### page.evaluate() 无限挂起
**现象**:养号运行到某一步突然停止,无错误日志,浏览器进程存活
**根因**:Playwright 的 `page.evaluate()` 在页面状态异常时无限挂起,**无默认超时**
**解决**:所有 `page.evaluate()` 调用加 `asyncio.wait_for()` 超时(8-10s)
**影响范围**:所有涉及 JS 调用的函数(dismiss_login_modal, get_note_cards, click_refresh_button 等)
**注意**:超时会捕获 `asyncio.TimeoutError`,返回 False/None,不会导致进程退出
### 搜索结果页无 FAB 刷新按钮
**现象**:搜索返回首页后 click_refresh_button() 一直在找按钮,日志显示"刷新瀑布流页面..."长时间无进展
**根因**:go_back_to_home() 后可能回到搜索页,搜索页没有右下角 FAB 刷新按钮
**解决**:click_refresh_button() 加 10s 超时 → 找不到按钮安全返回 False → 流程继续
## 诊断方法论
遇到异常时按以下流程处理:
1. **环境因素**:浏览器版本变化?网络波动?进程锁残留?文件被占用?
2. **人为操作**:用户手动关闭浏览器?拖拽窗口?修改配置?
3. **确认代码问题**:记录日志,确认后再改
4. **读文档**:查看项目规划/架构设计,不偏离已有设计
5. **一次改一处**:改完验证再继续
**反面教材**:Chrome 148 升级导致 CDP API 失效,反复修改 cdp_connector.py 6 次把好的改坏了。
@@ -0,0 +1,152 @@
---
title: Matrix 养号系统架构与运维指南
tags: [matrix, nurture, architecture, operations, douyin, xhs]
created: 2026-05-29
updated: 2026-05-29
nature: method
collected: true
collected_date: 2026-06-09
---
## 系统概览
Matrix 养号系统是一套多平台浏览器自动化框架,支持抖音(Douyin)和小红书(XHS)的账号养号、浏览互动、评论等操作。
## 架构
```
统一入口 matrix.py (CLI)
├── 双引擎连接 cdp_connector.py
│ ├── Chrome CDP (UA + viewport + touchEmulation 三合一)
│ └── Camoufox 持久化 (config 注入, 反检测)
├── 养号控制 runner.py (nurture_loop / nurture_xhs_loop)
├── 操作层 ops/
│ ├── douyin/ (browse.py, interact.py)
│ └── xhs/ (browse.py, interact.py, selectors.py)
├── 行为模拟 nurture/behavior.py (13 项可配置参数)
├── 身份管理 identities/{name}/
│ ├── config.yaml (窗口位置、代理等)
│ ├── fingerprint.pkl (浏览器指纹)
│ └── user_data/ (浏览器 profile 数据)
└── 主控脚本 nurture_master.sh (Phase 1 抖音 → Phase 2 小红书)
```
## 核心概念
### 双引擎模式
| 引擎 | 适用场景 | 特点 |
|------|----------|------|
| Chrome CDP | 需要高性能/稳定连接 | UA+viewport+touchEmulation 三合一注入 |
| Camoufox | 需要强反检测 | 持久化 profile, config 注入, 指纹隔离 |
### 统一 CLI
```bash
matrix {account|nurture|config|status} {action} {params}
```
### 账号共享身份
XHS 和抖音可共享同一 identity_dir(如 `douyin_01_camo` 同时用于 `xhs_01`),实现指纹统一。但需注意 **profile 互斥**:同一时刻只能有一个浏览器实例使用同一 profile。
## 养号流程
### 抖音养号轮次
```
首页 → 点击推荐卡片 → 看视频(随机时长) → 点赞/收藏(5中1) → 搜索框搜索 → 返回首页 → 循环
```
### 小红书养号轮次
```
首页(瀑布流)
→ Step 0: click_refresh_button() ← 必执行,预防黑屏/卡死
→ Step 1: scroll_feed_human(1-3屏) ← 拟人化滚动
→ Step 2: click_note_card(max_retries=3) ← 含误触作者→新标签页检测+重试
→ Step 3: browse_note_detail(4-12s) ← 图文/视频自适应
→ Step 4: random_interact() ← 点赞/收藏/关注随机触发
→ Step 5: comment(每3轮1次) ← pbcopy+Meta+V+Enter
→ Step 6: QR墙检测 + 返回首页 ← click_qr_wall_back_button优先
→ Step 7: 搜索发现(每2轮1次) → 返回后刷新 ← 搜索返回后加click_refresh_button
→ 循环
```
所有 `page.evaluate()` 调用均有 `asyncio.wait_for` 超时保护(8-10s),防止卡死。
## 后台运行
```bash
nohup matrix nurture run --account <name> --rounds 10 > /tmp/nurture_<acct>.log 2>&1 &
```
- 独立日志:`/tmp/nurture_<acct>.log`
- 全局日志:`/tmp/nurture.log`
- **Python stdout 缓冲问题**:后台进程日志可能延迟写入,需检查账号级日志
## 浏览器生命周期管理
### daemon 模式(重要)
- `--daemon`:养号结束后浏览器保持运行(便于调试)
- `--no-daemon`:养号结束后正常关闭浏览器释放资源
- **跨 Phase 场景**:Phase 1 抖音结束必须用 `--no-daemon` 关闭浏览器,否则 Phase 2 小红书复用同 profile 会 TargetClosedError
- `argparse.BooleanOptionalAction` 实现 `--daemon` / `--no-daemon` 双向开关
### Profile 冲突排查
**现象**:`TargetClosedError` 或页面无法操作
**根因**:两个浏览器实例争用同一 profile 目录(`.parentlock` 锁文件)
**解决**:确保前一个实例完全关闭(`conn.close()`),必要时清理 `.parentlock`
### conn.close() 实现
调用 `_camoufox_browser.close()` + `_playwright.stop()`,完整释放所有资源。
## Cookie 管理(CookieGuard)
- **模块**:`utils/cookie_manager.py` 中的 `CookieGuard(identity_name)`
- **规则**:共享 identity_dir 不能用裸 `sqlite3 DELETE`,必须用 `delete_platform_cookies_safe()` 自动备份对方平台 cookie
- **备份位置**:`backups/cookies/{identity_dir_name}/`
- **自动集成**:runner.py + matrix.py + nurture_master.sh
## 行为模拟参数(behavior.py)
13 项可配置参数控制养号行为的人性化程度:
- 点击延迟、滑动速度、浏览时长、互动概率、轮间休息等
## 账号管理
- **identities 目录**:每个身份独立文件夹(config.yaml + fingerprint.pkl + user_data/)
- **accounts.yaml**:集中管理所有账号配置(窗口位置、identity_dir 映射等)
- **窗口位置**:从 accounts.yaml 读取,运行结束不回写
- **identity_dir**:必须从 accounts.yaml 读取,不能仅用 identity_name 构造路径
## known-pitfalls
### Camoufox
- 启动失败 → 清理 `.user_data` 锁文件(`.parentlock`)
- 指纹分辨率影响 XHS 布局版本(见 AI-layout 兼容知识卡)
### Chrome
- Chrome 148+ → `Emulation.setDeviceMetricsOverride` 失效,改用 `set_viewport_size`
- macOS 窗口激活需用 AppleScript(系统限制)
### macOS 26.4
- 系统升级后 .so 文件因 Team ID 不匹配无法加载 → ad-hoc 重新签名
- 脚本:`codesign --force --sign - <file>`
### Python 后台进程
- stdout 默认缓冲导致日志延迟/丢失 → 检查账号级日志文件
- `PYTHONUNBUFFERED=1` 可强制无缓冲
## 全模拟运营架构
```
Layer 4: 管理层 排期/调度/KPI(规划中)
Layer 3: 流水线层 内容/电商/直播流水线
Layer 2: 操作层 原子操作(发布/评论/互动)
Layer 1: 数据层 每账号工作区(素材/脚本/发布记录/cookie备份)
```
@@ -0,0 +1,62 @@
---
id: KB-20260510-001
title: "Matrix 养号系统 SMS 验证码自动接收"
type: system
status: active
nature: reference
domain: system
subdomain: [matrix, 养号, 自动化]
tags: [SMS, 验证码, 抖音养号, 自动化登录]
confidence: 0.9
source: "07_matrix 养号系统代码审查"
date_created: 2026-05-10
date_modified: 2026-05-10
version: 1
summary: "SMS 验证码自动接收模块,通过 wx.tyhtak.com API 轮询获取短信验证码,替代手动输入。配置已从源码提取到 config/sms.yaml。"
collected: true
collected_date: 2026-05-16---
# SMS 验证码自动接收模块
## 架构
```
base.py — SMSHandler(抽象基类)
├── ManualSMSHandler — 手动输入(信号文件交互)
└── ApiSMSHandler — API 自动轮询(默认)
API: https://wx.tyhtak.com/api/biz/msg/messages
配置: config/sms.yaml
```
## 配置
位置:`config_template/sms.yaml`(模板)→ `scripts/config/sms.yaml`(实际)
每台机器独立,已加入 .gitignore。
```yaml
sms:
api_key: "gtmsg2026"
phone: "15370103682"
base_url: "https://wx.tyhtak.com/api/biz/msg/messages"
poll_interval: 3
timeout: 120
```
## 工作流程
```
养号脚本需要验证码
↓
ApiSMSHandler.wait() 启动轮询
↓ 每3秒拉一次API
检测到新短信 → 提取验证码(4-6位数字)
↓
返回给登录流程自动填入
```
## 注意事项
- API Key 和手机号已从源码提取到配置文件,不再硬编码
- 超时120秒,可配置
- API 不可用时会打印警告,不中断流程
@@ -0,0 +1,476 @@
# Matrix v6.0 产品规划 —— 全平台社交账号智能运营系统
> 版本: v6.0-draft | 日期: 2026-06-12
> 基于: AgentOS 联邦多机协同 + PlatformOps 架构
> 目标: 从"养号工具"升级为"全平台账号智能运营中台"
---
## 一、现状评估
### 当前能力
| 模块 | 状态 | 说明 |
|------|------|------|
| 抖音养号 | ✅ 生产可用 | 浏览/点赞/收藏/评论/搜索,93% 成功率 |
| 小红书养号 | ✅ 生产可用 | 浏览/点赞/收藏/搜索/关注,100% 成功率 |
| 多账号并行 | ✅ 生产可用 | asyncio.gather + 窗口分散 |
| 蓝图编排 | ✅ 生产可用 | JSON 定义操作序列 |
| mc CLI | ✅ 生产可用 | 统一命令入口 |
| 联邦状态上报 | ✅ 运行中 | guardd → cross_machine/data/matrix/ |
| Peekaboo 截图 | ✅ 可用 | MCP 截图调试 |
### 缺失能力
| 缺失 | 关键程度 | 说明 |
|------|---------|------|
| 短信验证码自动接收 | P0 | 登录/恢复账号必备 |
| 评论语料库 | P0 | 智能生成评论内容 |
| 定向评论任务 | P0 | 给指定 URL 视频发评论 |
| 评论互动(追评/回复) | P1 | 与作者或其他用户互动 |
| 定时错开发布 | P1 | 时间策略避免风控 |
| 账号健康度 | P1 | 限流/风控预警 |
| 数据看板 | P1 | 账号运营数据可视化 |
| 知乎/B站/微博支持 | P2 | 扩展平台 |
---
## 二、整体架构设计
```
┌─────────────────────────────────────────────────────────────────────┐
│ Matrix 运营中台 v6.0 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 养号引擎 │ │ 评论系统 │ │ 任务中心 │ │ 联邦协同层 │ │
│ │ (现有) │ │ (新增) │ │ (新增) │ │ (guardd) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │ │ │
│ ┌────▼─────────────▼─────────────▼──────────────────▼──────────┐ │
│ │ PlatformOps 操作层 │ │
│ │ DouyinOps │ XhsOps │ ZhihuOps(future) │ ... │ │
│ └───────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────▼──────────────────────────────────┐ │
│ │ Camoufox 浏览器引擎 + CDP │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ SMS 验证 │ │ 语料库 │ │ 知识库 │ │ L2 记忆(facts) │ │
│ │ 模块 │ │ 引擎 │ │ 策略库 │ │ 账号健康数据 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 三、模块详细设计
### 3.1 短信验证码模块(P0)
**目标**: 自动接收短信验证码,完成账号登录/恢复/验证
**现有基础**: `03_knowledge/40_references/matrix-sms-verification.md` 已有 API 方案
```
养号脚本需要验证码
↓
ApiSMSHandler.wait() 启动轮询
↓ 每3秒拉一次API (wx.tyhtak.com / 其他供应商)
检测到新短信 → 提取验证码(4-6位数字)
↓
返回给登录流程自动填入
↓
登录完成后标记验证码已使用
```
**设计要点**:
- 抽象 `SMSHandler` 基类(已存在 `matrix_modules/account/sms/base.py`)
- 支持多供应商切换(tyhtak / 其他API / 自建短信猫)
- 手机号关联账号配置(`accounts.override.yaml` 中的 phone 字段)
- 超时 120 秒,失败告警
- 验证码消耗后标记,避免重复使用
**配置示例**:
```yaml
# config/sms.yaml
sms:
provider: tyhtak
api_key: "gtmsg2026"
poll_interval: 3
timeout: 120
```
### 3.2 评论语料库系统(P0)
**目标**: 生成真实、多样、上下文相关的评论内容
**现有基础**: `scripts/config/comment_corpus.yaml` 已有基础语料
**架构**:
```
┌─────────────────────────────────────────────────────────────┐
│ 评论语料库引擎 │
│ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
│ │ 静态语料池 │ │ AI 生成器 │ │ 上下文追踪器 │ │
│ │ (预设模板) │ │ (本地LLM) │ │ (已发评论/已评视频) │ │
│ └─────┬──────┘ └─────┬──────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ └───────┬───────┴────────┬───────────┘ │
│ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ │
│ │ 选择策略 │ │ 安全过滤 │ │
│ │ (随机/轮询)│ │ (敏感词/重复)│ │
│ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
**语料分层**:
| 层级 | 来源 | 用途 | 特点 |
|------|------|------|------|
| L1 静态模板 | 人工预设 | 日常普通评论 | "学到了"、"真不错"、"支持" |
| L2 关键词模板 | 按视频类型匹配 | 相关评论 | 科技→"技术流"、美食→"看着就好吃" |
| L3 AI 生成 | 本地 oMLX + Qwen | 精准评论 | 根据视频标题/描述生成 |
| L4 上下文延续 | 追评/回复 | 互动 | 基于已有评论内容 |
**存储**:
```yaml
# matrix_modules/comment/corpus.yaml
corpus:
douyin:
- text: "学到了"
tags: [通用, 知识]
weight: 0.3
- text: "这个厉害了"
tags: [通用, 技术]
weight: 0.2
xiaohongshu:
- text: "好实用呀"
tags: [通用, 教程]
weight: 0.3
```
### 3.3 定向评论任务系统(P0)
**目标**: 接收指定 URL,让所有账号去该视频下评论,时间错开、内容不同
**工作流程**:
```
用户/智能体下发任务:
"给这些视频发评论: [url1, url2, url3]"
↓
任务中心解析 →
提取视频ID → 分析视频内容/标题 → 确定评论策略
↓
生成评论计划 →
每个账号 → 分配不同的评论内容 → 设置时间偏移
↓
任务写入 cross_machine/tasks/ →
持久化到 cross_machine/data/matrix/comment_tasks/
↓
各机器 guardd 检测到新任务 →
按计划执行评论(时间错开 5-30 分钟)
↓
完成后上报结果 →
cross_machine/data/matrix/comment_results/{uid}.json
```
**任务格式**:
```json
{
"task_id": "comment_20260612_001",
"url": "https://www.douyin.com/video/xxx",
"platform": "douyin",
"type": "batch_comment",
"strategy": "diverse",
"accounts": ["douyin_01", "douyin_02"],
"time_window": {"start": "10:00", "end": "12:00"},
"min_interval": 300,
"assigned": {
"douyin_01": {"text": "讲得真好,学到了", "time": "10:05"},
"douyin_02": {"text": "这个角度很新颖", "time": "10:12"}
},
"context": {"video_title": "xxx", "author": "yyy"},
"status": "pending"
}
```
### 3.4 评论互动系统(P1)
**目标**: 追评、回复作者回复、与其他用户互动
**场景**:
1. 发布评论后 → 如果作者回复了 → 自动追评回复
2. 其他用户回复了我们的评论 → 选择性回复
**技术方案**:
- 评论发布后记录 `{comment_id, video_id, account}`
- 定时检查是否有新回复(通过评论区 DOM 或 API)
- 检测到回复 → 加载回复内容 → 确定回复策略 → 回复
**频率控制**:
- 每天最多回复 5 条/账号
- 回复间隔至少 30 分钟
- 回复内容需与原文相关
### 3.5 账号健康度与反检测(贯穿全局)
**风险维度**:
| 风险 | 检测方式 | 应对措施 |
|------|---------|---------|
| 操作频率过高 | 各操作 rate limit | 按分钟/小时/天三级限速 |
| 行为模式单一 | 蓝图随机化 + wait_jitter | 操作间隔随机化 |
| 内容重复 | 语料库去重 | 同一内容不重复使用 |
| 登录地点异常 | IP 检测 | 静态住宅 IP / 代理 |
| 设备指纹一致 | BrowserForge | 每账号独立指纹 |
| 评论时间集中 | 时间策略 | 错开 5-30 分钟 |
| 评论内容异常 | AI 质量检测 | 敏感词过滤 + 长度变化 |
**账号健康度评分**:
```python
class AccountHealth:
def score(self, account_id) -> dict:
return {
"overall": 85, # 0-100
"risk_level": "low", # low/medium/high
"metrics": {
"daily_ops": 45, # 今日操作数
"op_freq": 0.8, # 操作频率 vs 安全阈值
"comment_rate": 0.3, # 评论占比
"repeat_rate": 0.05, # 内容重复率
"fail_rate": 0.1, # 操作失败率
"days_active": 15, # 连续活跃天数
},
"alerts": [] # 预警列表
}
```
### 3.6 联邦协同(增强现有 guardd)
**新增 State 类型**:
```yaml
# cross_machine/data/matrix/{uid}.json 新增字段
{
"plugin": "matrix",
"version": "6.0.0",
"machine_uid": "...",
"hostname": "...",
"timestamp": "...",
"data": {
"注册表账号": 4,
"本机": 4,
"本机已启用": 4,
"本机已登录": 4,
"各机器": {...},
"今日评论": 12, # 新增
"今日互动": 3, # 新增
"账号健康度": { # 新增
"douyin_test": {"score": 85, "alerts": []}
},
"待处理任务": 2, # 新增
"任务队列": [ # 新增
{"id": "comment_xxx", "type": "batch_comment", "status": "pending"}
]
}
}
```
### 3.7 定时策略引擎
**核心规则**:
- 养号时间分布:分散在 8:00-22:00,每次 15-30 分钟
- 评论间隔:同一视频不同账号间隔 5-30 分钟
- 跨视频间隔:同一账号不同视频间隔 10-60 分钟
- 账号休息日:每周 1-2 天静默
**策略配置**:
```yaml
strategy:
daily:
time_windows: ["09:00-09:30", "14:00-14:30", "20:00-20:30"]
round_per_window: 2
interval_between_rounds: 45-90
comment:
min_interval: 300 # 同账号评论最短间隔(秒)
max_daily: 10 # 单账号每天最多评论
batch_spread: 1800 # 同一批任务最大散布时间(秒)
rest_day:
probability: 0.15 # 每天有 15% 概率静默
```
### 3.8 监控看板(P1)
在现有 `10_dashboard` 基础上扩展 Matrix 插件:
```python
class MatrixPlugin(DashboardPlugin):
# 新增视图
def get_sub_views(self):
return [
{"key": "overview", "label": "总览"},
{"key": "accounts", "label": "账号管理"},
{"key": "daily_ops", "label": "今日运营"},
{"key": "comments", "label": "评论任务"},
{"key": "health", "label": "健康度"},
{"key": "blueprints", "label": "蓝图管理"},
]
```
---
## 四、实施路线图
```
Phase 1(2-3天)—— 基础能力补齐
├── SMS 验证码模块 → 对接现有 API
├── 静态语料库建立 → 从现有 comment_corpus.yaml 扩展
└── 评论任务原型 → mc comment 子命令
Phase 2(3-5天)—— 评论系统
├── 定向评论任务(指定 URL → 多账号评论)
├── 时间策略引擎(错开时间)
├── AI 生成评论(对接本地 oMLX)
└── 跨机器任务分发(cross_machine/tasks/)
Phase 3(3-5天)—— 互动系统
├── 追评(作者回复后自动回复)
├── 评论上下文追踪
├── 回复其他用户评论
└── 互动频率控制
Phase 4(持续)—— 平台扩展
├── 知乎平台接入(ZhihuOps)
├── B站平台接入(BilibiliOps)
├── 微博平台接入(WeiboOps)
└── 通用平台框架
Phase 5(持续)—— 智能优化
├── 行为策略自动调优(基于健康度反馈)
├── 评论质量评估
├── 账号休眠/恢复策略
└── 联邦协同决策(多机器共享策略)
```
---
## 五、现有项目参考
### 5.1 同类项目分析
| 项目 | 优势 | 不足 | 可借鉴 |
|------|------|------|--------|
| **MediaCrawler** | 多平台采集,代码开源 | 无养号运营,无评论系统 | 平台适配模式 |
| **水军SaaS平台** | 评论任务分发成熟 | 云端不安全,封号风险高 | 任务队列模型 |
| **OpenClaw** | 浏览器自动化,反检测 | 没有养号业务逻辑 | 反检测指纹技术 |
| **TikOmni** | TikTok 自动化完整 | 付费,不开源 | 商业产品思路 |
### 5.2 AgentOS 独特优势
| 能力 | 其他项目 | AgentOS + Matrix |
|------|---------|------------------|
| 部署方式 | 单机/云端 | **多机联邦,本机安全** |
| 账号安全 | 明文上传 | **配置在 agent-local,不同步** |
| 智能决策 | 固定规则 | **智能体+知识库+记忆系统** |
| 数据积累 | 无/外部 | **L2 facts.db + 知识库沉淀** |
| 跨机协同 | 无 | **guardd + cross_machine** |
| 平台扩展 | 需重写 | **PlatformOps 接口,新增 ops 文件** |
---
## 六、项目目录结构规划
```
05_tools/07_matrix/
├── mc ← CLI 入口
├── install.sh ← 新机部署
├── README.md / TOOL.md / MODULE.md
├── blueprints/ ← 任务蓝图
│
├── scripts/
│ ├── mc/ ← CLI 引擎
│ │ ├── cli.py
│ │ ├── engine.py ← 养号引擎
│ │ ├── runlog.py ← 运行日志
│ │ └── corpus.py ← 语料库(现有)
│ │
│ ├── ops/ ← 平台操作层(现有)
│ │ ├── _base.py
│ │ ├── douyin_ops.py
│ │ └── xhs_ops.py
│ │
│ ├── matrix_modules/ ← 功能模块
│ │ ├── comment/ ← 评论系统(新增)
│ │ │ ├── __init__.py
│ │ │ ├── corpus.py ← 语料库管理
│ │ │ ├── generator.py ← AI 评论生成
│ │ │ ├── task.py ← 评论任务队列
│ │ │ ├── scheduler.py ← 时间策略
│ │ │ └── tracker.py ← 评论上下文追踪
│ │ │
│ │ ├── sms/ ← 短信验证(新增)
│ │ │ ├── base.py ← 抽象基类(已有)
│ │ │ ├── tyhtak.py ← tyhtak API 实现
│ │ │ └── config.py ← 供应商配置
│ │ │
│ │ └── health/ ← 健康监控(新增)
│ │ ├── scorer.py ← 健康度评分
│ │ ├── alerts.py ← 预警
│ │ └── reporter.py ← 状态上报
│ │
│ └── config/ ← 配置(新增)
│ ├── comment_corpus.yaml ← 语料库
│ └── sms.yaml ← SMS配置
│
└── docs/
└── MATRIX_V5_GUIDE.md ← 现有文档
```
---
## 七、关键设计原则
### 7.1 反检测第一
所有操作设计优先考虑反检测:
1. **频率控制**: 每个操作独立 rate limit(like/comment/search/follow)
2. **时间随机化**: 所有等待时间加 jitter ±30%
3. **内容去重**: 同一内容不在 7 天内重复
4. **IP 分散**: 多账号用不同代理出口
5. **行为多样性**: 每次养号随机选择蓝图组合
6. **账号休息**: 模拟真人作息(夜间不操作)
### 7.2 联邦优先
1. 每台机器独立执行本地任务
2. 任务通过 cross_machine 文件异步传递
3. 机器间不直接通信(避免网络暴露)
4. 任务结果通过 Git 被动同步
5. Dashboard 统一展示联邦状态
### 7.3 渐进增强
1. 现有功能不动(养号引擎稳定)
2. 新功能先以独立模块开发
3. 通过 mc CLI 子命令暴露新功能
4. 每完成一个模块就做回归测试
5. 保持向后兼容
---
## 八、风险与缓解
| 风险 | 影响 | 缓解 |
|------|------|------|
| 平台接口变更 | 选择器失效 | 多级选择器 fallback + 定期验证 |
| 封号 | 账号损失 | 健康度预警 + 操作上限 + 新号先养 |
| 浏览器检测 | 所有账号受影响 | Camoufox 指纹隔离 + BrowserForge |
| 短信API不可用 | 无法登录 | 多供应商切换 + 手动兜底 |
| 代理不稳定 | 操作失败 | 自动切换代理 + 超时重试 |
| 跨机冲突 | 同一任务重复执行 | 任务状态机 + TTL |
@@ -0,0 +1,230 @@
---
title: 小红书养号页面交互技术
tags: [xhs, browse, interaction, qr-wall, refresh, black-screen, ai-layout, tab-detection, timeout]
created: 2026-05-29
updated: 2026-05-29
nature: method
collected: true
collected_date: 2026-06-09
---
## 核心选择器
### 首页/瀑布流
- 瀑布流容器:`#app .layout.limit`
- 笔记卡片:`section.note-item`(DOM 分析确认)
- 卡片封面链接:`a.cover.mask.ld`
- 卡片图片:`a.cover.mask.ld img`
### 笔记详情页
- 详情容器:`.note-detail, [class*=note-detail], .note-page`
- 标题:`.title, h1, [class*=note-title]`
- 内容:`.content, [class*=content], .desc`
### 弹窗/遮罩
- 登录弹窗:`[class*=login], [class*=modal], [role=dialog], .reds-alert`
- 通用遮罩:`[class*=mask], [class*=overlay], [class*=modal]`
### 锚点验证
| 锚点类型 | 选择器 |
|----------|--------|
| 首页 | `section.note-item` |
| 笔记详情 | `.note-detail, [class*=note-detail], .title, h1` |
| 视频页 | `video` |
| 评论打开 | `.comment-section, [class*=comment-section]` |
| 搜索页 | `.search-result, [class*=search-result]` |
## JS 智能查找函数
### find_refresh_button_js()
三策略查找瀑布流页面右下角 FAB 刷新按钮:
1. **选择器匹配**:`button[class*="refresh"]`、`[class*="fab"]`、`[class*="floating"]` 等
2. **SVG 扫描**:查找包含旋转箭头路径的 SVG 元素(刷新图标 = arc + rotate 特征)
3. **fixed 定位过滤**:右下角区域(x > viewport 60%, y > viewport 60%)的可点击元素
返回 `{found, x, y, w, h, text, method}`
### find_qr_wall_back_button_js()
检测 QR 检测墙并定位返回首页按钮:
1. **QR 墙关键词检测**:body 文本包含"扫码"/"二维码"/"非常用登录"/"验证"等
2. **返回按钮匹配**:优先级排序 — "返回首页"(10) > "返回"(20) > "首页"(30) > "确定/我知道了"(50)
3. **候选过滤**:宽度/高度 > 5px(排除不可见元素),文本长度 < 15 字符
返回 `{found, x, y, w, h, text, priority}`
### dismiss_login_modal_js()
隐藏登录弹窗和固定定位遮罩层。设置 `display:none` 而非移除 DOM。
### get_note_cards_js()
获取瀑布流所有笔记卡片的 index、href、title、rect 信息。
## 鼠标模拟技术
### L 型鼠标路径(_l_shaped_click)
模拟真人鼠标移动轨迹:
```
起点(随机偏移) → 水平移动(5-10步) → 垂直移动(3-8步) → 微抖动(±2px) → 点击
```
- 每步延迟 10-30ms(随机),总耗时 100-400ms
- 起点偏移范围:x ±150px, y -100~+50px
- 限制在视口内(防止鼠标移出窗口)
- 微抖动模拟手指不稳
### 双击策略(聚焦→执行)
macOS 窗口激活限制下,点击需要先聚焦再执行:
1. 第一次点击 → 聚焦窗口(可能不触发元素事件)
2. 第二次点击 → 实际执行操作
## 误触作者主页(新标签页)检测
点击笔记卡片时可能点到作者头像/名称,导致新标签页打开作者主页。
### 检测方法(确定性锚点)
```
点击前: tabs_before = len(context.pages)
执行点击(Playwright click)
点击后: if len(context.pages) > tabs_before → 误触作者
```
- 新 tab URL 即为作者主页地址
- 关闭新 tab,回到原 tab,重新选卡片重试(最多 3 次)
- 3 次全部误触才返回 None(本轮失败,不导致全局退出)
### 代码位置
`browse.click_note_card(page, max_retries=3)`:
- 使用 `page.context.pages` 获取所有标签页
- 点击后比对 tab 数量变化
- 比截图 + AI 分析更可靠,是确定性锚点
## page.evaluate() 超时保护
### 问题
Playwright 的 `page.evaluate()` 在页面状态异常时会**无限挂起**,无默认超时。卡死时不会抛出异常,整个 asyncio 循环停滞。
### 受影响的函数
所有涉及 `page.evaluate()` 的操作都可能卡死:
- `dismiss_login_modal()` — 登录弹窗关闭
- `get_note_cards()` — 获取卡片列表
- `click_note_card()` — 内部调用 get_note_cards
- `browse_note_detail()` — 视频检测
- `click_refresh_button()` — 刷新按钮查找
- `click_qr_wall_back_button()` — QR墙按钮查找
### 修复
全部加 `asyncio.wait_for()` 超时保护:
```python
result = await asyncio.wait_for(
page.evaluate(some_js_function()),
timeout=10 # 秒
)
```
### 超时时间表
| 函数 | 超时 | 说明 |
|------|------|------|
| click_refresh_button | 10s | JS 三策略扫描,最复杂 |
| click_qr_wall_back_button | 10s | QR 墙检测 + 按钮定位 |
| get_note_cards | 10s | DOM 卡片遍历 |
| dismiss_login_modal | 8s | 简单 DOM 操作 |
| browse_note_detail (视频检测) | 8s | 简单 DOM 查询 |
### 搜索结果页刷新按钮
搜索页(URL含 `/search`)没有右下角 FAB 刷新按钮,`click_refresh_button()` 会在 10s 超时后安全返回 False,不会卡死。
## 常见问题与恢复策略
### 黑屏恢复
**现象**:瀑布流页面变黑,卡片不可见
**根因**:SPA 状态异常,非窗口移出屏幕
**恢复链**:
```
1. scroll_feed 回到顶部 → 检测卡片
2. click_bottom_nav_tab("发现") → 刷新页面状态
3. goto_home() → 直接导航兜底
```
### QR 检测墙恢复
**现象**:中央弹出大弹窗,提示"扫码"/"非常用登录"
**根因**:XHS 非常用登录检测触发,非登录状态失效
**恢复链**:
```
1. click_qr_wall_back_button() → 点弹窗上的"返回首页"按钮
2. click_bottom_nav_tab("发现") → 底部导航恢复
3. go_back_to_home() → 直接导航兜底
```
**重要区别**:QR 墙 ≠ 未登录。QR 墙是频控触发,多刷会恢复。未登录二维码弹窗是另一种情况。
### 每轮必刷新策略(Step 0)
为彻底防止瀑布流黑屏/卡死,小红书养号每轮循环开头**强制**点击右下角刷新按钮:
```
每轮循环:
Step 0: click_refresh_button() ← 必执行(模拟鼠标点 FAB)
Step 1: scroll_feed_human()
Step 2: click_note_card()
...
搜索返回后: click_refresh_button() × 1 ← 回到首页后也刷新
```
- 找不到刷新按钮时不中断,仅跳过并记日志(搜索结果页无 FAB)
- `click_refresh_button()` 自带 10s 超时保护,不会卡死
- 原锚点失败时的刷新逻辑保留作为兜底
### 首页锚点验证失败
```
1. click_refresh_button() → 点右下角物理刷新按钮(优先,模拟真人)
2. page.reload() → API 刷新(fallback)
3. init_anti_detection() → 重新初始化反检测
```
### AI-layout 兼容(A/B 测试)
**触发条件**:指纹分辨率 + DPR(低分屏 1920x1080 DPR=1.0 易触发)
**差异**:无顶部搜索栏、无 header-container、首卡 y 偏移 272px vs 144px
**兼容措施**:
- `search()`:标准输入框 → ALT 输入框 → URL fallback(三重降级)
- `click_search_result()`:标准选择器 → 通用 href 匹配
- 启动时自动检测布局版本(standard/ai-layout/unknown)
**指纹建议**:screen ≥ 1920x1080, DPR ≥ 1.25
## 登录检测规则
1. **顺序**:页面级登录检测 → dismiss_login_modal(先检测再关闭,否则误判)
2. **等待**:首页加载后等 5 秒(给弹窗渲染时间)
3. **关键词**:"登录之后更精彩" / "扫码登录" / "手机号登录" / "请登录" / "立即登录"
4. **尺寸区分**:`getBoundingClientRect(width>200, height>100)` 区分导航栏 vs 模态弹窗
## 操作前状态检查清单
进入每轮养号前:
1. 首页锚点验证(`section.note-item` 存在)
2. 登录弹窗关闭
3. 反检测初始化
4. Cookie/Session 检查(非必需,未登录也可浏览)
@@ -0,0 +1,64 @@
---
title: 指纹分辨率触发小红书 AI-layout 版本
tags: [xhs, fingerprint, ai-layout, resolution]
created: 2026-05-27
collected: true
collected_date: 2026-06-09
---
## 现象
xhs_03(douyin_camo01)的首页布局和其他两个账号不同:
- 没有顶部搜索栏(`input.search-input` 不存在)
- 没有顶部 `header-container`
- 首卡 y 偏移从 144px → 272px
- 主容器 class 是 `ai-layout-active`
- 但 `section.note-item` 选择器兼容,卡片正常
## 根因分析
通过修改指纹确认:**屏幕分辨率 + devicePixelRatio 触发了 XHS 的 A/B 测试版本分配。**
### 三账号指纹对比
| 特征 | xhs_01 | xhs_02 | xhs_03(改前) | xhs_03(改后) |
|------|--------|--------|----------------|----------------|
| Screen | 2752x1152 | 2176x1224 | **1920x1080** | 2752x1152 |
| DPR | 1.25 | 1.76 | **1.0** | 1.25 |
| 字体数 | 20 | 20 | **16** | 20(未改) |
| 布局 | standard | standard | **ai-layout** | **standard** ✅ |
### 验证方法
1. 读取 xhs_03 的 `fingerprint.pkl` → `screen.width=1920, screen.height=1080, devicePixelRatio=1.0`
2. 修改为 xhs_01 的规格(2752x1152, DPR=1.25)
3. 清除 XHS cookies + localStorage(清楚 A/B 分配缓存)
4. 重新加载 → **布局变为标准版**(搜索框出现、top bar 恢复、首卡 y=144)
## 结论
**XHS 会基于指纹的 screen 分辨率 + DPR 分配不同的 UI 版本。**
- 低分屏(1920x1080, DPR=1.0)→ `ai-layout-active`(无搜索栏、无顶栏)
- 高分屏(2752x1152, DPR=1.25+)→ 标准版
## 新建账号的指纹建议
为避免触发 AI layout 导致代码兼容问题:
```
fingerprint screen: 推荐 ≥ 1920x1080, DPR ≥ 1.25
具体建议值:
screen.width: 1920 或更高(如 2752)
screen.height: 1080 或更高
devicePixelRatio: 1.25 ~ 2.0
availWidth: 匹配 screen.width
availHeight: screen.height - 48
```
## 代码兼容(已做)
即使遇到 AI layout,代码也做了兼容处理:
- `browse.py search()`: 三重降级(标准 → ALT → URL fallback)
- `browse.py click_search_result()`: 标准选择器 → href 通用匹配
- `runner.py`: 启动时自动检测布局版本(standard/ai-layout)