AI Agent实战:结合Playwright与LLM实现智能网页自动化

AI Agent实战:结合Playwright与LLM实现智能网页自动化
最近在折腾AI自动化项目时发现一个痛点很多AI模型虽然能写代码、回答问题但一旦涉及需要实时信息或操作网页的任务就“离线”了。比如让它查一下最新的技术文档、自动填写表单、或者监控某个网页的变化它都无能为力。这让我开始思考能不能给AI装上一个“眼睛”和“手”让它能自己上网干活经过一番探索我成功地将一个浏览器自动化环境与一个强大的AI模型我们暂且称它为Codex结合了起来。现在这个AI不仅能理解我的自然语言指令还能自动打开浏览器、导航到指定页面、提取信息、点击按钮甚至完成一系列复杂的网页操作流程。这就像是为AI配备了一个全功能的浏览器让它从“离线智库”变成了“在线智能体”。本文将详细拆解这个“为AI装浏览器”的完整实现过程。无论你是对AI Agent智能体开发感兴趣想了解网页自动化与AI结合的实战还是单纯想实现一个能自动处理网页任务的脚本这篇文章都能提供从概念到代码的一站式指南。我们将从核心概念讲起一步步搭建环境、编写代码并解决其中常见的坑点。1. 背景与核心概念当AI遇见浏览器自动化在深入代码之前我们有必要厘清几个关键概念理解我们到底在构建什么。1.1 什么是AI Agent智能体简单来说AI Agent是一个能够感知环境、自主决策并执行行动以实现目标的软件实体。它不仅仅是聊天机器人更是一个“执行者”。一个完整的Agent通常包含感知模块获取环境信息如读取屏幕文字、解析网页HTML。决策模块基于目标和感知信息规划下一步行动通常由大语言模型驱动。执行模块将决策转化为具体操作如点击鼠标、输入文本。我们本次项目就是在构建一个具备网页环境感知和执行能力的AI Agent。1.2 浏览器自动化工具Selenium与Playwright要让程序控制浏览器我们需要借助自动化工具。目前主流的有Selenium老牌、稳定、社区庞大支持多种语言和浏览器。是入门和传统Web测试的首选。Playwright后起之秀由微软开发支持Chromium、Firefox、WebKit。它提供了更强大的API如自动等待、网络拦截、移动端模拟等在现代Web自动化项目中越来越流行。考虑到Playwright在可靠性和功能上的优势本文将选用Playwright作为我们的“浏览器之手”。1.3 “Codex”与AI决策核心这里的“Codex”是一个代称泛指能够理解指令、生成代码或规划步骤的大语言模型LLM例如OpenAI的GPT系列、Anthropic的Claude、或是开源的DeepSeek等。它的角色是决策模块分析用户指令“去GitHub trending页面把前三个仓库的名字和star数保存下来”然后生成一系列可执行的浏览器操作步骤。1.4 整体架构我们的系统工作流程如下用户提出一个自然语言任务。AI决策核心LLM理解任务并将其分解成一系列具体的、可执行的浏览器操作指令JSON格式。指令解析与执行引擎接收这些指令调用Playwright来实际操控浏览器。Playwright驱动真实的浏览器如Chrome执行操作并将结果如页面文本、截图返回。执行引擎将结果反馈给AI决策核心以进行后续步骤的决策如需多步操作。最终任务完成结果返回给用户。接下来我们就从环境搭建开始亲手实现这个流程。2. 环境准备与版本说明我们将使用Python作为主要开发语言因为它拥有丰富的AI和自动化库生态。2.1 基础环境操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)均可。本文示例在Windows 11上完成。Python版本 3.8。建议使用3.9或3.10以获得最佳兼容性。包管理工具pip。2.2 核心Python库我们需要安装以下库请在你的项目虚拟环境中执行# 创建并激活虚拟环境推荐 python -m venv venv # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 安装核心库 pip install playwright # 浏览器自动化框架 pip install openai # 用于接入OpenAI API示例用可选其他LLM # 或者使用其他LLM SDK例如pip install anthropic 用于Claude # 安装Playwright所需的浏览器内核 playwright install chromium注意playwright install命令会下载Chromium浏览器体积较大请耐心等待。2.3 AI模型API准备本文将以OpenAI GPT-4 API为例。你需要拥有一个OpenAI平台账号。在 API Keys页面 创建一个API密钥。妥善保管该密钥我们将把它设置为环境变量。替代方案你也可以使用任何提供API的LLM服务如DeepSeek、智谱AI、月之暗面等只需替换对应的SDK和调用方式即可。2.4 项目结构建议创建如下项目结构ai_browser_agent/ ├── main.py # 主程序入口 ├── agent/ │ ├── __init__.py │ ├── browser_engine.py # Playwright浏览器引擎封装 │ └── llm_planner.py # AI决策规划模块 ├── config.py # 配置文件如API密钥 ├── requirements.txt # 项目依赖 └── tasks/ # 存储任务示例或结果3. 核心模块拆解与实现我们将系统拆分为两个核心模块浏览器引擎和AI规划器。3.1 浏览器引擎模块 (browser_engine.py)这个模块负责所有与浏览器交互的底层操作它提供一套简洁的API供上层调用。# agent/browser_engine.py import asyncio from typing import Dict, Any, Optional from playwright.async_api import async_playwright, Page, BrowserContext class BrowserEngine: def __init__(self, headless: bool False): 初始化浏览器引擎。 :param headless: 是否以无头模式运行不显示浏览器界面 self.headless headless self.playwright None self.browser None self.context None self.page None async def start(self): 启动Playwright和浏览器实例 self.playwright await async_playwright().start() # 使用Chromium可替换为 firefox 或 webkit self.browser await self.playwright.chromium.launch(headlessself.headless) self.context await self.browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ) self.page await self.context.new_page() print(浏览器引擎启动成功。) async def goto(self, url: str): 导航到指定URL await self.page.goto(url) print(f已导航至: {url}) async def get_content(self, selector: Optional[str] None) - str: 获取页面内容。 :param selector: 可选CSS选择器用于获取特定元素内容。如为None则获取整个页面文本。 :return: 文本内容 if selector: element await self.page.wait_for_selector(selector, stateattached) content await element.text_content() else: # 获取body内容避免获取过多脚本和样式文本 content await self.page.text_content(body) return content.strip() if content else async def click(self, selector: str): 点击指定元素 await self.page.click(selector) print(f已点击元素: {selector}) async def type_text(self, selector: str, text: str): 在输入框中输入文本 await self.page.fill(selector, text) print(f已在 {selector} 中输入: {text}) async def screenshot(self, path: str screenshot.png): 截取当前页面屏幕 await self.page.screenshot(pathpath) print(f截图已保存至: {path}) async def execute_action(self, action: Dict[str, Any]): 执行一个标准化动作指令。 指令格式示例: {action: goto, params: {url: https://example.com}} action_type action.get(action) params action.get(params, {}) if action_type goto: await self.goto(params[url]) elif action_type click: await self.click(params[selector]) elif action_type type: await self.type_text(params[selector], params[text]) elif action_type get_content: return await self.get_content(params.get(selector)) elif action_type screenshot: await self.screenshot(params.get(path, screenshot.png)) else: raise ValueError(f未知动作类型: {action_type}) return None async def close(self): 关闭浏览器和Playwright if self.browser: await self.browser.close() if self.playwright: await self.playwright.stop() print(浏览器引擎已关闭。)3.2 AI规划器模块 (llm_planner.py)这个模块负责与LLM对话将自然语言任务解析成浏览器操作指令序列。# agent/llm_planner.py import openai import json import os from typing import List, Dict, Any # 从环境变量或配置文件读取API密钥 openai.api_key os.getenv(OPENAI_API_KEY) class LLMPlanner: def __init__(self, model: str gpt-4): self.model model # 系统提示词用于定义AI的角色和能力 self.system_prompt 你是一个专业的网页自动化助手。你的任务是将用户用自然语言描述的任务分解成一系列具体的、可执行的浏览器操作步骤。 操作步骤必须使用以下JSON格式的指令列表来表示每个指令包含一个action和对应的params。 可用的动作 (action) 包括 1. goto: 导航到某个URL。 参数: {url: https://example.com} 2. click: 点击一个页面元素。 参数: {selector: CSS选择器例如 button#submit} 3. type: 在输入框中输入文本。 参数: {selector: input[nameq], text: 要输入的文字} 4. get_content: 获取页面或特定元素的文本内容。 参数: {selector: 可选CSS选择器。如果为空则获取整个页面主要内容} 5. screenshot: 截取屏幕。 参数: {path: 截图保存路径可选默认screenshot.png} 请根据任务逻辑输出一个JSON数组。例如对于任务“打开百度并搜索AI”你应该输出 [ {action: goto, params: {url: https://www.baidu.com}}, {action: type, params: {selector: input#kw, text: AI}}, {action: click, params: {selector: input#su}}, {action: get_content, params: {selector: div#content_left}} ] 注意 - 选择器应尽量使用稳定且唯一的ID、类名或属性。 - 考虑页面加载时间必要时在关键步骤后添加get_content来确认状态。 - 只输出纯JSON不要有任何额外的解释或标记。 def plan_actions(self, user_task: str) - List[Dict[str, Any]]: 根据用户任务规划浏览器操作步骤 try: response openai.ChatCompletion.create( modelself.model, messages[ {role: system, content: self.system_prompt}, {role: user, content: user_task} ], temperature0.1, # 低温度保证输出格式稳定 max_tokens1000 ) # 从AI回复中提取JSON部分 content response.choices[0].message.content.strip() # 处理可能出现的代码块标记 if content.startswith(json): content content[7:-3] # 移除 json 和结尾的 elif content.startswith(): content content[3:-3] # 移除通用的 actions json.loads(content) if not isinstance(actions, list): actions [actions] print(fAI规划了 {len(actions)} 个步骤。) return actions except json.JSONDecodeError as e: print(f解析AI返回的JSON时出错: {e}) print(f原始返回内容: {content}) raise except Exception as e: print(f调用AI API时出错: {e}) raise4. 完整实战案例让AI自动查询GitHub趋势榜现在我们将上述模块组合起来完成一个具体的任务“打开GitHub Trending页面https://github.com/trending获取今天排名前5的仓库名称和描述并保存到本地文件。”4.1 创建主程序 (main.py)主程序负责串联整个流程接收任务 - AI规划 - 执行自动化 - 输出结果。# main.py import asyncio import json from agent.browser_engine import BrowserEngine from agent.llm_planner import LLMPlanner async def main(): # 1. 用户输入任务 user_task 请打开GitHub Trending页面 (https://github.com/trending)获取今天Today排名前5的仓库repository信息。 对于每个仓库我需要它的名称name和描述description。 请将这些信息整理并保存下来。 print(f接收到的任务: {user_task}) # 2. 初始化AI规划器和浏览器引擎 planner LLMPlanner(modelgpt-4) # 可根据实际情况调整模型 browser_engine BrowserEngine(headlessFalse) # 设为True则不显示浏览器窗口 try: # 3. AI规划行动步骤 print(正在请求AI规划行动步骤...) actions planner.plan_actions(user_task) print(规划步骤:, json.dumps(actions, indent2, ensure_asciiFalse)) # 4. 启动浏览器并执行步骤 await browser_engine.start() results [] for i, action in enumerate(actions, 1): print(f\n--- 执行步骤 {i}/{len(actions)}: {action[action]} ---) # 执行单个动作如果是get_content则收集结果 result await browser_engine.execute_action(action) if result: results.append(result) print(f获取到内容片段长度: {len(result)} 字符) # 5. 将最终获取的内容交给AI进行信息提取和整理 print(\n所有步骤执行完毕。正在请求AI从抓取的内容中提取结构化信息...) # 这里简单地将所有获取的内容拼接起来。更复杂的场景可能需要分步处理。 combined_content \n---\n.join(results) # 构建一个专门用于信息提取的提示 extraction_prompt f 以下是从GitHub Trending页面抓取到的文本内容。请从中提取出今天Today排名前5的仓库信息。 对于每个仓库请提取 1. 仓库名称name 2. 仓库描述description 请以JSON数组的格式输出每个元素是一个包含name和description键的对象。 抓取内容 {combined_content[:3000]} # 限制长度避免超出token限制 extraction_actions planner.plan_actions(extraction_prompt) # 注意这里plan_actions返回的是动作列表但我们的提示是让它直接输出JSON。 # 我们需要调整系统提示或直接调用一个只做文本分析的LLM对话。 # 为了简化我们这里假设AI返回的extraction_actions是一个只有一个get_content动作的列表 # 并且其params中包含了我们想要的JSON字符串。这需要更精细的提示工程。 # 作为演示我们换一种更直接的方式 final_response openai.ChatCompletion.create( modelgpt-4, messages[ {role: system, content: 你是一个数据提取助手。请从提供的文本中精确提取信息并以指定的JSON格式输出。}, {role: user, content: extraction_prompt} ], temperature0 ) extracted_data_str final_response.choices[0].message.content.strip() # 清理可能的代码块标记 if extracted_data_str.startswith(json): extracted_data_str extracted_data_str[7:-3] elif extracted_data_str.startswith(): extracted_data_str extracted_data_str[3:-3] try: trending_repos json.loads(extracted_data_str) print(\n 提取到的GitHub今日趋势榜Top 5 ) for idx, repo in enumerate(trending_repos, 1): print(f{idx}. 名称: {repo.get(name, N/A)}) print(f 描述: {repo.get(description, N/A)}) print() # 6. 保存结果到文件 with open(github_trending_top5.json, w, encodingutf-8) as f: json.dump(trending_repos, f, indent2, ensure_asciiFalse) print(结果已保存至 github_trending_top5.json) except json.JSONDecodeError: print(AI返回的信息提取结果不是有效JSON原始内容如下) print(extracted_data_str) # 可选最后截个图作为凭证 await browser_engine.screenshot(final_page.png) except Exception as e: print(f程序执行过程中出错: {e}) finally: # 7. 确保浏览器被关闭 await browser_engine.close() print(任务结束。) if __name__ __main__: # 运行异步主函数 asyncio.run(main())4.2 配置环境变量在运行前需要设置你的OpenAI API密钥。Linux/macOS:export OPENAI_API_KEY你的-api-key-here python main.pyWindows (PowerShell):$env:OPENAI_API_KEY你的-api-key-here python main.pyWindows (CMD):set OPENAI_API_KEY你的-api-key-here python main.py4.3 运行与验证确保已安装所有依赖并配置好API密钥。在终端中运行python main.py。你会看到浏览器自动打开并导航到GitHub Trending页面。AI会控制浏览器进行可能的滚动或点击如果它规划了这些步骤。程序会在控制台打印出提取到的仓库信息并将完整结果保存为github_trending_top5.json文件。当前页面截图也会保存为final_page.png。4.4 结果示例github_trending_top5.json文件内容可能如下[ { name: microsoft/Codex-CLI, description: 一个基于OpenAI Codex的命令行代码生成工具。 }, { name: langchain-ai/langchain, description: 用于开发由语言模型驱动的应用程序的框架。 }, { name: facebookresearch/llama, description: Meta的大型语言模型项目。 }, { name: google-research/google-research, description: Google Research的公开代码仓库。 }, { name: deepmind/deepmind-research, description: DeepMind的公开研究论文和代码。 } ]5. 常见问题与排查思路在实现和运行此类AI浏览器自动化项目时你可能会遇到以下问题问题现象可能原因排查与解决思路playwright install失败或极慢网络连接问题或访问CDN受限。1. 检查网络连接。2. 尝试设置代理环境变量如HTTP_PROXY,HTTPS_PROXY。3. 使用playwright install --help查看是否有离线安装选项。浏览器启动失败报错找不到可执行文件Playwright的浏览器内核未正确安装。1. 确认已运行playwright install chromium。2. 检查安装路径权限。3. 尝试卸载 (pip uninstall playwright) 后重装并确保安装过程无报错。AI返回的操作步骤无法执行如元素找不到1. 网页结构已变化CSS选择器失效。2. AI生成的选择器不够精确或错误。3. 页面未完全加载就执行操作。1.手动验证打开浏览器开发者工具检查AI生成的选择器是否能定位到目标元素。2.增强提示词在系统提示中要求AI使用更稳定、唯一的属性如id,>OpenAI API调用失败报错AuthenticationErrorAPI密钥未设置或错误。1. 确认已正确设置OPENAI_API_KEY环境变量。2. 检查密钥是否有权限、是否过期。3. 尝试在代码中直接传入密钥仅用于测试生产环境务必用环境变量openai.api_key sk-...。AI返回的指令不是有效JSON1. AI没有严格遵守提示词格式。2. 提示词不够清晰。3. 模型temperature参数过高导致输出随机。1.优化提示词在系统提示中更严格地规定输出格式例如“你必须只输出一个JSON数组不要有任何其他文本。”2.降低temperature设置为0或0.1使输出更确定。3.后处理在代码中添加更健壮的JSON提取逻辑如使用正则表达式匹配\[.*\]或\{.*\}。程序卡住或无响应1. 异步操作死锁。2. 某个页面操作如等待元素超时。3. AI API调用超时。1.添加超时设置在Playwright操作中使用timeout参数例如page.click(selector, timeout10000)。2.使用调试模式将BrowserEngine(headlessFalse)以便观察浏览器行为。3.增加日志在每个关键步骤前后打印日志定位卡住的位置。4.检查异步函数确保正确使用async/await没有在同步函数中调用异步方法。抓取的内容是空或不全1. 页面内容是动态加载的JavaScript渲染。2.get_content的选择器不对。3. 内容在iframe内。1.等待网络空闲在goto后使用await page.wait_for_load_state(networkidle)。2.等待特定元素在获取内容前等待一个代表内容已加载的元素出现。3.处理动态内容对于SPA单页应用可能需要模拟滚动或点击来触发加载。4.检查iframe使用page.frame相关API来访问iframe内的内容。6. 最佳实践与工程建议将AI与浏览器自动化结合用于生产环境需要考虑更多工程化问题。6.1 提示工程优化分步规划对于复杂任务不要让AI一次性规划所有步骤。可以采用“规划-执行-观察-再规划”的循环ReAct模式。让AI先规划几步执行后观察页面状态如获取当前URL、页面关键文本再规划后续步骤。提供示例在系统提示词中提供多个不同场景的、格式完美的输入输出示例Few-shot Learning能极大提高AI输出指令的准确性和稳定性。限制动作集严格定义AI可以使用的动作类型和参数格式避免它生成无法识别的指令。6.2 浏览器引擎的健壮性异常处理在execute_action方法中为每个Playwright操作添加try...except捕获TimeoutError,ElementHandleError等并实现重试或备用方案逻辑。上下文管理使用async with语句管理Playwright对象的生命周期确保资源被正确清理。多页面/多上下文如果任务需要处理多个标签页或隔离会话如多账号合理使用browser.new_context()和context.new_page()。代理与指纹应对反爬机制时可以配置代理、自定义User-Agent、Viewport甚至使用playwright.device模拟特定设备。6.3 性能与成本无头模式生产环境或服务器上运行时务必使用headlessTrue节省资源。并发控制避免同时启动过多浏览器实例消耗大量内存。考虑使用连接池或任务队列。AI API成本LLM的API调用是按Token收费的。优化提示词减少不必要的上下文长度。对于简单的、可预定义的任务可以考虑使用规则引擎而非每次都调用AI。6.4 安全与合规密钥管理绝对不要将API密钥硬编码在代码中。使用环境变量、密钥管理服务或配置文件并加入.gitignore。操作边界明确AI Agent的操作范围。禁止其执行涉及个人信息输入、金融交易、发布内容等高风险操作。在系统层面设置操作白名单。遵守robots.txt尊重目标网站的爬虫协议。你的自动化行为不应给目标网站服务器带来过大压力。人机验证处理如果遇到验证码目前的纯自动化方案很难绕过。需要引入人工干预或专门的验证码处理服务但这可能涉及合规风险需谨慎评估。6.5 可维护性与扩展性配置化将目标网站的URL、关键元素的选择器、AI模型参数等提取到配置文件如config.yaml中。插件化设计将针对不同网站如GitHub、电商平台、新闻站的任务逻辑封装成独立的插件或模块主程序根据任务类型动态加载。状态持久化保存任务执行日志、截图和结果便于回溯和调试。可以考虑集成简单的数据库。监控与告警为长时间运行的任务添加超时监控失败时发送通知如邮件、钉钉、Slack。通过这个项目我们实现了一个能够理解自然语言指令并自动操作浏览器的AI智能体原型。它结合了大语言模型的规划能力和浏览器自动化的执行能力为自动化测试、数据采集、日常办公助手等场景提供了新的思路。当然这只是一个起点。要构建一个真正鲁棒、通用的AI Agent还需要在错误处理、状态管理、长时记忆、工具调用等方面做大量工作。你可以在此基础上尝试集成更多的“工具”比如文件系统操作、数据库查询、调用其他API让AI Agent的能力边界不断扩展。

最新新闻

日新闻

周新闻

月新闻