📝 项目概述
本系统是一个基于 Python asyncio + Playwright + FastAPI 的全栈自动化工具, 专注于 巨量引擎(字节系)广告计划 的批量搭建与优化。 解决投放优化师日常工作中「重复建计划、人工效率低、无法 7×24h 运行」的核心痛点。
支持多账户并发调度、自动填充计划配置、批量上传素材、定时执行。从 Tkinter 桌面工具起步, 演进为 FastAPI + Vue 3 的 Web 全栈应用,并接入 Hermes AI Agent 框架 + 飞书机器人, 实现「发条消息就能建计划」的无人值守体验。
🔁 系统流程设计
用户通过飞书发送自然语言指令,系统自动解析意图、调度资源、执行操作并回传结果,形成完整闭环。
🎥 功能演示
全流程自动化运行效果:任务导入 → 渠道分配 → 并发执行 → 结果统计,完整闭环。
🏗️ 系统架构
采用经典的四层架构设计,各层职责清晰、独立演进。
✨ 核心能力
全栈架构
FastAPI + Vue 3 + SQLite,从桌面工具升级为 Web 全栈应用
AI Agent
35+ 注册工具,自然语言意图识别,自动编排执行步骤
飞书机器人
60+ 聊天命令,飞书即可完成所有管理操作
平台适配器
适配器模式抽象各平台,新渠道即插即用
定时调度
APScheduler 支持 cron / 每日 / 每周定时执行
步骤调试
拖拽式单步调试 + 实时日志 WebSocket
🛠️ 技术栈
📋 PRD — 产品需求文档
核心产品需求定义,明确系统边界与关键设计决策。
用户故事
- 作为优化师,我希望在飞书发送「搭建巨量计划,预算 5000」就能自动执行,不用手动操作浏览器。
- 作为优化师,我希望同时执行多个账户的计划搭建任务,系统自动管理并发,不要互相干扰。
- 作为管理者,我希望看到每天的执行报告,包括成功/失败数量、消耗、ROI 等核心指标。
- 作为开发者,我希望新增一个广告平台时只需实现几个接口,不需要改核心逻辑。
关键设计决策
🎯 选型:Playwright vs Selenium
Playwright 基于 CDP 协议,速度比 Selenium 快 3-5 倍,原生支持 asyncio 和 BrowserContext 隔离,多账户并行更安全。
🎯 专注:巨量引擎计划搭建
当前聚焦巨量引擎(字节系)广告计划搭建,覆盖批量建计划、素材上传、配置填充等核心环节,后续可扩展至更多渠道。
🧠 AI:Hermes Agent 编排
将核心功能封装为 skills + JSON Schema 描述,LLM 自然语言识别意图后自动调用对应 skill,无需硬编码对话逻辑。
⏱ 调度:APScheduler
使用 AsyncIOScheduler 集成到 FastAPI 中,支持 cron 表达式、每日定时、周期性执行,与异步引擎无缝配合。
📄 部分源代码
项目核心模块的关键代码片段,完整源码已开源在 GitHub。
1. 自动化引擎 — 多账户并发调度
核心是 asyncio.Semaphore 控制并发数,每个任务通过 async with self._semaphore 获取 Playwright BrowserContext 实例执行,用完自动释放。
# 自动化引擎核心 — 多账户并发调度 class AutomationEngine: """自动化引擎:管理 Playwright 浏览器实例与任务队列""" def __init__(self, max_concurrency: int = 5): self._semaphore = asyncio.Semaphore(max_concurrency) self._browsers: dict[str, BrowserContext] = {} self._task_queue = asyncio.Queue() self._running = False async def dispatch(self, task: Task): """分配任务到可用浏览器上下文""" async with self._semaphore: ctx = await self._get_context(task.platform) try: result = await self._execute(ctx, task) await task.callback(True, result) except Exception as e: await task.callback(False, str(e)) logger.error(f"任务失败: {task.id} - {e}") finally: self._task_queue.task_done()
2. 平台适配器模式 — 新渠道即插即用
抽象基类定义三个核心接口,快手、抖音等平台各自实现,新增渠道只需继承 PlatformAdapter(ABC)。
# 平台适配器模式 — 新渠道即插即用 class PlatformAdapter(ABC): """各广告平台适配器基类""" @abstractmethod async def login(self, credentials: dict) -> bool: """平台登录""" ... @abstractmethod async def create_plan(self, plan_data: dict) -> str: """创建广告计划""" ... @abstractmethod async def upload_material(self, file_path: str) -> str: """上传素材""" ... class KSAdapter(PlatformAdapter): """快手渠道适配器""" async def login(self, creds): logger.info("快手渠道登录...") return True async def create_plan(self, data): plan_id = await self.page.evaluate("createPlan", data) return plan_id
3. FastAPI 任务管理 API
异步路由处理任务创建与状态查询,创建后自动加入自动化队列执行。
router = APIRouter(prefix="/api/tasks") @router.post("/create") async def create_task( task: TaskCreate, db: Session = Depends(get_db) ): """创建自动化投放任务""" db_task = TaskModel( platform=task.platform, plan_name=task.plan_name, budget=task.budget, schedule_type=task.schedule_type, status="pending" ) db.add(db_task); db.commit(); db.refresh(db_task) await engine.dispatch(Task(id=db_task.id, platform=db_task.platform, data=task.dict())) return {"success": True, "task_id": db_task.id} @router.get("/status/{task_id}") async def get_task_status(task_id: int, db: Session = Depends(get_db)): task = db.query(TaskModel).get(task_id) if not task: raise HTTPException(404, "任务不存在") return { "id": task.id, "platform": task.platform, "status": task.status, "progress": task.progress, "logs": task.logs[-50:] }
4. SQLAlchemy ORM 模型
15 张业务表覆盖任务、账户、平台配置、执行日志等,Repository 模式封装数据访问。
class TaskModel(Base): __tablename__ = "tasks" id = Column(Integer, primary_key=True) platform = Column(String(32), nullable=False) plan_name = Column(String(128)) budget = Column(Float, default=0) status = Column(String(16), default="pending") progress = Column(Integer, default=0) logs = Column(Text, default="") created_at = Column(DateTime, default=datetime.utcnow) account_id = Column(Integer, ForeignKey("accounts.id")) account = relationship("AccountModel", back_populates="tasks")