news 2026/9/9 2:25:01

浏览器自动化实测:LLM Agent与传统脚本,谁更值得投入?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
浏览器自动化实测:LLM Agent与传统脚本,谁更值得投入?

聊浏览器自动化,现在绕不开的话题就是 LLM Agent,也就是让大模型像人一样看页面、点按钮、填表单的那套玩法。过去半年,我把主流的浏览器自动化工具挨个测了一遍,同时也在几个真实项目里跑了跑社区里流行的 Agent 框架。测完之后我的感受有点反直觉:真正值得大多数团队投入的,可能不是“AI 自己操作浏览器”这个方向,而是 Playwright 这种看起来毫无“智能”的传统工具。

这篇文章不是要唱衰 LLM Agent,而是想把我实测里的成本、可靠性、调试体验摊开来算一笔账。如果你正在纠结“要不要上 Agent 做浏览器自动化”,或者想给现有流程找一个更省心的方案,这篇内容应该能帮你少走两三个月的弯路。

1. 为什么 LLM Agent 会火,以及它实测中的真正短板

1.1 LLM Agent 是怎么“操作”浏览器的

先说清楚 LLM Agent 的原理。它和传统脚本最大的区别是:脚本是“人把每一步写死”,而 Agent 是“模型看着页面自己决定下一步”。

典型的工作循环是这样的:模型拿当前页面的截图或者简化后的 DOM 树,判断“现在该干什么”,然后调用工具去执行点击、输入、滚动、跳转,执行完再截一张新图,继续推理。代表性项目有 browser-use、UI-TARS,以及各家大厂出的 Operator、Computer Use 之类。用户的输入不是代码,而是一句自然语言,比如“帮我把这个商品页的价格抓下来存成一个 JSON”。

这个思路确实吸引人。以前写自动化脚本,最烦的就是页面结构一变脚本就废;Agent 号称可以“理解页面语义”,不用写固定选择器,页面改版了它也能自己找到登录框和价格标签。听上去像是把自动化门槛直接砍掉了,这也是它流量暴涨的根本原因。

1.2 账算下来,四个绕不开的现实问题

但我在实际项目里跑通一轮之后,发现理想和现实之间隔着一笔很具体的账。至少四个问题绕不开:

成本不低。模型每走一步就要调用一次接口,带截图的视觉输入比纯文本贵得多。一个五步左右的任务,轻松烧掉几万 token。我做了一个 50 个商品页的价格抓取实验,用视觉类模型跑一遍,光 token 费用就是几美元起步。换成 Playwright 跑,成本几乎为零。如果任务是每天跑一次,这个差距就是天壤之别。

速度撑不住批量场景。一次推理要 1 到 3 秒,五步就是十几秒,中间还有截图、传输、重试。传统脚本操作一个页面通常几百毫秒就结束。做定时任务或者大批量数据采集时,Agent 的慢是没法忍的。

不确定性是硬伤。同一个任务,模型今天跑和明天跑,走的路径可能完全不一样。今天点对了按钮,明天可能点到旁边的推荐位。这种不确定性在开发环境里还能接受,一旦放到生产环境做回归,连断言都没法写,因为你不知道它下一步会干什么。传统脚本虽然“笨”,但它每一步都是确定的,错了就是错了,可以精准定位。

调试和合规很难受。模型决策过程是个黑盒,出错以后你很难复现,只能一遍遍重试看运气。而且页面内容要发给外部模型服务,对企业来说这涉及数据合规,很多内部系统根本不允许页面内容出网。这些在选型阶段容易被忽略,上线时全是坑。

2. 传统工具:Playwright / Selenium / Puppeteer 到底哪家强

2.1 先把工具排个序

传统浏览器自动化工具不止一个,很多人一上来就懵,不知道该学哪个。我按自己的使用经验整理了一张表:

工具支持的浏览器语言生态核心特点最适合的场景
Selenium全部主流浏览器Java/Python/JS 等WebDriver 标准,老牌,生态最全跨浏览器兼容性测试,老项目维护
PuppeteerChromium 系Node.jsDevTools 协议,API 简洁,截图/导出 PDF 强Node 技术栈的爬取、生成截图、PDF
PlaywrightChromium/Firefox/WebKitPython/JS/Java/.NET自动等待、代码生成、Trace 回放现代 Web 自动化与 E2E 测试首选
CypressChromium 系JavaScript运行在浏览器内部,调试体验好前端团队做端到端测试

我的结论很简单:没有历史包袱的新项目直接选 Playwright。Selenium 的优势是语言绑定多、线上资料全,但它的定位偏测试,而且没有自动等待,写脚本要手动 sleep,体验停留在十年前。Puppeteer 只在 Node 生态里好用,抓 Chrome 页面很顺手,但 Firefox 和 WebKit 不支持,做不了全面的跨浏览器验证。Playwright 是微软出的,三浏览器通吃,而且把超时、重试、等待这些脏活都封装好了,是目前综合体验最好的。

2.2 确定性:自动化领域最被低估的品质

传统工具最大的优点,说出来可能有点“土”,就是确定性强。脚本里的每一步都是人明确写好的:先打开这个 URL,等待这个元素出现,在这个输入框里填这个值,点击这个按钮,然后断言页面上出现了什么。每一步都有明确的行为,不会因为模型“今天心情不好”就换个姿势操作。

这种确定性在工程上的价值是巨大的。出错可以复现,定位只需要看日志和截图;跑一百次结果都一样,可以在 CI/CD 里放心做回归;成本几乎是零,不在乎跑多少遍。

用一个生活化的类比:LLM Agent 像是一个聪明但偶尔走神的实习生,你交代一句“去把那张报表导出来”,他大部分时候能干成,但偶尔会点错菜单、看错数据,你还得盯着他。传统脚本则像一张写死的 SOP,每一步都拍成照片贴在墙上,照着做永远不会错,但前提是有人先把 SOP 编好。对于重复执行一万次的任务,你真正需要的不是聪明,是靠谱。

2.3 它并不“难写”:Codegen 和自动等待把门槛拉低了一大截

很多没认真用过的人,对传统工具的印象还停留在“要对着 HTML 手写 XPath,写两天跑一天就废”。这个印象至少过时五年了。

Playwright 自带一个叫 Codegen 的功能,说白了就是录制器。你启动它,它开一个浏览器窗口,你在页面上手动点几下、填几下,代码就自动生成好了。选择器、跳转、输入全都给你写出来,你要做的只是把生成的代码改一改、补上业务逻辑。原来最费时间的“定位元素”环节,现在基本是半自动的。

另外就是自动等待。老式 Selenium 里最痛苦的莫过于“页面还没加载完我就去找元素”,于是大家到处塞time.sleep(2)。Playwright 把这一步变成了内建行为:你定位一个元素准备点击时,它会自动等元素可见、可交互、不被遮挡,全部满足才执行。这相当于把最容易翻车的一环交给了框架处理,脚本写起来自然就稳了。

3. 同一件事,两套方案各走一遍:商品价格监控

3.1 任务设定与验收标准

光说原理有点虚,我拿一个特别常见的场景来实测:监控一批商品页的价格变化。需求是每天凌晨抓取 50 个商品页的标题、价格、库存状态,结果存成 JSON,方便后面做价格走势分析。

验收标准有三条:第一,能稳定跑完 50 个页面不出错;第二,单个页面的数据抓取要准确;第三,跑完一轮的时间和费用要可控。这个任务足够典型,既有固定流程,又涉及动态页面和结构化数据提取,两套方案都能做,但做出来的效果差距很大。

3.2 方案 A:LLM Agent 的实现与成本

用 Agent 实现,代码量确实很小,核心逻辑大概长这样:

# 伪代码:LLM Agent 实现示意 from browser_agent import create_agent agent = create_agent( model="视觉语言模型", instructions="打开商品页,提取标题、价格、库存状态,存成 JSON", ) result = agent.run("https://example.com/products/123")

表面上看,一行指令就完成了。但实际上,模型并不知道“猜到了哪个元素才是价格”,它需要先截图、识别、推理,然后再尝试点击或者读取。我实测下来,一个商品页平均要 5 到 8 次模型调用才能完整拿到数据,单页耗时 8 到 15 秒,还有大概十分之一的概率识别错元素,比如把“参考价”当成了“售价”,你得在 prompt 里反复强调规则或者加后置校验。

成本方面,视觉模型按图像 token 计费,一个页面跑一轮就要几毛钱,50 个页面一次任务下来轻松烧掉几美元。这还只是一天一次的价格监控,如果做实时监控,这个成本直接让人放弃项目。速度和费用一对比,Agent 在这个场景里属于“能实现,但不划算”。

3.3 方案 B:Playwright 脚本 20 行搞定

同一个任务用 Playwright 写,代码也没多到哪里去,而且逻辑是完全可控的:

import json import time from pathlib import Path from playwright.sync_api import sync_playwright URLS = [ "https://example.com/products/one", "https://example.com/products/two", # 这里放 50 个 URL ] def fetch_one(page, url): page.goto(url, wait_until="networkidle", timeout=30000) return { "url": url, "title": page.locator(".product-title").inner_text(), "price": page.locator(".price").inner_text(), "stock": page.locator(".stock-status").inner_text(), "ts": time.strftime("%Y-%m-%d %H:%M:%S"), } def main(): results = [] with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() for url in URLS: try: results.append(fetch_one(page, url)) except Exception as exc: page.screenshot(path=f"error_{len(results)}.png") print(f"[失败] {url}: {exc}") browser.close() Path("results.json").write_text( json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8", ) if __name__ == "__main__": main()

这是完整的可运行脚本,核心抓取函数就十几行。跑 50 个页面,实测用时大约 40 到 60 秒,费用为零,出错时还有截图留存。如果加了重试机制,成功率基本能到 100%,唯一的维护成本是页面改版时更新选择器。

3.4 两边结果对比,差别到底在哪

我把两套方案的关键指标放到一起对比:

指标LLM AgentPlaywright 脚本
50 页耗时8 到 15 分钟1 分钟以内
单次运行成本几美元 token 费用约等于零
结果一致性存在随机误差完全确定
出错定位黑盒,难复现日志 + 截图,秒级定位
代码复杂度很低中等偏低

这里要强调一下,这个对比不是在说“传统工具打败了 AI”。而是在说明一个关键点:任务类型决定方案。价格监控这种“固定流程、重复执行、结果要准”的任务,LLM Agent 的聪明劲根本用不上,反而变成了成本和不确定性的来源。真正适合 Agent 的是另外一类任务:一次性、页面没见过、允许试错,比如老板丢给你一个陌生网站让你快速扒点资料出来。这种场景写脚本的时间成本太高,Agent 的灵活性才有价值。

4. 实操落地:写一个能跑两周不坏的 Playwright 自动化

4.1 环境准备:pip 安装加上浏览器内核

如果你看到这里决定试试 Playwright,环境准备其实特别简单,两条命令:

pip install playwright playwright install chromium

第一条装 Python 库,第二条下载 Chromium 内核。我只装了 Chromium,因为绝大多数目标页面用它就够了;如果做兼容性测试,再补 firefox、webkit 就行。装完之后,把上一节的脚本保存成monitor.pypython monitor.py就能跑。

这里说个经验:playwright install需要访问官方 CDN,建议第一次安装时耐心一点。下载完成后可以用playwright install --dry-run检查哪些浏览器已经就绪。

4.2 先用 Codegen 把页面元素摸清楚

很多人上手第一步就去读网页源码找 class 名,这不是不行,而是效率太低。更好的方式是用 Codegen,一条命令:

playwright codegen https://example.com/products/123

它会弹出一个带检查器的浏览器窗口。你在页面上点击标题、框选价格区域,右侧就会实时生成对应的定位代码,包括 CSS 选择器和get_by_role写法。手动在页面上点几次,一个完整的操作序列就有了。

生成的代码我一般不会直接用,而是拿它当定位元素的参考。重点关注两个点:一是选择器是否稳定,尽量挑有语义的,比如>import time def with_retry(fn, retries=3, base_delay=2): for attempt in range(retries): try: return fn() except Exception: if attempt == retries - 1: raise time.sleep(base_delay * (attempt + 1))

日志方面,不用搞复杂系统,print带时间戳就够了,方便在定时任务里查看每天的执行情况。错误截图我在第三节的代码里已经写了:任何一个 URL 出错,立即对当前页面截图,文件名带上序号。这样第二天早上看任务结果时,不需要登录服务器翻日志,扫一眼截图就能判断是页面改版还是网络问题。

定时任务可以用系统的 crontab 或者 Windows 计划任务,也可以直接在 Python 里写个 while 循环加 sleep。我的习惯是丢到服务器上用 cron,在脚本退出码上下点功夫:成功返回 0,失败返回非 0,这样运维那边能直接告警。

4.4 登录态怎么保持:storage_state

如果你的目标页面需要登录,每次启动都走一遍账号密码流程挺烦的,而且很多系统有验证码,自动登录不一定成功。Playwright 提供了storage_state,可以把登录后的 Cookie 和 localStorage 保存下来复用,相当于把会话“保鲜”起来。

第一次先手动登录一次,然后把状态存下来:

# 第一次:手动登录,保存会话状态 context = browser.new_context() page = context.new_page() page.goto("https://admin.example.com/login") page.fill("#username", "me") page.fill("#password", "******") page.click("button[type=submit]") page.wait_for_selector(".dashboard") context.storage_state(path="state.json")

之后每次运行,直接加载这个状态文件,浏览器就会带着之前的登录态:

context = browser.new_context(storage_state="state.json") page = context.new_page()

这个技巧在抓内部系统、后台管理页面的时候特别管用。需要注意的是,登录态会过期,所以脚本里要留一个判断:如果打开后跳回登录页,就重新走一遍登录流程,再更新 state 文件。这个“自动续期”逻辑加上前面的重试机制,基本就能覆盖绝大多数日常运维场景。

5. 我踩过的坑:常见问题与排查技巧实录

5.1 问题速查表

把我在实际项目中遇到的高频问题整理成了一张速查表,遇到类似情况直接照着排查:

现象可能原因解决办法
定位不到元素前端改版、类名被动态打乱改用>from playwright.sync_api import expect expect(page.locator(".price")).to_have_text("¥5999")

这句断言会一直重试,直到价格文本变成预期的值或者超时。比任何 sleep 都好用,因为它是按真实业务状态来等的。

5.4 关于防抓取检测:先说边界

不少朋友玩自动化,第一反应就是担心“页面会不会检测我是脚本”。这里我必须先把边界说清楚:做自动化前,先看目标网站的 robots 协议和服务条款,个人学习、调试接口没问题,但大规模抓取、绕过访问控制这些事,不管技术上能不能做到,都不能碰。这篇文章讨论的都是正经的自动化测试、内部系统提效、公开数据的小规模采集。

在合规的前提下,如果你遇到的是“无头浏览器被识别”这类技术问题,常用的处理方式包括设置真实的 User-Agent、配置合理的 viewport、偶尔用有头模式运行等。但这些手段都有度,原则是只用于正当用途。

6. 我看到的“另一条路”:让 LLM 做副驾,而不是司机

6.1 核心转变:从“AI 全自动”到“AI 辅助确定性自动化”

测完这一圈,我对“另一条路”的理解越来越清晰:它不是什么革命性新工具,而是把传统浏览器自动化和 LLM 的能力放到正确的位置上。

以前讨论这事,大家默认的框架是“选 AI 还是选脚本”。但实际操作中更优的思路是:让 Playwright 这类工具当主干,负责所有“需要确定执行”的部分;让 LLM 当副驾,负责“需要理解和生成”的部分。这个分工的本质是——浏览器操作的闭环里,有大量环节根本不需要智能,硬塞智能进去只会增加成本和不确定性。

6.2 三种已经能落地的混合形态

如果你认同上面的思路,这里分享三个我自己验证过可行的混合方案:

第一种:LLM 写选择器、修选择器。页面改版后传统脚本最大的维护成本是找新选择器。与其让人肉眼翻 DOM,不如把失效的 HTML 片段丢给模型,让它推荐新选择器,人确认后改上去就行。代码骨架仍然是 Playwright,LLM 只干定位这个具体活。

def repair_selector(broken_selector, html_snippet, target_desc): prompt = f""" 目标元素:{target_desc} 旧选择器:{broken_selector} 已失效。 以下是该元素附近的 HTML 片段: {html_snippet[:2000]} 请只输出一个能定位该元素的 CSS 选择器,不要任何解释。 """ new_selector = call_llm(prompt).strip() return new_selector

第二种:LLM 解析非结构化数据。有些页面里的信息不是规整的表格,而是大段描述性文本,比如商品详情、PDF 说明。传统脚本会把整块文本抓回来,但提取关键字段很费劲。这时可以把文本切好块丢给模型,让它按固定 JSON 结构输出字段,Playwright 负责把文本从页面里捞出来。一个负责捞,一个负责理解,各干各擅长的事。

第三种:LLM 做需求拆解,生成初始脚本。最实用的一个场景:你给模型描述一个自动化需求,它帮你生成 Playwright 脚本骨架,你再在 Codegen 的辅助下微调选择器和业务逻辑。这相当于把“写自动化脚本”这个门槛,从“会写代码”降到了“会描述需求 + 会微调”。你仍然拥有一个确定性的脚本,只是它的第一版是 AI 帮你起草的。

6.3 给不同类型任务的选型建议

每次有人问我“做浏览器自动化到底该用哪个”,我都说先回答一个问题:这个任务要重复执行多少次?

如果答案是“几十次以上”,或者“要定时跑”,那就老老实实上 Playwright。脚本的构建成本是一次性的,重复执行的每一次都是纯赚。这类任务的范围其实很广:定时监控、批量填表、测试回归、数据同步、报表生成,全属于这一类。

如果任务是“一次性、页面没见过、允许试错”,比如临时调研、快速扒点资料,那用 LLM Agent 没问题,它的价值正好在这种非重复劳动上。

还有一种情况是“流程里既有确定性环节,又有开放性环节”——比如先自动登录抓数据,再让模型判断数据有没有异常。这种别把整个流程都交给 Agent,拆开来,确定的部分走脚本,开放的部分交给模型。一条流水线上,普通机器和 AI 各就各位,效率就是最高的。

7. 写在最后:我从这次测试里带走的三件事

测完这一圈,我自己最大的变化是选型时不再被“AI 万能”的氛围带着走。第一件事,是重新认识了“确定性”这三个字的分量——一个跑了五百次都不会跑偏的脚本,比一个聪明但偶尔抽风的操作手可靠太多。第二件事,是明白了成本不光是钱,还有调试时的伤神,传统工具用日志和截图就能定位问题,这种踏实感是黑盒模型给不了的。第三件事,是学会了分工:该确定的事绝不偷懒交给模型,该理解的事也别硬写在脚本里,两者结合的混合方案才是性价比最高的那条路。

最后再分享一个小技巧:如果你刚接触 Playwright,别一上来就追求写“完美脚本”,先用 Codegen 把核心流程录出来跑通,再逐步加重试、登录态、告警。自动化这东西,第一版永远是最丑的,但只要跑起来了,后面就都是打磨的问题了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 2:23:47

鸿蒙应用启动优化实战:冷启动链路拆解与性能调优

兄弟们,这个话题我憋了很久了。每次在群里看到有人发鸿蒙App的启动录屏,要么是点图标后白屏半天,要么是首页框架出来了但数据干等两秒,评论区一群人刷“挤牙膏”;而隔壁组的应用,冷启动直接秒开&#xff0c…

作者头像 李华
网站建设 2026/9/9 2:23:21

STM32 Proteus仿真入门:LCD1602与4×4矩阵键盘驱动模板

简介:基于STM32F103C8T6的Proteus基础模板,围绕LCD1602液晶显示与4乘4矩阵键盘交互,面向初学者快速构建单片机原型。工程由CubeMX初始化,基于HAL库编写驱动,可在Proteus仿真中直接运行。压缩包约6.25MB,共1…

作者头像 李华
网站建设 2026/9/9 2:22:50

鸿蒙Next适配实战:用uts插件实现微信支付全流程

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

作者头像 李华
网站建设 2026/9/9 2:19:20

AI陪练企业选型实测:从销售到客服的落地效果与避坑指南

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

作者头像 李华
网站建设 2026/9/9 2:15:10

微透镜阵列光学仿真实践:Zemax与MATLAB联调实现光场相机与波前传感器

作为常年折腾光学仿真与图像处理联调的人,我看到“基于微透镜阵列的Zemax与MATLAB实现:光场相机、波前传感器及Sensor的应用研究”这个题目,第一反应就是这活儿我熟。微透镜阵列这玩意儿看着就是一片小透镜排排坐,但真要从Zemax建…

作者头像 李华

关于博客

这是一个专注于编程技术分享的极简博客,旨在为开发者提供高质量的技术文章和教程。

订阅更新

输入您的邮箱,获取最新文章更新。

© 2025 极简编程博客. 保留所有权利.