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,152 @@
|
||||
"""
|
||||
agentos ave — 视频工厂(渲染/脚本/素材/模板)
|
||||
|
||||
封装 09_ave/scripts/ 目录下的 96 个脚本,提供 CLI 统一入口。
|
||||
|
||||
⚠️ 状态:**已废弃(2026-10-01)** —— 本插件引用的脚本路径全部失效
|
||||
(`01_director_parser/run.py`、`06_composer/run.py`、`07_service_layer/*.py`、
|
||||
`asset_manager/run.py` 均不存在;早期编号目录已归档到
|
||||
`90_archive/ave_legacy_numbered_dirs_20261001/`)。
|
||||
|
||||
视频工厂的实际入口:
|
||||
· CLI 原子能力:`09_ave/scripts/main.py`(voice/material/parse/compose/anchors…)
|
||||
· 生产模式: `09_ave/scripts/video_factory.py`(run_oral/beat/digital_human/hybrid/story)
|
||||
· 能力清单: `09_ave/config/capabilities.yaml` + Dashboard「🧩 能力库」
|
||||
· Dashboard: 视频工厂各视图(导演创作/工作流/能力库…)
|
||||
|
||||
保留本文件仅为兼容 `agentos ave --help` 的历史调用,**不建议继续使用**。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from agentos.base import AgentOSPlugin
|
||||
|
||||
AGENT_SYNC = Path(__file__).resolve().parent.parent.parent.parent.parent
|
||||
AVE_SCRIPTS = AGENT_SYNC / "05_tools" / "09_ave" / "scripts"
|
||||
|
||||
|
||||
class AvePlugin(AgentOSPlugin):
|
||||
name = "ave"
|
||||
description = "视频工厂 — 渲染/脚本/素材/模板"
|
||||
nav = {
|
||||
'group': '视频工厂',
|
||||
'icon': '🎬',
|
||||
'order': 3,
|
||||
'items': [
|
||||
{'view': 'ave-render', 'label': '渲染任务'},
|
||||
{'view': 'ave-script', 'label': '脚本生成'},
|
||||
{'view': 'ave-materials', 'label': '素材库'},
|
||||
{'view': 'ave-templates', 'label': '模板'},
|
||||
]
|
||||
}
|
||||
|
||||
def _run_script(self, script_name: str, args: list[str] = None) -> int:
|
||||
"""运行 AVE scripts 目录下的 Python 脚本"""
|
||||
script_path = AVE_SCRIPTS / script_name
|
||||
if not script_path.exists():
|
||||
print(f"脚本不存在: {script_path}")
|
||||
return 1
|
||||
cmd = [sys.executable, str(script_path)] + (args or [])
|
||||
result = subprocess.run(cmd, cwd=str(AVE_SCRIPTS))
|
||||
return result.returncode
|
||||
|
||||
def register(self, subparsers):
|
||||
p = subparsers.add_parser("ave", help=self.description)
|
||||
p_sub = p.add_subparsers(dest="ave_action", help="视频工厂操作")
|
||||
|
||||
# render - 视频渲染
|
||||
pr = p_sub.add_parser("render", help="视频渲染/生成")
|
||||
pr.add_argument("--strategy", default="oral", help="策略: oral/beat-sync/digital-human")
|
||||
pr.add_argument("--script", help="脚本路径")
|
||||
pr.add_argument("--count", type=int, default=1, help="生成数量")
|
||||
pr.set_defaults(ave_func=self.cmd_render)
|
||||
|
||||
# script - 脚本/文案生成
|
||||
ps = p_sub.add_parser("script", help="脚本/文案生成")
|
||||
ps.add_argument("action", nargs="?", default="list",
|
||||
choices=["list", "generate", "edit"],
|
||||
help="操作")
|
||||
ps.add_argument("--topic", help="主题/方向")
|
||||
ps.add_argument("--style", help="风格: 口播/卡点/叙事")
|
||||
ps.set_defaults(ave_func=self.cmd_script)
|
||||
|
||||
# material - 素材管理
|
||||
pm = p_sub.add_parser("material", help="素材管理")
|
||||
pm.add_argument("action", nargs="?", default="list",
|
||||
choices=["list", "search", "add", "stats"],
|
||||
help="操作")
|
||||
pm.add_argument("--tag", help="标签过滤")
|
||||
pm.add_argument("--type", dest="mat_type", help="素材类型: bgm/clip/character")
|
||||
pm.set_defaults(ave_func=self.cmd_material)
|
||||
|
||||
# template - 模板管理
|
||||
pt = p_sub.add_parser("template", help="模板管理")
|
||||
pt.add_argument("action", nargs="?", default="list",
|
||||
choices=["list", "create", "edit", "apply"],
|
||||
help="操作")
|
||||
pt.set_defaults(ave_func=self.cmd_template)
|
||||
|
||||
# character - 角色管理(包装 character_portrait.py)
|
||||
pch = p_sub.add_parser("character", help="角色管理")
|
||||
pch.add_argument("action", nargs="?", default="list",
|
||||
choices=["list", "create", "generate"],
|
||||
help="操作")
|
||||
pch.set_defaults(ave_func=self.cmd_character)
|
||||
|
||||
# workflow - 工作流(包装 workflow 模块)
|
||||
pwf = p_sub.add_parser("workflow", help="工作流管理")
|
||||
pwf.add_argument("action", nargs="?", default="list",
|
||||
choices=["list", "run", "status"],
|
||||
help="操作")
|
||||
pwf.set_defaults(ave_func=self.cmd_workflow)
|
||||
|
||||
return p
|
||||
|
||||
def dispatch(self, args):
|
||||
if hasattr(args, 'ave_func'):
|
||||
return args.ave_func(args)
|
||||
print("未知视频工厂命令,使用 agentos ave --help")
|
||||
return 1
|
||||
|
||||
def cmd_render(self, args):
|
||||
print(f"🎬 渲染任务: strategy={args.strategy}")
|
||||
if args.strategy == "oral":
|
||||
return self._run_script("01_director_parser/run.py", [f"--strategy={args.strategy}"])
|
||||
print(f" 调用 06_composer 模块 (strategy={args.strategy}, count={args.count})")
|
||||
return self._run_script("06_composer/run.py", [f"--strategy={args.strategy}", f"--count={args.count}"])
|
||||
|
||||
def cmd_script(self, args):
|
||||
if args.action == "generate":
|
||||
print(f"📝 生成脚本: topic={args.topic or '(自动)'}, style={args.style or '口播'}")
|
||||
return self._run_script("01_director_parser/run.py",
|
||||
["--action=generate_script"] +
|
||||
(["--topic", args.topic] if args.topic else []) +
|
||||
(["--style", args.style] if args.style else []))
|
||||
print(f"📝 脚本 {args.action}")
|
||||
return self._run_script("01_director_parser/run.py", [f"--action={args.action}"])
|
||||
|
||||
def cmd_material(self, args):
|
||||
print(f"📦 素材 {args.action}" + (f" (tag={args.tag})" if args.tag else ""))
|
||||
return self._run_script("asset_manager/run.py",
|
||||
[f"--action={args.action}"] +
|
||||
(["--tag", args.tag] if args.tag else []) +
|
||||
(["--type", args.mat_type] if args.mat_type else []))
|
||||
|
||||
def cmd_template(self, args):
|
||||
print(f"📋 模板 {args.action}")
|
||||
if args.action == "list":
|
||||
return self._run_script("07_service_layer/template_manager.py", ["list"])
|
||||
elif args.action == "apply":
|
||||
print("(模板应用需要指定模板ID,功能开发中)")
|
||||
return 0
|
||||
|
||||
def cmd_character(self, args):
|
||||
print(f"🧑 角色 {args.action}")
|
||||
return self._run_script("character_portrait.py", [f"--action={args.action}"])
|
||||
|
||||
def cmd_workflow(self, args):
|
||||
print(f"🔧 工作流 {args.action}")
|
||||
return self._run_script("07_service_layer/workflow_engine.py", [f"--action={args.action}"])
|
||||
Reference in New Issue
Block a user