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,236 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# fleet_sync.sh — AgentOS 联邦一键同步脚本 (v1.0)
# 功能: 一条命令让所有机器 git pull + 环境检查 + 状态报告
# 用法: bash 00_bootstrap/fleet_sync.sh
#
# 依赖:
# - Tailscale 网络 (所有机器在同一网络)
# - SSH 密钥已加入各机器 authorized_keys
# - ORACLE.yaml 定义了所有机器信息
# ═══════════════════════════════════════════════════════════════
set -e
# ── 配置 ──
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ORACLE="$SCRIPT_DIR/ORACLE.yaml"
PARALLEL=true
# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
ok() { echo -e " ${GREEN}✅${NC} $1"; }
warn() { echo -e " ${YELLOW}⚠️${NC} $1"; }
fail() { echo -e " ${RED}❌${NC} $1"; }
info() { echo -e " ${CYAN}ℹ️${NC} $1"; }
# ── 读取 ORACLE 获取机器信息 ──
echo ""
echo "═══════════════════════════════════════════════"
echo " 🚀 AgentOS 联邦同步"
echo "═══════════════════════════════════════════════"
echo ""
if [ ! -f "$ORACLE" ]; then
fail "ORACLE.yaml 不存在: $ORACLE"
exit 1
fi
# 用 Python 从 ORACLE 解析机器信息
MACHINES=$(python3 -c "
import yaml, json
d = yaml.safe_load(open('$ORACLE'))
machines = []
for name, info in d.get('machines', {}).items():
machines.append({
'name': name,
'hostname': info.get('hostname', name),
'tailscale_ip': info.get('tailscale_ip', ''),
'user': info.get('ssh_user', ''),
})
print(json.dumps(machines))
")
# 解析本机名
LOCAL_HOSTNAME=$(hostname)
# 同步每个远程机器
sync_machine() {
local name="$1"
local ip="$2"
local user="$3"
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 📡 $name ($ip)"
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# SSH 执行同步
local result
result=$(ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 \
"$user@$ip" '
export AGENT_SYNC="$HOME/workbuddy-agent-os/agent-sync"
export AGENT_LOCAL="$HOME/workbuddy-agent-os/agent-local"
echo "SYNC_START"
# 1. 环境变量检查
if [ -f ~/.zshrc ] && grep -q "AGENT_SYNC" ~/.zshrc 2>/dev/null; then
echo "ENV_OK"
else
echo "ENV_MISSING"
fi
# 2. git pull
cd "$AGENT_SYNC" 2>/dev/null && {
git pull origin main --rebase 2>&1 | tail -1
echo "GIT_VERSION:$(git log --oneline -1 2>/dev/null)"
} || echo "GIT_FAIL"
# 3. 目录检查
for d in "$AGENT_SYNC" "$AGENT_LOCAL"; do
if [ -d "$d" ]; then echo "DIR_OK:$d"; else echo "DIR_FAIL:$d"; fi
done
# 4. 关键文件检查
for f in "$AGENT_SYNC/ORACLE.yaml" "$AGENT_LOCAL/config.yaml"; do
if [ -f "$f" ]; then echo "FILE_OK:$f"; else echo "FILE_FAIL:$f"; fi
done
# 5. Dashboard 状态
local dash
dash=$(curl -s --connect-timeout 3 http://localhost:9988/api/identity 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin).get('hostname',''))" 2>/dev/null)
if [ -n "$dash" ]; then
echo "DASH_OK:$dash"
else
echo "DASH_FAIL"
fi
echo "SYNC_END"
' 2>&1)
local exit_code=$?
if [ $exit_code -ne 0 ]; then
fail "$name — SSH 连接失败 (exit=$exit_code)"
return 1
fi
# 解析结果
local env_ok=false dir_ok=true file_ok=true dash_ok=false git_ver=""
while IFS= read -r line; do
case "$line" in
ENV_OK) env_ok=true ;;
ENV_MISSING) warn "$name — 环境变量 AGENT_SYNC 未在 .zshrc 中配置" ;;
GIT_VERSION:*) git_ver="${line#GIT_VERSION:}" ;;
GIT_FAIL) fail "$name — Git 仓库不存在" ;;
DIR_OK:*) ;;
DIR_FAIL:*) fail "$name — 目录缺失: ${line#DIR_FAIL:}"; dir_ok=false ;;
FILE_OK:*) ;;
FILE_FAIL:*) fail "$name — 文件缺失: ${line#FILE_FAIL:}"; file_ok=false ;;
DASH_OK:*) dash_ok=true ;;
DASH_FAIL) warn "$name — Dashboard 未运行" ;;
Already|Updating|Successfully|From*) ;;
SYNC_START|SYNC_END) ;;
esac
done <<< "$result"
# 总结
if $env_ok && $dir_ok && $file_ok; then
ok "$name — 同步完成"
[ -n "$git_ver" ] && echo " Git: $git_ver"
$dash_ok && echo " Dashboard: ✅ 运行中" || echo " Dashboard: ⚠️ 未运行"
else
warn "$name — 部分异常,请检查"
fi
}
# 同步本机
sync_local() {
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🖥️ 本机 ($LOCAL_HOSTNAME)"
echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# 1. 环境变量
if [ -n "$AGENT_SYNC" ]; then
ok "环境变量 AGENT_SYNC=$AGENT_SYNC"
elif [ -f ~/.zshrc ] && grep -q "AGENT_SYNC" ~/.zshrc 2>/dev/null; then
warn "环境变量已配置但未生效,请执行: source ~/.zshrc"
else
warn "环境变量未配置"
fi
# 2. git pull
cd "$SCRIPT_DIR" 2>/dev/null
if git pull origin main --rebase 2>&1 | grep -q "Already up to date"; then
ok "Git 已是最新: $(git log --oneline -1)"
else
ok "Git 已更新: $(git log --oneline -1)"
fi
# 3. 目录检查
local sync_dir="$SCRIPT_DIR"
local local_dir="${AGENT_LOCAL:-$HOME/workbuddy-agent-os/agent-local}"
test -d "$sync_dir" && ok "agent-sync: ✅" || fail "agent-sync: ❌"
test -d "$local_dir" && ok "agent-local: ✅" || fail "agent-local: ❌"
# 4. 文件检查
test -f "$sync_dir/ORACLE.yaml" && ok "ORACLE.yaml: ✅" || fail "ORACLE.yaml: ❌"
test -f "$local_dir/config.yaml" && ok "config.yaml: ✅" || fail "config.yaml: ❌"
# 5. Dashboard
local dash
dash=$(curl -s --connect-timeout 2 http://localhost:9988/api/identity 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin).get('hostname',''))" 2>/dev/null)
if [ -n "$dash" ]; then
ok "Dashboard: ✅ ($dash)"
else
warn "Dashboard: 未运行"
fi
}
# ── 主流程 ──
sync_local
echo ""
echo " ─────────────────────────────────────────"
echo ""
# 解析机器列表并逐个同步
REMOTE_COUNT=0
while IFS= read -r line; do
name=$(echo "$line" | python3 -c "import sys,json;print(json.load(sys.stdin).get('name',''))")
ip=$(echo "$line" | python3 -c "import sys,json;print(json.load(sys.stdin).get('tailscale_ip',''))")
user=$(echo "$line" | python3 -c "import sys,json;print(json.load(sys.stdin).get('user',''))")
# 跳过本机
if [ "$(echo "$line" | python3 -c "import sys,json;print(json.load(sys.stdin).get('hostname',''))")" = "$LOCAL_HOSTNAME" ]; then
continue
fi
if [ -n "$ip" ] && [ -n "$user" ]; then
sync_machine "$name" "$ip" "$user"
REMOTE_COUNT=$((REMOTE_COUNT + 1))
echo ""
fi
done < <(echo "$MACHINES" | python3 -c "
import sys, json
machines = json.load(sys.stdin)
for m in machines:
print(json.dumps(m))
")
# ── 汇总 ──
echo "═══════════════════════════════════════════════"
echo " 同步完成"
echo "═══════════════════════════════════════════════"
echo ""
ok "本机: $LOCAL_HOSTNAME"
ok "远程机器: $REMOTE_COUNT 台已同步"
echo ""
echo " 如果一切正常,所有机器的代码和配置现在是一致的。"
echo " 如果某台机器报 ❌,请检查 SSH 连接或手动登录处理。"
echo ""