尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Playwright结合AI大模型:用自然语言驱动浏览器自动化的完整方案
把 Playwright 和 AI 大模型接起来是什么体验我最近花了两周时间把“用一句话控制浏览器干活”这件事从想法变成了可以跑的脚本——不用手写 CSS 选择器也不用一帧帧录脚本只要把任务描述给 AI浏览器会自己打开页面、找输入框、点按钮、把结果回传给我。这篇文章就把我的完整思路、核心代码和踩坑记录都交代清楚。想做网页自动化、AI Agent或者单纯对“浏览器机器人”感兴趣的开发者都可以从里面拿到一套能直接跑的方案。这个玩法不是什么魔法。底层仍然是 Playwright 那一套成熟的自动化 APIAI 在里面扮演的是“指挥官”的角色负责把自然语言拆解成计算机能执行的步骤。我试下来最大的感受是稳定性比“让 AI 直接生成自动化代码”高太多这背后其实是一套很明确的设计取舍。1. 自然语言控制浏览器到底在解决什么问题1.1 传统自动化脚本的痛点以及 AI 的切入点先聊一个每个做网页自动化的人都绕不开的痛点业务一变脚本就废。比如要写一个“在电商网站搜索某商品并加购”的脚本你得先打开网站按 F12 找到输入框的 id、按钮的 class再处理各种加载等待和弹窗。页面结构一改选择器失效脚本就得从头排查。维护成本长时间累积下来比最初写脚本的成本还高。AI 解决的并不是“点击按钮”这个动作本身而是“如何把自然语言任务翻译成浏览器操作序列”。换句话说执行层仍然用 Playwright 的高质量 APIAI 只做决策层。比如“帮我查一下今天上海到北京的高铁”这句话AI 需要拆解成先打开 12306找到出发地输入框填入上海找到目的地输入框填入北京选择今天日期点击查询按钮最后读取结果列表。这些动作拆解能力是大模型的强项而“稳定点击”是 Playwright 的强项。两者一拍即合。这也是 Midscene、browser-use 等项目的核心思路。它们没有让 AI 直接写 Python 代码而是让 AI 输出结构化的操作指令再由 Playwright 去执行。这样做的好处非常明显每一步动作都经过严格校验不用依赖模型生成的随机代码。1.2 为什么选 Playwright而不是裸写 CDP你可能已经听说过 Chrome DevTools Protocol也就是 CDP它是浏览器调试的底层协议功能很强但裸写 CDP 的体验有点像直接拿汇编语言写界面。Playwright 在 CDP 之上封装了一层稳定、友好的 API是自然语言控制浏览器最合适的底座。对比维度Playwright裸写 CDP传统 Selenium元素定位支持角色、文本、测试ID语义化定位需要自己处理 Runtime.evaluate很原始主要靠 CSS/XPath脆弱自动等待内置动作等待机制自己实现等待逻辑有显式等待但灵活度一般多页面/弹窗处理Page/Context 模型清晰手动管理 target 事件EventContext 较繁琐可访问性快照原生accessibility.snapshot()自己调用 Accessibility domain支持较弱浏览器隔离上下文天然隔离需要手动配置 profile靠 options 折腾其中最关键的一点是accessibility.snapshot()。它能拿到浏览器为读屏器准备的语义结构树也就是可访问性快照。这棵树上包含按钮、输入框、链接、标题等元素的 role 和 name相当于把复杂页面压缩成一个“语义版视图”。AI 拿到这份快照就不需要猜测一堆残缺的 CSS 选择器了。1.3 一条自然语言任务是怎么变成浏览器操作的我这里用的流程是一个受控的多轮循环拆开来看其实很直白打开浏览器并进入目标页面获取当前页面的可访问性快照。把快照转成紧凑文本连同用户任务、历史动作结果一起发给大模型。大模型返回一个受 JSON 格式约束的动作比如{action:click,params:{node_id:12}}。Playwright 解析这个动作根据node_id找到真实元素并执行。检查任务是否完成如果没完成回到第 1 步继续循环直到模型返回done。这个循环看起来简单但有一点值得强调AI 每一轮看到的“页面状态”都是全新的。点击按钮之后页面跳转了下一轮快照自然就变成了新页面的内容模型不需要硬背旧状态。这种方式既降低了模型的记忆负担也减少了很多状态错乱的问题。2. 核心原理拆解AI 怎么“看到”页面并决定动作2.1 可访问性快照给 AI 看一个“语义版”页面为什么不直接给 AI 传 HTML因为整个 DOM 树又长又杂充满布局节点、脚本节点、隐藏的装饰性元素直接发给大模型非常浪费 token而且模型很容易被无关内容干扰。可访问性快照就不一样了它只保留对用户有意义的结构比如“按钮登录”“文本输入框手机号”“链接帮助中心”。我用 Playwright 拿快照非常快from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com) snap page.accessibility.snapshot(interesting_onlyTrue) print(snap)不过原始快照是嵌套的 Python 字典或 JSON模型直接读它还是不够方便。我一般会把它拍平成带编号的文本方便模型引用def flatten_a11y(node, counter, locators): lines [] role node.get(role) name node.get(name) if role and (name or node.get(value)): key f{role}|{name} counter[key] counter.get(key, 0) 1 node_id len(locators) value node.get(value, ) extra f value{value} if value else lines.append(f[{node_id}] {role}: {name}{extra} (same_key{counter[key]})) locators.append({ role: role, name: name, index: counter[key] }) for child in node.get(children, []): lines.extend(flatten_a11y(child, counter, locators)) return lines这样模型看到的页面状态长这样[0] navigation: 主导航 [1] link: 首页 (same_key1) [2] textbox: 搜索关键词 (same_key1) [3] button: 搜索 (same_key1) [4] textbox: 手机号 (same_key1) [5] button: 提交 (same_key1)同一类link可能有多个所以我在后面加了一个(same_keyN)表示这是同名节点中的第几个执行时用.nth(N-1)定位避免歧义。这套做法让模型只需要选一个数字而不是编选择器。2.2 动作收敛让 AI 做选择题而不是写代码这是整个方案里最重要的一条经验永远不要让 AI 自由生成page.locator(xxx).click()这样的代码。模型一旦开始写 Python语法错误、选择器幻觉、元素不存在、方法名拼错各种问题都会冒出来。我调试过几次之后彻底放弃了这个方向。正确的做法是给 AI 定义一套很小的动作集合把它逼成“选择题”动作参数用途gotourl打开新页面clicknode_id点击某个可访问节点fillnode_id,value在输入框填内容pressnode_id,key按键比如 Enterextract无提取当前页面正文文本doneresult标记任务完成并返回最终结果动作集越小模型的出错空间越小。而且这套动作在大多数网页上已经够用。真正复杂的操作比如拖拽、上传文件我也只会慢慢往里加“受控动作”不会放开让 AI 自己发挥。2.3 多轮循环与状态记忆AI 的短期工作记忆大模型本身没有真正常驻的“页面状态”它每轮看到的是我发过去的最新快照和历史动作结果。所以我的消息结构是system定义浏览器助手的角色和 JSON 输出规则。user包含“用户任务 当前页面状态 历史动作结果”。这样 AI 在同一轮里就能知道“我刚才填了关键词页面结果已经加载出来了现在应该点第一个结果链接。” 历史记录不需要很长我一般只保留最近 3-5 步太长的历史会稀释模型对当前页面的注意力。这和写自动化测试用例时的思路很像断言不是越多越好而是在关键路径上设置足够的节点。2.4 再往前走一步Function Calling 和 MCP我最初实现的是用提示词硬约束 JSON 输出这种方案兼容任何大模型。后来我换到支持 Function Calling 的接口体验又顺了一点模型可以在 API 层选择调用哪个工具不需要我在 prompt 里反复强调 JSON 格式。本质上思路完全一样只是工程上更规范。另外现在社区里流行的 MCP 也走了类似路径。Playwright MCP Server 会把浏览器操作包装成可供 AI 调用的工具Claude 这类客户端通过 MCP 协议调用它们就能操作浏览器。你可以把 MCP 理解成“AI 时代的 USB 接口”而 function calling 是这个接口的底层协议之一。如果你想做更通用的 Agent直接研究 MCP 生态是顺势而为。3. 实操从零搭一个 Playwright AI 浏览器助手3.1 环境准备装什么、怎么装最省心我的示例环境是 Python 3.10配 Chrome/Chromium。先安装依赖pip install playwright openai playwright install chromium这里有个小提醒playwright install chromium下载的是 Playwright 自己管理的 Chromium不是你在电脑上日常用的 Chrome两者不冲突。如果你电脑里已经有可用的 Chrome 或 Edge也可以不执行这行命令直接在代码里指定可执行文件路径这样能省一次下载。我后面的代码用 OpenAI SDK 调用大模型但如果你想接本地模型比如 Qwen、DeepSeek 这类兼容 OpenAI 接口的服务只需要改base_url和api_key代码框架完全不用动。3.2 核心代码快照生成、动作执行与控制循环先把基础工具函数补齐。首先是动作执行器它负责把 JSON 动作真正变成 Playwright 调用from playwright.sync_api import sync_playwright from openai import OpenAI import json, os client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL, https://api.openai.com/v1), ) MODEL os.getenv(LLM_MODEL, gpt-4o-mini) def get_state(page): snap page.accessibility.snapshot(interesting_onlyTrue) if not snap: return 页面无可用可交互元素, [] counter {} locators [] lines flatten_a11y(snap, counter, locators) return \n.join(lines), locators def resolve_locator(page, locator_info): loc page.get_by_role(locator_info[role], namelocator_info[name]) return loc.nth(locator_info[index] - 1) def execute_action(page, action, locators): act action.get(action) params action.get(params, {}) try: if act goto: page.goto(params[url], wait_untildomcontentloaded) return goto ok, True if act click: info locators[params[node_id]] resolve_locator(page, info).click() return click ok, True if act fill: info locators[params[node_id]] resolve_locator(page, info).fill(params[value]) return fill ok, True if act press: info locators[params[node_id]] resolve_locator(page, info).press(params[key]) return press ok, True if act extract: txt page.locator(body).inner_text() return txt[:2000], True return funknown action {act}, False except Exception as e: return f执行失败: {e}, False然后需要把模型输出里的 Markdown 代码块去掉防止json.loads崩掉def clean_json(text): text text.strip() if text.startswith(): lines text.split(\n)[1:] if lines and lines[-1].strip() : lines lines[:-1] text \n.join(lines) start text.find({) end text.rfind(}) if start ! -1 and end ! -1: return text[start:end1] return text最后是控制循环。这一步相当于把整个任务拆成一个“感知-决策-执行”的循环def run_task(task, start_urlNone, max_steps15): with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(start_url if start_url else about:blank) history [] completed False for step in range(max_steps): state, locators get_state(page) user_content ( f任务{task}\n\n f当前页面状态\n{state}\n\n f已执行历史{history}\n\n f请输出下一步 JSON 动作。 ) resp client.chat.completions.create( modelMODEL, temperature0, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_content}, ], ) raw resp.choices[0].message.content try: action json.loads(clean_json(raw)) except Exception: history.append(fstep{step}: JSON 解析失败 {raw}) continue if action[action] done: print(任务完成, action[params].get(result)) completed True break result, ok execute_action(page, action, locators) history.append(fstep{step}: {action} {result}) page.wait_for_timeout(800) if not completed: print(到达最大步数任务未完成) browser.close()控制循环里我没有把每一步的完整页面快照存进历史只存了动作和结果避免 token 爆炸。后续如果任务太复杂可以在历史里保存“上一轮动作执行后的短摘要”效果会更明显。3.3 完整演示让浏览器自己搜索并点击第一条结果我拿一个最常用的场景来跑在百度搜索“Playwright 最新版本”然后点击第一条搜索结果。任务描述会直接写进 user_content任务打开百度搜索 Playwright 最新版本然后点击第一条搜索结果。模型在循环里大概会输出这样的动作序列示意goto- https://www.baidu.comfill- node_id 对应搜索输入框value 为“Playwright 最新版本”click- node_id 对应“百度一下”按钮或者直接pressEnter等待页面跳转后新快照会显示搜索结果列表click- node_id 对应第一个结果链接done- result 里返回当前页面标题或 URL这个流程里最关键的一步是第 4 步搜索结果页加载完成后快照里会突然出现很多link节点。AI 要能识别出“第一条结果”到底是第几个link所以我在快照文本里对同名节点做了编号。实测下来只要页面语义结构正常模型选对的概率非常高。3.4 提示词和参数调优哪里有坑Prompt 决定了整个系统的下限我试过各种写法最后沉淀出一个比较稳的系统提示词你是一个浏览器操作助手。你只能输出一个 JSON 对象不要输出任何解释、Markdown 或代码块。 可选 action: - goto: {action:goto,params:{url:...}} - click: {action:click,params:{node_id:整数}} - fill: {action:fill,params:{node_id:整数,value:...}} - press: {action:press,params:{node_id:整数,key:Enter}} - extract: {action:extract,params:{}} - done: {action:done,params:{result:最终回答}} 页面状态里每一行是一个可交互节点格式为 [node_id] role: name (same_keyN)表示同名节点中的第 N 个。 你只选择 node_id 来操作不要编造选择器。若任务已完成返回 done。几个调优经验temperature0是必须的。浏览器操作是确定性任务不需要“创意”。任务描述尽量带验证点。比如“点击标题为 X 的链接”比“点击一个看起来像结果的链接”靠谱得多。设置max_steps防止死循环。我见过模型在一个失败动作上反复重试如果没步数上限它会一直在原地转圈。页面状态文本如果太长可以先截断到 1500 字符左右优先保留按钮、链接、输入框等可交互节点减少干扰。4. 常见问题与排查技巧实录4.1 模型输出非法 JSON 怎么办这是我遇到的第一个大坑。很多大模型即使你反复强调“只输出 JSON”它还是会给你一段带 Markdown 的代码块甚至开头写一句“好的以下是下一步动作”。直接json.loads就崩了。我的处理分三层先用clean_json去掉代码块、提取首尾花括号。如果解析成功但动作不在白名单里返回错误信息给模型让它重新输出。如果连续三次解析失败就停止循环并打印上下文方便排查。说到底这是模型服务本身的不稳定因素。越强的模型越遵守指令但如果用本地小模型就要容忍一定比例的格式错误把重试机制写好。4.2 元素定位不稳定同名节点和自定义控件页面状态里同名节点很常见比如导航栏一堆“link: 首页”可能不同位置出现。我用same_key计数和.nth()解决了一部分但还有一类问题更难办很多现代前端控件不是原生 button而是div加了onclick在可访问性快照里可能只有generic角色AI 不知道它能不能点。我的兜底思路是如果快照里找不到合适的可交互节点可以先让 AI 执行extract拿到页面正文文本然后根据正文内容判断下一步。更彻底的办法是把可访问性快照和常见可点击元素信息合并在图谱上补充>
RELATED

相关推荐

OpenShell:统一Shell配置管理与跨平台命令行增强实战

OpenShell:统一Shell配置管理与跨平台命令行增强实战

在终端里泡了十几年,shell 始终是每天点击量最高的窗口。从最早的 Bash 一路用到 Zsh、Fish,再到各种框架和插件,说实话,工具越装越多,真正能沉淀下来的配置和经验反而越来越少。这几年我一直在用一个叫 OpenShell 的开…

📅 2026/10/3 15:12:06
CST Studio Suite许可证安装故障全解析

CST Studio Suite许可证安装故障全解析

1. 为什么CST Studio Suite安装总卡在许可证这一步?——一个电磁仿真工程师的十年踩坑实录CST Studio Suite,这个在微波天线、高速PCB信号完整性、EMC预兼容测试领域几乎无人不晓的电磁场仿真旗舰软件,它的安装过程远比“双击setup.exe→下一…

📅 2026/10/3 15:12:06
自然语言控制浏览器:用Playwright与AI大模型实现自动化操作

自然语言控制浏览器:用Playwright与AI大模型实现自动化操作

说实话,做测试开发这么些年,写浏览器自动化脚本早就是家常便饭了。但每次接到一个新项目的UI用例,打开页面看到那一堆密密麻麻的xpath、css selector,还是忍不住头大。直到我最近把Playwright和AI大模型串在一起,用自然…

📅 2026/10/3 15:12:06
MORE NEWS

更多资讯

📰

2025 AI安全供应链风险与TRiSM治理落地指南

简介:《Gartner预测2025:应对AI驱动的网络安全新挑战与70%恶意攻击来自供应链和技术栈中毒》是一份AI安全趋势解读资料,面向网络安全决策者、AI治理与供应链安全从业者。资料为单个PDF文件,压缩包约215KB,内容精炼便于…

📰

GitHub日榜实战:AI Agent与本地推理项目筛选与避坑指南

1. 日榜速报到底在追什么:先搞清楚这份榜单的筛选逻辑 很多人第一次看 GitHub 日榜,第一反应是"这不就是个按 star 排序的列表吗"。真上手追一段时间就会发现,日榜和总榜完全是两码事。总榜拼的是历史积累,一个项目攒了…

📰

LDPC编解码器设计与仿真全流程解析:从原理到硬件验证

做LDPC编解码器设计和仿真这件事,我前前后后折腾了大半年,从最初的原理推导到最终的硬件验证,踩了不少坑,也积累了一些比较靠谱的经验。通信圈的朋友应该都知道,LDPC(低密度奇偶校验码)这几年基…

📰

RTC实时时钟实验全解析:从DS1302原理到掉电保持与调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

山东大学软件学院人工智能导论:名词解释与简答题高效整理指南

简介:这份资料面向软件工程、人工智能等专业学生及备考人工智能导论的学习者,围绕《人工智能导论》第五版的核心考点,系统整理了简答题与名词解释,帮助读者梳理基础概念、应对课程考核与复习。内容覆盖逻辑思维、形象思维与顿悟思…

📰

软件项目风险回避措施:从“不做清单”到风险台账落地

简介:软件项目风险回避措施.docx 聚焦软件项目开发中的风险识别、评估与应对,适合项目管理者、软件开发人员及质量保障人员参考。内容从软件管理与软件体系结构两条主线展开,前者覆盖工期、需求调研、实现技术、质量体系等风险,后…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬