十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

单机部署多浏览器智能体:Playwright与Agent框架的工程实践

单机部署多浏览器智能体:Playwright与Agent框架的工程实践 最近在尝试把一些重复的网页操作自动化比如批量处理数据、定时检查状态或者模拟一些用户行为。一开始觉得不就是写个脚本嘛用 Selenium 或者 Puppeteer 控制浏览器不就行了但真做起来才发现事情没那么简单。单账号、单任务跑起来还好一旦想同时跑多个任务或者让多个“机器人”各自独立、长期稳定地运行麻烦就来了。浏览器实例管理、Cookie 隔离、资源占用、异常处理……每一个环节都可能让脚本在运行几小时后莫名崩溃。这时候一个更“工程化”的思路就变得很重要我们需要的可能不是一个简单的脚本而是一个能管理多个独立“智能体”Agent的框架。每个智能体都有自己的身份、会话状态和任务队列它们可以像真正的用户一样在各自的浏览器环境中独立工作互不干扰。这听起来像是需要一套复杂的分布式系统但有没有可能在单台 Mac 上就实现呢这就是“Lots of Agents”这个项目试图回答的问题。它不是一个具体的、公开可下载的工具但从其标题和相关的技术热词如 Cursor、Grok、Playwright、Agents 开发来看它指向了一个非常具体的工程场景在单台 Mac 上实现大量已登录状态的、基于浏览器的自动化智能体的无限运行和管理。这背后涉及的核心技术栈很可能包括 Cursor作为 AI 辅助的 IDE 或 Agent 运行环境、Grok作为 AI 模型或特定的自动化服务、Playwright作为浏览器自动化控制库以及一系列围绕身份管理、状态持久化和任务调度的工程实践。今天我们不讨论某个特定的、未公开的项目而是基于这个极具吸引力的命题深入探讨一下如果你想在单台 Mac 上构建一个能稳定运行大量“已登录”自动化智能体的系统你需要跨越哪些技术鸿沟真正的难点在哪里以及一个可行的、从零到一的实现路径应该是怎样的。1. 核心挑战从“跑通一个脚本”到“管理一群智能体”很多人对自动化智能体的第一印象还停留在写一个 Python 脚本用playwright打开浏览器填表、点击、抓取数据。这没错但这只是万里长征的第一步。当你的目标从“完成一次任务”变为“让一群智能体 7x24 小时稳定工作”时挑战的性质就完全变了。1.1 身份与状态的隔离每个智能体都是独立的“人”这是最核心的挑战。一个“已登录”的智能体意味着它拥有独立的用户会话Session。在 Web 应用中这通常由 Cookie、LocalStorage、SessionStorage 以及可能的 Token 来维持。简单脚本的陷阱一个常见的错误是在同一个浏览器进程或用户数据目录User Data Dir下启动多个 Playwright 上下文Context或页面Page并认为它们是隔离的。实际上它们可能共享底层存储导致 Cookie 串号A 智能体的操作影响了 B 智能体的登录状态。正确的隔离单元真正的隔离必须以“浏览器实例 独立的用户数据目录”为最小单元。每个智能体都应该拥有自己完全独立的--user-data-dir。这意味着为每个智能体启动一个独立的浏览器进程尽管 Playwright 可以复用浏览器二进制文件并为其指定一个唯一的、干净的本地目录来存放缓存、Cookie 等数据。这是模拟多个真实用户客户端的基础。1.2 资源管理与生命周期别让智能体“撑死”你的 Mac单台 Mac 的资源CPU、内存、磁盘 I/O是有限的。同时运行 10 个、50 个甚至 100 个带图形界面的浏览器实例这听起来就像一场灾难。无头模式Headless是必选项对于自动化任务绝大多数情况下不需要看到浏览器界面。使用headlessTrue或较新版本的headless“new”可以大幅减少内存和 GPU 开销。这是支持多实例的前提。进程与内存监控你需要一个“管理者”来监控每个智能体浏览器进程的资源消耗。当某个智能体内存泄漏Web 应用本身或你的脚本可能导致或僵死时管理者需要能安全地终止并重启它。这涉及到进程信号处理、状态检查和优雅退出机制。会话持久化与恢复智能体崩溃了难道要重新登录吗理想情况下它的“状态”主要是那个独立的user-data-dir应该被保留。重启后通过加载相同的用户数据目录它应该能恢复到崩溃前的登录会话。这要求你的架构能够将智能体实例与其状态目录动态关联和管理。1.3 任务调度与通信智能体不是孤岛智能体们通常不是漫无目的地上网它们需要执行具体的任务访问某个 URL、填写表单、提取信息、等待特定元素出现、处理分页等。中心化任务队列一个常见的模式是有一个中心化的任务队列例如使用 Redis、RabbitMQ或者简单点用一个数据库表。管理者从队列中取出任务分配给空闲的智能体执行。这避免了智能体之间的任务冲突也便于统一监控和重试。智能体与主控的通信智能体运行在独立浏览器进程中的脚本如何向主控程序报告状态“任务开始”、“任务成功”、“任务失败错误原因是XXX”又或者如何接收新的指令这可以通过进程间通信IPC、WebSocket 连接或者更简单地让智能体脚本在执行关键节点后向一个中心化的 API 或数据库报告状态来实现。结果收集与处理智能体执行任务产生的数据抓取到的文本、截图、错误日志需要被统一收集、存储和处理。这需要一个设计良好的数据管道。2. 技术栈选型构建你的“智能体农场”基石基于以上挑战我们可以勾勒出一个可行的技术栈。这里没有唯一的答案但以下组合是经过实践检验的可靠路径。2.1 浏览器自动化核心Playwright vs. Puppeteer vs. Selenium对于此类高并发、需要稳定控制的应用Playwright 是目前最推荐的选择理由如下多浏览器支持一套 API 控制 Chromium、Firefox、WebKit覆盖更全。自动等待内置的智能等待机制page.wait_for_selector,page.wait_for_function能极大简化脚本避免因网络或渲染延迟导致的失败。强大的上下文隔离Playwright 的BrowserContext概念天然适合隔离会话虽然如前所述对于最高级别的隔离我们仍倾向于使用独立的浏览器实例用户目录但 Context 在单实例内做轻量级隔离时仍有价值。丰富的工具链Codegen录制脚本、Trace Viewer调试等工具能极大提升开发效率。相比之下Puppeteer 只专注于 ChromeSelenium 的 API 相对老旧在复杂异步页面的控制上不如 Playwright 简洁稳定。2.2 智能体运行环境与开发为什么是 Cursor“Lots of Agents”标题中提到了 Cursor。Cursor 是一个集成了强大 AI如 GPT-4、Claude的 IDE。它在这里的角色可能有两个高效的开发工具编写和调试 Playwright 智能体脚本。AI 辅助可以快速生成选择器、处理异步逻辑、编写错误处理代码大幅提升开发这类自动化脚本的效率。潜在的“元智能体”平台更激进的设想是利用 Cursor 的 AI 能力动态生成、调整或调度其他智能体的行为逻辑。例如一个“管理者智能体”在 Cursor 中运行分析任务需求然后生成或修改具体的 Playwright 脚本再分发给底层的“工人智能体”去执行。这实现了更高阶的自动化。对于大多数实际项目我们首先将 Cursor 视为一个生产力倍增的开发工具。用它来写 Playwright 脚本、设计状态机、处理异常流程事半功倍。2.3 编排与管理层让一切井然有序这是将一堆脚本提升为“智能体系统”的关键。进程管理Python 的subprocess模块可以启动和管理浏览器进程。更高级的选择是使用supervisor或systemd在 Mac 上是launchd来守护进程但这在动态创建大量进程的场景下可能过于笨重。通常一个用 Python 或 Node.js 写的自定义“调度器”程序更灵活。状态存储你需要一个地方记录哪个智能体 ID 对应哪个用户数据目录路径、当前状态空闲/忙碌/死亡、当前任务、启动时间、历史日志等。一个轻量级的 SQLite 数据库或 Redis 就足够了。任务队列同样Redis 的 List 或 Sorted Set 数据结构非常适合做简单的任务队列。如果需要更复杂的特性优先级、延迟任务、死信队列可以考虑 CeleryPython或 BullNode.js等专业队列库。日志与监控每个智能体应该将日志输出到独立的文件同时调度器也应该有全局日志。使用像structlog或winston这样的结构化日志库便于后续用 ELK 或 Grafana 进行分析。监控内存、CPU 使用率也是必要的。2.4 身份与认证管理最棘手的部分“Infinite Logged in”是最大的卖点也是最难实现的部分。完全自动化的注册和登录往往不现实需要验证码、手机号等因此通常依赖预先准备好的、已登录的状态。“Auth Store”的启示在相关热词中出现了auth store: /home/honor/.openclaw/agents/main/agent/auth-profiles.json这个路径。这强烈暗示了一种设计模式将认证信息Cookie、Token、用户数据目录快照作为“配置文件”或“资源文件”进行管理。实现思路手动制备通过人工或半自动方式使用独立的脚本或程序为每个需要的账号完成登录流程并将登录成功后的浏览器用户数据目录user-data-dir完整地压缩备份。集中存储将这些备份文件或提取出的关键 Cookie 文件存储在中央仓库如auth-profiles.json所指向的结构化存储中。每个备份对应一个“智能体身份”。动态部署当调度器需要启动一个智能体时从仓库中取出一个空闲的身份包解压到一个临时工作目录并将此目录作为--user-data-dir参数启动浏览器。任务完成后可以根据策略决定是销毁该目录下次用干净的备份重建还是保留修改用于维持更长期的状态。安全警告这种方式意味着大量账号的认证信息以文件形式存储。必须极其注意安全这些文件应加密存储访问权限严格控制并且要清楚了解相关网站的服务条款避免违规操作。3. 从零搭建一个最小可行系统架构让我们抛开抽象概念设计一个最简单的、可在单台 Mac 上运行的“多智能体系统”架构。[任务提交端] - (任务放入) - [Redis 任务队列] | v [调度器 (Scheduler)] - (轮询队列) - [Redis] | (分配任务) v [智能体 Worker 池] / | \ / | \ [Worker1] [Worker2] ... [WorkerN] | | | v v v [Playwright] [Playwright] [Playwright] [独立用户目录] [独立用户目录] ... [独立用户目录] | | | v v v [目标网站] [目标网站] [目标网站]核心组件说明Redis作为消息总线。存储任务队列、智能体状态、全局配置。调度器 (Scheduler)一个常驻 Python 脚本。它的职责是监听 Redis 中的任务队列。检查有哪些空闲的智能体 Worker通过检查 Worker 在 Redis 中的心跳或状态。将任务分配给空闲 Worker并更新任务和 Worker 的状态。监控 Worker 的健康状况重启僵死的 Worker。智能体 Worker这才是执行具体任务的单元。每个 Worker 是一个独立的进程它启动时向 Redis 注册自己声明自己空闲。从调度器那里领取任务通过 Redis 传递任务详情。根据任务要求找到分配给自己的“身份包”用户数据目录启动一个 Playwright 浏览器实例指向该目录。执行 Playwright 脚本与目标网站交互。将执行结果成功数据或失败错误写回 Redis 指定的位置。报告心跳任务完成后重新声明自己为空闲状态。身份仓库 (Auth Store)一个目录或数据库存储所有可用的“身份包”用户数据目录的压缩包。调度器或 Worker 在启动时按需从这里领取和解压身份。一个 Worker 的简化启动流程Python示例import asyncio import redis import json import shutil import tempfile from playwright.async_api import async_playwright async def worker_loop(worker_id): r redis.Redis(hostlocalhost, port6379, db0) # 1. 注册 worker r.set(fworker:{worker_id}:status, idle) while True: # 2. 等待任务分配 (这里简化实际应由调度器主动分配) task_data r.brpop(fworker:{worker_id}:tasks, timeout30) if not task_data: continue _, task_json task_data task json.loads(task_json) r.set(fworker:{worker_id}:status, busy) # 3. 准备身份目录 auth_profile_id task.get(auth_profile_id) user_data_dir prepare_user_data_dir(auth_profile_id) # 从仓库解压到临时目录 # 4. 执行任务 async with async_playwright() as p: # 使用独立的用户数据目录启动浏览器 browser await p.chromium.launch_persistent_context( user_data_diruser_data_dir, headlessTrue, args[--disable-blink-featuresAutomationControlled] # 可选反反爬 ) page await browser.new_page() try: # 这里是具体的 Playwright 操作逻辑 await page.goto(task[url]) # ... 更多操作 ... result {status: success, data: ...} except Exception as e: result {status: error, message: str(e)} finally: await browser.close() # 清理临时目录或根据策略保留 shutil.rmtree(user_data_dir, ignore_errorsTrue) # 5. 上报结果恢复空闲 r.set(ftask:{task[id]}:result, json.dumps(result)) r.set(fworker:{worker_id}:status, idle) def prepare_user_data_dir(profile_id): 从中央仓库解压对应的身份包到临时目录 temp_dir tempfile.mkdtemp(prefixfagent_{profile_id}_) # 假设身份包是 tar.gz 格式存储在指定路径 auth_store_path f/path/to/auth_store/{profile_id}.tar.gz shutil.unpack_archive(auth_store_path, temp_dir) return temp_dir if __name__ __main__: asyncio.run(worker_loop(worker_01))4. 进阶考量与避坑指南当你把基础系统跑起来后才会遇到真正考验工程能力的深水区。4.1 反爬虫对抗智能体不是“机器人”现代网站有完善的反爬虫机制。大量来自同一 IP、具有相似浏览器指纹的请求会很快被封锁。指纹伪装Playwright 可以通过args传递各种启动参数来修改浏览器指纹如--disable-blink-featuresAutomationControlled可以隐藏自动化特征。但更高级的指纹检测Canvas, WebGL, Fonts需要更复杂的对抗。代理IP池这是必须的。每个智能体 Worker 应该配置不同的代理 IP。代理服务需要稳定、匿名并且最好支持按请求更换 IP。管理代理IP的可用性、速度、成本是另一个复杂子系统。行为模拟脚本的操作节奏不能太规律。需要加入随机延迟、模拟人的鼠标移动轨迹Playwright 支持page.mouse.move(x, y, steps10)、随机滚动页面等。太快太准的操作本身就是非人类信号。4.2 稳定性与容错预期一切都会失败网络会断网站会改版元素选择器会失效验证码会弹出。健壮的 Selector优先使用>
返回列表