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,142 @@
#!/usr/bin/env python3
"""
AgentOS 角色检查工具
所有自动化脚本在执行前调用此工具检查本机角色是否符合要求。
"""
import os
import sys
import yaml
from pathlib import Path
HOST_ID_PATH = Path.home() / "workbuddy-agent-os" / "agent-local" / "identity" / "HOST_ID.md"
LOCAL_IDENTITY_DIR = Path.home() / "workbuddy-agent-os" / "agent-local" / "identity"
def read_host_id() -> dict:
"""读取本机 HOST_ID,返回角色和能力字典"""
if not HOST_ID_PATH.exists():
print(f"[role_check] ⚠️ HOST_ID.md 不存在: {HOST_ID_PATH}")
print("[role_check] 请运行 agentos init --localize 生成")
return {"role": "unknown", "capabilities": {}}
try:
with open(HOST_ID_PATH, "r", encoding="utf-8") as f:
content = f.read()
# 从 yaml code block 中提取能力配置
yaml_block = None
in_block = False
lines = []
for line in content.split("\n"):
if line.strip().startswith("```yaml"):
in_block = True
continue
elif line.strip().startswith("```"):
if in_block:
yaml_block = "\n".join(lines)
break
in_block = False
continue
if in_block:
lines.append(line)
# 提取角色
role = "unknown"
for line in content.split("\n"):
if "角色:" in line and ":" in line:
role_part = line.split("角色:")[-1].strip()
if role_part:
role = role_part
# 解析能力
capabilities = {}
if yaml_block:
try:
parsed = yaml.safe_load(yaml_block)
if isinstance(parsed, dict):
capabilities = parsed
except Exception:
pass
return {
"role": role,
"capabilities": capabilities,
}
except Exception as e:
print(f"[role_check] ❌ 读取 HOST_ID.md 失败: {e}")
return {"role": "unknown", "capabilities": {}}
def check_role(allowed_roles: list) -> bool:
"""
检查本机角色是否在允许列表中。
同时运行集群冲突检测(如果任务涉及 master 权限)。
如果角色不匹配,打印提示并返回 False。
"""
info = read_host_id()
role = info["role"]
if role in allowed_roles:
# 角色通过后,额外检测集群冲突(仅限 master 级别任务)
if "master" in allowed_roles:
try:
from cluster_registry import pre_task_check
return pre_task_check(allowed_roles)
except ImportError:
pass
return True
print(f"[role_check] ⛔ 本机角色={role},需要角色={allowed_roles},跳过执行")
print(f"[role_check] 如需修改角色,编辑 {HOST_ID_PATH}")
return False
def check_capability(capability: str) -> bool:
"""
检查本机是否启用指定能力。
"""
info = read_host_id()
caps = info.get("capabilities", {})
if isinstance(caps.get(capability), bool):
return caps[capability]
# 也兼容字符串 True/False
if isinstance(caps.get(capability), str):
return caps[capability].lower() in ("true", "yes", "1")
print(f"[role_check] ⛔ 能力 {capability} 未定义或已禁用,跳过执行")
return False
def print_status():
"""打印本机角色状态"""
info = read_host_id()
print(f"[role_check] 本机角色: {info['role']}")
print(f"[role_check] 能力配置:")
for k, v in info.get("capabilities", {}).items():
status = "✅" if v else "❌"
print(f" {status} {k}: {v}")
if __name__ == "__main__":
if len(sys.argv) > 1:
cmd = sys.argv[1]
if cmd == "status":
print_status()
elif cmd == "check-role":
allowed = sys.argv[2:] if len(sys.argv) > 2 else ["master"]
if check_role(allowed):
print(f"[role_check] ✅ 角色确认通过")
sys.exit(0)
else:
sys.exit(1)
elif cmd == "check-capability":
cap = sys.argv[2] if len(sys.argv) > 2 else ""
if check_capability(cap):
print(f"[role_check] ✅ 能力确认通过: {cap}")
sys.exit(0)
else:
sys.exit(1)
else:
print_status()