实战:使用 Stagehand + Qwen (通义千问) 实现智能化浏览器自动化
什么是 Stagehand?
Stagehand 是一个专为 AI 时代设计的浏览器自动化 SDK。它建立在强大的 Playwright 之上,但彻底改变了我们与浏览器交互的方式。
传统的自动化工具(如 Selenium 或原生 Playwright)要求开发者像“工匠”一样,精确地指定每一个操作的坐标或元素选择器(如 div > .btn-primary)。一旦网页结构微调,脚本往往就会失效。
Stagehand 则让开发者变成了“指挥官”。 你不再需要关心底层的 DOM 结构,只需用自然语言下达指令(例如“点击注册按钮”或“提取所有商品价格”),Stagehand 就会利用大语言模型(LLM)的视觉和语义理解能力,自动分析页面并执行操作。它主要提供了三个核心能力:
- Act (行动): 执行具体操作(如点击、输入、滚动)。
- Extract (提取): 从页面中智能提取结构化数据。
- Observe (观察): 分析页面当前状态,为后续决策提供依据。
在浏览器自动化领域,Playwright 和 Selenium 是我们熟悉的工具,但它们通常需要精确的选择器(Selectors)和硬编码的逻辑。如果能让脚本“看懂”页面并根据自然语言指令行动呢?
本文将基于一段实战代码 test_stagehand_qwen.py,深入解析如何利用 Stagehand 框架结合阿里云 Qwen (通义千问) 大模型,构建一个能够理解自然语言指令的自动化脚本。
1. 快速开始
1.1 安装依赖
在使用本技术之前,你需要安装 Stagehand 的 Python SDK 以及 Playwright 和环境变量管理工具。
打开终端,运行以下命令:
# 安装核心依赖
pip install stagehand playwright python-dotenv
# 安装 Playwright 浏览器驱动
playwright install
1.2 准备工作
- 获取 API Key: 确保你拥有阿里云 DashScope 的 API Key(用于调用 Qwen 模型)。
- 配置环境: 在项目根目录创建
.env文件,填入你的 API Key:DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
2. 核心技术栈
- Stagehand: 一个旨在让 AI 驱动浏览器自动化的 SDK,它在 Playwright 之上提供了更高级的抽象(
act,extract,observe)。 - Qwen (通义千问): 阿里云强大的大语言模型,这里我们使用的是
qwen3-max-preview,通过 DashScope API 调用。 - Python Asyncio: 用于处理异步浏览器操作。
3. 代码深度解析
3.1 环境配置与模型集成
传统的自动化脚本通常难以适配不同的 LLM,但 Stagehand 提供了灵活的配置接口。代码首先配置了 DashScope(灵积模型服务)作为 AI 后端。
import os
from stagehand import Stagehand, StagehandConfig
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
async def main():
# 设置 DashScope API Base 地址
os.environ['DASHSCOPE_API_BASE'] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
config = StagehandConfig(
env="LOCAL",
# 指定使用 Qwen 模型
modelName="dashscope/qwen3-max-preview",
modelApiKey=os.getenv("DASHSCOPE_API_KEY"),
# ... 其他配置
)
关键点:
- 自定义模型:通过
modelName="dashscope/qwen3-max-preview",我们指示 Stagehand 使用兼容 OpenAI 协议的 Qwen 模型。 - API Base:设置
DASHSCOPE_API_BASE指向阿里云的兼容接口,这是集成非 OpenAI 官方模型的关键技巧。
3.2 浏览器与调试参数调优
为了保证自动化过程的稳定性和可观测性,配置中包含了大量细节调优:
config = StagehandConfig(
# ... 基础配置
headless=True, # 无头模式运行
verbose=3, # 详细日志级别
debug_dom=True, # 启用 DOM 调试,帮助 AI 理解页面结构
dom_settle_timeout_ms=60000, # 增加 DOM 稳定超时时间,应对动态加载页面
wait_for_network_idle_ms=5000, # 等待网络空闲
local_browser_launch_options={
"slow_mo": 100, # 慢动作模式,便于人眼观察每一步操作
"devtools": True, # 开启开发者工具
"args": [
"--window-size=1920,1080",
"--disable-web-security", # 解决跨域等安全限制问题
]
}
)
这些参数对于调试 AI 驱动的自动化非常重要,特别是 dom_settle_timeout_ms 和 debug_dom,它们能显著提高 AI 在复杂单页应用(SPA)中的识别准确率。
3.3 自然语言驱动的操作 (Act)
Stagehand 的核心魔力在于 act 方法。我们不再查找 XPath 或 CSS Selector,而是直接告诉它“要做什么”。
stagehand = Stagehand(config)
await stagehand.init()
page = stagehand.page
# 1. 访问目标网站(示例中使用百度)
await page.goto("https://www.baidu.com/")
# 2. 自然语言指令:输入搜索词
await page.act("在搜索框中输入搜索词'天气'")
# 3. 自然语言指令:点击搜索
await page.act("点击搜索按钮")
AI 是如何工作的?
Stagehand 会抓取当前页面的 DOM 快照,将其简化后发送给 Qwen 模型。Qwen 理解“搜索框”和“输入”的语义,返回具体的 Playwright 操作指令。这使得脚本对页面改版具有极强的鲁棒性——只要搜索框还在,脚本就能跑。
3.4 智能信息提取 (Extract)
除了操作,Stagehand 还支持结构化数据提取。
# 提取页面主要内容
content = await page.extract("提取页面中与天气相关的主要信息", schema=None)
print(f"搜索结果: {content}")
extract 方法不仅仅是抓取文本,它会根据你的指令(Prompt)从杂乱的 HTML 中提炼出关键信息。这对于爬虫开发来说是一个巨大的效率提升。
4. 技术优势
相比于传统的浏览器自动化方案,结合 Stagehand 和 Qwen 的方案具有以下显著优势:
-
零选择器维护 (Zero-Selector Maintenance)
- 传统痛点: 页面改版导致 class 或 id 变化,脚本即刻失效。
- 本方案优势: 基于视觉和语义理解页面,只要元素还在(哪怕位置变了、样式变了),脚本依然能找到并操作。
-
自然语言驱动 (Natural Language Driven)
- 传统痛点: 需要编写复杂的
page.click('div > span:nth-child(2)')代码,可读性差。 - 本方案优势: 直接使用
page.act("点击搜索按钮"),代码即文档,非技术人员也能看懂。
- 传统痛点: 需要编写复杂的
-
自适应与容错 (Adaptive & Robust)
- 传统痛点: 遇到意外弹窗或加载延迟,脚本容易报错退出。
- 本方案优势: 大模型具备一定的推理能力,可以根据上下文判断当前状态,甚至处理意料之外的简单交互。
-
智能提取 (Intelligent Extraction)
- 传统痛点: 编写正则表达式或解析规则提取数据非常繁琐且容易出错。
- 本方案优势:
extract方法能直接将非结构化 HTML 转化为结构化的 JSON 数据。
5. 场景对比与应用
| 场景 | 传统方案 (Selenium/Playwright) | 智能化方案 (Stagehand + Qwen) | 智能化方案优势 |
|---|---|---|---|
| 端到端测试 (E2E) | 脚本脆弱,UI 微调需频繁维护脚本。 | 关注业务逻辑而非 UI 实现细节。 | 维护成本降低 80%,测试脚本更稳定,不因前端改版而频繁重写。 |
| 通用爬虫/数据采集 | 针对每个网站编写特定的解析规则,反爬对抗成本高。 | 通用指令提取数据(如“提取所有商品价格”)。 | 开发效率极高,一套逻辑可适配多个结构相似的网站;自动适应页面结构变化。 |
| RPA (流程自动化) | 仅能处理固定流程,遇到异常弹窗或分支流程容易卡死。 | 具备“视觉”和“大脑”,能动态决策下一步操作。 | 更强的鲁棒性,能处理复杂的动态流程和非预期异常。 |
| 竞品监控 | 需要长期维护针对竞品网站的抓取脚本。 | 即使竞品网站改版,监控脚本依然有效。 | 长期稳定性好,减少因目标网站更新导致的数据中断。 |
6. 总结
这段代码展示了下一代浏览器自动化的雏形:
- 去选择器化:不再依赖脆弱的 CSS/XPath 选择器。
- 语义理解:模型理解“输入”、“点击”、“提取”的意图。
- 多模型支持:证明了国产大模型(如 Qwen)完全有能力胜任此类复杂的 Agent 任务。
通过 test_stagehand_qwen.py,我们看到了一个高度可配置、易于调试且具备 AI 智能的自动化测试/爬虫方案。无论是用于端到端测试(E2E Testing)还是数据采集,这种模式都极具潜力。
7. 完整示例代码
import asyncio
import os
from stagehand import Stagehand, StagehandConfig
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
async def main():
print("Testing Stagehand with Local Playwright...")
os.environ['DASHSCOPE_API_BASE'] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 尝试配置使用 Qwen
# 注意:这里假设 litellm 支持 'qwen-max' 并且 Stagehand 允许自定义模型字符串
config = StagehandConfig(
env="LOCAL",
modelName="dashscope/qwen3-max-preview",
modelApiKey=os.getenv("DASHSCOPE_API_KEY"),
headless=True,
verbose=3,
debug_dom=True, # 启用DOM调试
dom_settle_timeout_ms=60000, # 增加DOM稳定超时时间
wait_for_network_idle_ms=5000, # 等待网络空闲的时间
local_browser_launch_options={ # 本地浏览器启动选项
"slow_mo": 100, # 减慢操作速度以便观察
"devtools": True, # 打开开发者工具
"args": [
"--window-size=1920,1080", # 设置窗口大小
"--disable-web-security", # 禁用网络安全限制
"--disable-features=IsolateOrigins,site-per-process" # 禁用某些隔离功能
]
}
)
stagehand = Stagehand(config)
try:
await stagehand.init()
page = stagehand.page
print("=== 百度搜索测试任务 ===")
# 1. 访问必应
print("1. 正在访问百度...")
await page.goto("https://www.baidu.com/")
print(" 已访问百度")
# 等待页面加载完成
await asyncio.sleep(3)
# 2. 输入搜索词
print("2. 正在输入搜索词...")
await page.act("在搜索框中输入搜索词'天气'")
print(" 已输入搜索词")
# 等待输入完成
await asyncio.sleep(2)
# 3. 点击搜索
print("3. 正在点击搜索按钮...")
await page.act("点击搜索按钮")
print(" 已点击搜索按钮")
# 等待搜索结果页面加载
await asyncio.sleep(5)
# 验证操作结果
print("4. 正在验证搜索结果...")
title = await page.title()
print(f" 当前页面标题: {title}")
# 获取页面URL验证是否跳转
current_url = page.url
print(f" 当前页面URL: {current_url}")
# 提取页面主要内容
print("5. 正在提取搜索结果...")
content = await page.extract("提取页面中与天气相关的主要信息", schema=None)
print(f" 搜索结果: {content}")
# 保持浏览器打开一段时间以便观察
print("6. 保持浏览器打开10秒钟以便观察...")
await asyncio.sleep(10)
print("=== 百度搜索测试完成 ===")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
finally:
await stagehand.close()
if __name__ == "__main__":
asyncio.run(main())
浙公网安备 33010602011771号