feat(monitor): 抖音 Web 接口客户端 —— 绕开爬虫子进程,直接发 HTTP
移植自 mac-agent-os 的 mediacrawler_adapter:不起子进程、不开页面,用浏览器里那份
登录态直接调抖音 Web 接口。产物键名照抄 store/douyin,所以 ingest 那条链路一个字不用改。
**目前能用的(真环境实测,非推断)**:
profile/other : 200, 7075 字节 —— 博主主页指标(粉丝/获赞/作品数/昵称)
aweme/detail : 200, 45425 字节 —— 单条作品详情(含点赞/评论/收藏/分享)
**目前不能用的:作品列表 `aweme/post`。** 两个互相独立的原因:
1. 这个接口被抖音单独升级成了真校验:不带 x-tt-argus 回 403「Uifid Not Found」,
带上 dummy 值回 200 + **空 body**。也就是说「头在不在」骗得过,「真校验」过不了。
同一套头打 profile/other 和 aweme/detail 都是通的 —— 抖音是挑着接口加保护的,
挑中的恰好是「批量拉作品列表」这个最敏感的动作。
2. 改走页面截获也不行:CDP 浏览器打开博主主页会落到「验证码中间页」(当天大量探测的
代价,过几小时要重测)。
所以现在的边界是:**已知作品的指标刷新能做,自动发现新作品做不了**。
**排查中控住变量后得到的两条事实**(都写进注释了):
· `Accept` / `Accept-Language` / `Referer` 才是主页接口能返回真数据的原因 —— 只有
UA+client hints+Cookie 时是 200 但仅 121 字节的空壳,补上这三个头变 7074 字节。
(我先前猜的 sec-ch-ua 不是关键。)
· 因此 UA 与 client hints 必须**成套地取自同一个浏览器**,所以 BrowserIdentity 一次
从 CDP 取齐 cookie + UA + hints,而不是各自写死。
「200 + 空 body 必须当场报错」也是刻意写死的:放过去它会在下游变成「这个博主没作品」,
把一次失败伪装成一条正常结果 —— 爬虫那条路正是这么栽的,还被翻译成「账号被封」。
测试 +11:cookie 解析、请求头成套性(含 uifid 缺失/回退)、产物键名与 store 对齐、
以及 _get 的三条失败路径(空 body / 403 带网关原话 / 正常返回)。
This commit is contained in:
@@ -0,0 +1,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 次把好的改坏了。
|
||||
Reference in New Issue
Block a user