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,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生成可灵任务对账表(供与账单核对)"""
|
||||
import datetime
|
||||
import json
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
urllib.request.install_opener(opener)
|
||||
|
||||
CFG = Path.home() / "workbuddy-agent-os" / "agent-local" / "tools" / "ave" / "config" / "local.yaml"
|
||||
AVE = Path.home() / "workbuddy-agent-os" / "agent-local" / "runtime" / "ave"
|
||||
|
||||
cfg = yaml.safe_load(CFG.read_text(encoding="utf-8"))
|
||||
k = cfg["kling"]
|
||||
hdr = {"Authorization": f"Bearer {k['api_key']}"}
|
||||
base = k["base_url"]
|
||||
|
||||
with urllib.request.urlopen(
|
||||
urllib.request.Request(base + "/v1/videos/text2video", headers=hdr), timeout=30) as r:
|
||||
vids = json.loads(r.read().decode()).get("data") or []
|
||||
|
||||
local_files = []
|
||||
for p in AVE.rglob("*.mp4"):
|
||||
local_files.append((p.stat().st_mtime, str(p.relative_to(AVE))))
|
||||
local_files.sort(reverse=True)
|
||||
|
||||
NOTES = {
|
||||
"934535832286281787": "首次验证 kling-v3 可用",
|
||||
"934536473163501607": "端到端流程 wf_5d5ffff0fc06 镜头1",
|
||||
"934536909153013802": "端到端流程 wf_5d5ffff0fc06 镜头2",
|
||||
"934537556619169795": "端到端流程 wf_5d5ffff0fc06 镜头3",
|
||||
"934539639007219781": "口播线验证(oral_verify2)",
|
||||
"934540864327012427": "并发生成测试 镜头1",
|
||||
"934540864352174168": "并发生成测试 镜头2",
|
||||
"934540864385880141": "并发生成测试 镜头3",
|
||||
"934542570569990201": "流程测试",
|
||||
"934612690390495243": "P2 编排通电验证",
|
||||
"934618791945191489": "完整链路验证 镜头1",
|
||||
"934618965245431828": "完整链路验证 镜头2",
|
||||
}
|
||||
|
||||
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
lines = ["# 可灵任务对账表", "", f"生成时间:{now}", "",
|
||||
"## 全部任务", "",
|
||||
"| 时间 | 任务号 | 状态 | 时长 | 用途 |",
|
||||
"|:--|:--|:--|:--|:--|"]
|
||||
|
||||
today_cnt = today_sec = 0
|
||||
rows = sorted(vids, key=lambda t: -int(t.get("created_at") or 0))
|
||||
for t in rows:
|
||||
ms = int(t.get("created_at") or 0)
|
||||
dts = datetime.datetime.fromtimestamp(ms / 1000).strftime("%Y-%m-%d %H:%M")
|
||||
vs = (t.get("task_result") or {}).get("videos") or []
|
||||
dur = float(vs[0].get("duration", 5) or 5) if vs else 5.0
|
||||
note = NOTES.get(str(t.get("task_id")), "(历史任务)")
|
||||
if dts.startswith("2026-10-01"):
|
||||
today_cnt += 1
|
||||
today_sec += dur
|
||||
lines.append(f"| {dts} | `{t.get('task_id')}` | {t.get('task_status')} | {dur:.1f}s | {note} |")
|
||||
|
||||
total_sec = sum(
|
||||
float(((t.get("task_result") or {}).get("videos") or [{}])[0].get("duration", 5) or 5)
|
||||
for t in rows
|
||||
)
|
||||
lines += ["",
|
||||
f"**今天(2026-10-01):{today_cnt} 条 / {today_sec:.1f} 秒**",
|
||||
f"**全部:{len(rows)} 条 / {total_sec:.1f} 秒**", "",
|
||||
"## 费用估算(按不同单价档)", "",
|
||||
"| 单价档 | 今天 | 全部 |", "|:--|:--|:--|",
|
||||
f"| ¥0.6/秒(无声 720P) | ¥{today_sec * 0.6:.1f} | ¥{total_sec * 0.6:.1f} |",
|
||||
f"| ¥1.2/秒(有声 / 1080P) | ¥{today_sec * 1.2:.1f} | ¥{total_sec * 1.2:.1f} |",
|
||||
f"| ¥2.4/秒(高配) | ¥{today_sec * 2.4:.1f} | ¥{total_sec * 2.4:.1f} |",
|
||||
"",
|
||||
"> ⚠️ 以上为**估算**。准确金额请以可灵控制台「消费明细」为准。",
|
||||
"> 若单价与我估算不符,请把明细发我,我核对后调整代码里的计费参数。",
|
||||
"",
|
||||
"## 本地产物文件(最新 20 个)", ""]
|
||||
for mt, rel in local_files[:20]:
|
||||
dts = datetime.datetime.fromtimestamp(mt).strftime("%m-%d %H:%M")
|
||||
lines.append(f"- `{dts}` {rel}")
|
||||
|
||||
out = AVE / "可灵任务对账表.md"
|
||||
out.write_text("\n".join(lines), encoding="utf-8")
|
||||
print(f"✅ 已生成: {out}")
|
||||
print(f" 今天 {today_cnt} 条 / {today_sec:.1f} 秒 · 全部 {len(rows)} 条 / {total_sec:.1f} 秒")
|
||||
Reference in New Issue
Block a user