当前位置: 首页 > news >正文

#24 Agent 的浏览器自动化:Playwright、Selenium 与网页交互

从一次凌晨三点的事故说起

去年冬天,我负责的一个自动化脚本在凌晨三点突然崩了。日志里只有一行:ElementClickInterceptedException。点一个“确认”按钮,被一个弹窗遮住了。Selenium 的WebDriverWait等了十秒,弹窗刚好在点击前0.5秒消失——经典的竞态条件。更糟的是,这个脚本跑在无头模式下,我连截图都没开。

那天晚上我意识到:浏览器自动化不是“写个脚本让它跑”那么简单。当你的 Agent 需要像人一样操作网页——填表单、抓数据、做断言——你面对的不是一个静态 DOM,而是一个活生生的、会异步渲染、会弹广告、会突然卡住的浏览器。

这篇文章不讲理论,只讲我在生产环境里踩过的坑,以及 Playwright 和 Selenium 到底该怎么选、怎么用。

选型:别只看 Star 数

先说结论:2026 年,新项目无脑选 Playwright。不是 Selenium 不好,是 Playwright 解决了太多 Selenium 时代需要自己手搓的痛点。

Selenium 的架构决定了它是个“遥控器”——你通过 WebDriver 协议告诉浏览器做什么,浏览器做完再告诉你。这个模型本身没问题,但跨进程通信的延迟、不同浏览器驱动的兼容性、以及那些诡异的StaleElementReferenceException,会让你在调试时想砸键盘。

Playwright 走的是 CDP(Chrome DevTools Protocol)通道,相当于直接跟浏览器内核对话。这意味着:

  • 等待网络空闲、等待元素可见、等待请求完成——这些在 Selenium 里需要自己写轮询的逻辑,Playwright 内置了。
  • 自动等待。这是 Playwright 最值钱的设计。你写page.click('#submit'),它不会立刻点,而是等元素稳定、可见、不被遮挡、没有动画——然后才点。Selenium 的ElementClickInterceptedException在 Playwright 里几乎绝迹。
  • 多浏览器支持。Chromium、Firefox、WebKit 一套 API,不用像 Selenium 那样为每个浏览器装不同的 driver。

但 Selenium 也有它的生态优势:老项目、遗留系统、某些只支持 Selenium 的云测试平台。如果你维护的是一个跑了五年的自动化框架,迁移成本可能高于收益。

Playwright 实战:让 Agent 像人一样操作网页

安装与启动

# 别用 pip install playwright 就完事,还要装浏览器pip install playwright playwright install chromium# 这里踩过坑:只装库不装浏览器,运行时报错

启动浏览器时,我习惯这样写:

fromplaywright.sync_apiimportsync_playwrightwithsync_playwright()asp:browser=p.chromium.launch(headless=False,slow_mo=500)# slow_mo 是神器,调试时加500ms延迟,看得清每一步page=browser.new_page()page.goto("https://example.com")

slow_mo这个参数,调试阶段一定要开。它让每一步操作之间插入固定延迟,不是用来解决竞态的,是用来让你肉眼能跟上脚本节奏的。生产环境关掉。

等待:别再用 time.sleep()

新手最爱写time.sleep(3),这是最粗暴也最不可靠的方式。网络慢一秒脚本就崩,网络快一秒白白浪费两秒。

Playwright 的等待策略:

# 等待元素可见page.wait_for_selector("#result",state="visible",timeout=10000)# 等待网络空闲(适合页面加载完后有异步请求的场景)page.wait_for_load_state("networkidle")# 等待自定义条件page.wait_for_function("() => document.title === '完成'")

这里有个坑:networkidle不是万能的。有些页面会持续发送心跳请求,导致networkidle永远等不到。这时候用domcontentloaded加一个合理的超时。

元素定位:XPath 还是 CSS?

我个人的经验:能用 CSS 选择器就别用 XPath。CSS 选择器更简洁、性能更好、可读性更强。但有些场景 XPath 无可替代——比如根据文本内容定位:

# CSS 做不到根据文本找按钮page.click("text=提交订单")# Playwright 内置的文本选择器,比 XPath 好写# 或者用 XPath 定位包含特定文本的元素page.locator("//button[contains(text(), '确认')]").click()

Playwright 的text=选择器是个好东西,它自动处理了文本匹配的模糊性。但注意:如果页面上有两个“提交订单”,它会报错说找到了多个元素。这时候用firstlastnth来限定:

page.locator("text=提交订单").first.click()

表单填写:模拟真实输入

很多自动化脚本填表单时直接fill(),但有些前端框架(比如 React)会监听input事件,fill()可能触发不了。这时候用type()模拟逐字输入:

# 别这样写:page.fill("#username", "admin")# 这样写更安全:page.type("#username","admin",delay=50)# delay 模拟打字间隔,50ms 一个字符

delay参数在对付那些有防机器人检测的页面时特别有用。有些网站会检测输入速度,如果瞬间填完就判定为脚本。

处理弹窗和对话框

# 监听对话框,自动确认page.on("dialog",lambdadialog:dialog.accept())# 或者处理特定对话框page.on("dialog",lambdadialog:print(f"对话框内容:{dialog.message}"))

这里踩过坑:page.on注册的监听器是全局的,如果页面弹出多个对话框,监听器会依次触发。但如果你在某个操作之后才注册监听器,之前的对话框就没人处理,脚本会卡住。所以最好在goto之前就注册好。

截图和录屏:调试的救命稻草

# 截图page.screenshot(path="debug.png",full_page=True)# 录屏(Chromium only)context=browser.new_context(record_video_dir="videos/")page=context.new_page()# ... 操作 ...context.close()# 关闭 context 才会保存视频

生产环境里,我习惯在每个关键步骤后截图,并保存到带时间戳的文件里。出问题时,翻截图比翻日志快十倍。

Selenium 的遗留战场

虽然我推荐 Playwright,但 Selenium 在某些场景下依然有它的位置。

什么时候不得不用 Selenium

  • 老项目维护:你接手的自动化框架是用 Selenium 写的,重写成本太高。
  • 特定浏览器支持:某些企业内网只能用 IE 或旧版 Edge,Playwright 不支持。
  • 云测试平台:BrowserStack、Sauce Labs 对 Selenium 的支持更成熟。

Selenium 的坑与填坑

fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasEC driver=webdriver.Chrome()wait=WebDriverWait(driver,10)# 经典写法:等元素可点击element=wait.until(EC.element_to_be_clickable((By.ID,"submit")))element.click()

这个写法看起来没问题,但实际运行中element_to_be_clickable只检查元素是否可见和启用,不检查是否被其他元素遮挡。所以ElementClickInterceptedException依然会出现。

我的解决方案:自己写一个等待条件,用 JavaScript 检查元素是否被遮挡:

defis_element_clickable(driver,locator):element=driver.find_element(*locator)# 别这样写:直接用 element.click() 会抛异常# 用 JS 检查returndriver.execute_script(""" var rect = arguments[0].getBoundingClientRect(); var x = rect.left + rect.width/2; var y = rect.top + rect.height/2; var topElement = document.elementFromPoint(x, y); return arguments[0].contains(topElement) || arguments[0] === topElement; """,element)wait.until(lambdad:is_element_clickable(d,(By.ID,"submit")))

这个脚本检查点击坐标处最上层的元素是不是目标元素本身或其子元素。如果是,说明没被遮挡。

Selenium 的隐式等待 vs 显式等待

# 隐式等待:全局设置,所有 find_element 都等driver.implicitly_wait(10)# 显式等待:只针对特定元素wait.until(EC.presence_of_element_located((By.ID,"result")))

别混用。隐式等待和显式等待混用会导致不可预测的等待时间。我见过一个项目,隐式等待设了 10 秒,显式等待又设了 10 秒,结果某些元素等了 20 秒才超时。要么全用显式,要么全用隐式——我选显式,因为更可控。

Agent 与浏览器的交互模式

当你的 Agent 需要操作浏览器时,不是简单地“打开网页-点击-获取数据”。Agent 需要理解网页内容、做出决策、执行操作、验证结果。这涉及到几个关键点:

上下文管理

Agent 每次操作都应该知道当前页面状态。我习惯用一个BrowserContext类来封装:

classBrowserContext:def__init__(self):self.page=Noneself.history=[]# 操作历史self.screenshots=[]# 截图记录defnavigate(self,url):self.page.goto(url)self.history.append(f"导航到:{url}")self.screenshots.append(self.page.screenshot())defclick(self,selector):self.page.click(selector)self.history.append(f"点击:{selector}")self.screenshots.append(self.page.screenshot())

这样 Agent 可以回溯操作历史,甚至在出错时回滚到某个状态。

错误恢复

网页自动化最烦人的就是“预期之外的状态”。弹窗、网络超时、元素加载失败——Agent 需要能处理这些异常。

defsafe_click(page,selector,retries=3):foriinrange(retries):try:page.click(selector,timeout=5000)returnTrueexceptExceptionase:print(f"第{i+1}次点击失败:{e}")ifi<retries-1:page.wait_for_timeout(1000)# 等一秒再重试returnFalse

这个简单的重试机制能解决 80% 的偶发问题。剩下的 20% 需要更复杂的策略——比如刷新页面、切换标签页、或者直接放弃当前操作。

数据提取与验证

Agent 操作网页的最终目的是获取数据或完成某个任务。提取数据时,别只依赖一种方式:

# 先尝试结构化提取data=page.evaluate(""" () => { const rows = document.querySelectorAll('table tr'); return Array.from(rows).map(row => { const cells = row.querySelectorAll('td'); return Array.from(cells).map(cell => cell.textContent.trim()); }); } """)# 如果结构化提取失败,用文本匹配ifnotdata:text=page.text_content("body")# 用正则或 LLM 提取

这里有个经验:能用 JS 提取就别用 Python 解析。在浏览器端执行 JS 提取数据,比把整个 HTML 传给 Python 再解析快一个数量级。

性能优化:别让浏览器成为瓶颈

无头模式

生产环境一定要用无头模式。Playwright 默认就是无头,但 Selenium 需要显式设置:

# Playwrightbrowser=p.chromium.launch(headless=True)# Seleniumoptions=webdriver.ChromeOptions()options.add_argument('--headless')driver=webdriver.Chrome(options=options)

复用浏览器实例

每次启动浏览器都很慢。如果 Agent 需要频繁操作,复用浏览器实例:

# Playwright 的 browser context 可以隔离会话context=browser.new_context()page1=context.new_page()page2=context.new_page()# 同一个 context 共享 cookies 和存储# 不同 context 完全隔离context2=browser.new_context()

资源释放

# 别忘记关闭page.close()browser.close()

忘记关闭浏览器会导致内存泄漏。在长时间运行的 Agent 里,我习惯用try...finally确保资源释放,或者用上下文管理器。

个人经验:别把自动化当黑盒

写浏览器自动化脚本最忌讳的就是“写完了就不管了”。网页是会变的——前端重构、第三方库升级、甚至只是某个 CDN 挂了,你的脚本就可能崩。

我的建议:

  1. 每个关键操作后截图。截图不占多少空间,但出问题时能帮你快速定位是哪个步骤出了问题。
  2. 记录操作日志。不只是“点击了按钮”,还要记录点击前后的页面状态、元素属性、网络请求。
  3. 定期运行测试。哪怕只是每周跑一次,也能提前发现页面变化。
  4. 不要过度依赖等待时间time.sleep(5)这种写法迟早会坑你。用 Playwright 的自动等待,或者自己写基于条件的等待。
  5. 考虑用 LLM 辅助定位元素。当页面结构频繁变化时,用 LLM 根据视觉或语义描述来定位元素,比硬编码选择器更鲁棒。但这又是另一个话题了。

最后说一句:浏览器自动化不是银弹。如果你的目标网站有强反爬机制(比如验证码、行为检测、IP 频率限制),自动化脚本可能跑不过十分钟。这时候要考虑的是“能不能换个方式获取数据”——比如找 API、买数据、或者跟网站所有者合作。

技术解决不了所有问题,但能解决大部分。剩下的,靠经验和运气。

http://www.jsqmd.com/news/782634/

相关文章:

  • 既要又要还要?噬菌体展示如何帮助科学家“钓”出完美的抗毒素抗体?
  • 智慧云柜生产厂家推荐-聚澜智能 - 聚澜智能
  • 初次使用大模型API的新手如何通过模型广场快速选择合适的模型
  • 领嵌iLeadE-588AI计算盒子数据采集视频分析一键部署
  • 2026年唐山幕墙清洗与烟道保洁专业服务商深度对标指南 - 企业名录优选推荐
  • 小红书内容采集终极指南:XHS-Downloader 5分钟快速上手
  • 【Pocket Flow】源码剖析(二):批量与异步——BatchNode、AsyncNode 与并行执行
  • 2025-2026年国内主流电竞鼠标品牌推荐:十大口碑产品评测日常使用防握持不适 - 品牌推荐
  • 桶排序、堆排序、奇偶排序、计数排序、阿坤老师的独特瓷器、封闭图形个数、二进制王国【算法赛】
  • Python 爬虫反爬突破:人机验证机制底层破解
  • CANN/sip BLAS向量复制操作
  • cann/ascend-transformer-boost编译与构建
  • 基于 OpenCV + OpenGL 的三维重建代码实现
  • Video DownloadHelper CoApp终极指南:轻松下载网络视频的完整教程
  • 企业级即时通讯「删除消息」:六个场景叠加之后,复杂性超出你的想象
  • # 026 Agent 的文件处理:PDF、Excel、图片、音频的解析与生成
  • 2026年唐山烟道清洗、外墙保洁与商业保洁一站式解决方案深度指南 - 企业名录优选推荐
  • 免费文本挖掘神器KH Coder:三步掌握多语言内容分析技巧
  • 项目改造为 Docker 容器使用指南
  • 不想打工开茶店,预算30万小成本中端预算创业,加盟岩茶品牌哪个不踩坑新手小白全程带教白皮书——以溪谷留香为基准样本的深度决策指南 - 商业科技观察
  • 模型广场功能如何帮助开发者根据任务特性选择合适模型
  • Seraphine:英雄联盟终极智能辅助工具完整指南 - 提升排位胜率的秘密武器
  • PUBG罗技鼠标宏压枪脚本架构揭秘:精准射击的自动化实现方案
  • Java并发编程:从基础到实战的技术探索
  • 性价比高的芯片老化座哪家公司好?
  • Atom编辑器终极中文汉化指南:告别英文困扰,轻松打造专属编程环境
  • 5分钟搭建专业级拼多多数据采集系统:电商运营的终极利器
  • 证书链技术与ADAC安全调试协议详解
  • 2026年唐山烟道清洗与外墙保洁一体化解决方案深度横评 - 企业名录优选推荐
  • FPGA开发实战:Verilog模块库pConst/basic_verilog深度解析与应用指南