浏览器 Agent 架构
纯视觉 vs DOM 感知的选择
在浏览器自动化中,有两种截然不同的架构方案,各有优劣:
纯视觉模式
通过截图让 Claude 理解页面,模拟人类鼠标键盘操作。优势:通用性强,任何网页都能处理;劣势:速度慢,精度可能不稳定,成本高。
DOM 感知模式
用 Playwright 获取页面 DOM 结构,让 Claude 基于 HTML 分析页面,再用 Playwright API 精确操作元素。优势:快速、可靠;劣势:对动态渲染页面可能有限制。
混合模式(推荐)
用 Playwright 截图(质量比桌面截图更好)和 DOM 信息,同时提供给 Claude。Claude 可以选择基于坐标操作或基于选择器操作,灵活切换。
Playwright + Computer Use 集成架构
from playwright.async_api import async_playwright, Page
import base64
class BrowserAgent:
"""基于 Playwright 的浏览器 Agent,支持 Computer Use 操作"""
def __init__(self):
self.playwright = None
self.browser = None
self.page: Optional[Page] = None
async def __aenter__(self):
self.playwright = await async_playwright().start()
self.browser = await self.playwright.chromium.launch(
headless=False, # 有界面模式,便于调试
args=["--start-maximized"]
)
context = await self.browser.new_context(
viewport={"width": 1280, "height": 800}
)
self.page = await context.new_page()
return self
async def __aexit__(self, *args):
await self.browser.close()
await self.playwright.stop()
async def take_screenshot(self) -> str:
"""截取浏览器截图并返回 base64 字符串"""
screenshot_bytes = await self.page.screenshot(type="jpeg", quality=80)
return base64.b64encode(screenshot_bytes).decode()
async def get_page_context(self) -> str:
"""获取页面的简化 HTML 结构(用于 DOM 感知模式)"""
# 提取关键元素(按钮、链接、输入框),不返回完整 HTML
context = await self.page.evaluate("""() => {
const elements = [];
const selectors = ['button', 'a', 'input', 'select', 'textarea'];
selectors.forEach(sel => {
document.querySelectorAll(sel).forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
elements.push({
tag: el.tagName,
text: el.textContent?.trim().slice(0, 50),
type: el.type,
placeholder: el.placeholder,
x: Math.round(rect.x),
y: Math.round(rect.y),
w: Math.round(rect.width),
h: Math.round(rect.height),
});
}
});
});
return JSON.stringify(elements);
}""")
return context
async def execute_action(self, action: str, **params) -> dict:
"""执行 Computer Use API 的动作"""
if action == "screenshot":
img_b64 = await self.take_screenshot()
return {
"type": "image",
"source": {"type": "base64", "media_type": "image/jpeg", "data": img_b64}
}
elif action == "left_click":
x, y = params["coordinate"]
await self.page.mouse.click(x, y)
return {"type": "text", "text": f"Clicked ({x}, {y})"}
elif action == "type":
await self.page.keyboard.type(params["text"])
return {"type": "text", "text": "Typed text"}
elif action == "key":
await self.page.keyboard.press(params["text"])
return {"type": "text", "text": f"Pressed {params['text']}"}
elif action == "scroll":
x, y = params["coordinate"]
delta_y = -300 if params.get("direction") == "up" else 300
await self.page.mouse.wheel(0, delta_y)
return {"type": "text", "text": "Scrolled"}
登录自动化
处理登录流程
async def login_task(agent: BrowserAgent, username: str, password: str) -> bool:
"""
AI 驱动的登录流程。
不硬编码 UI 结构,让 Claude 理解页面后自主操作。
"""
task = f"""
请登录到当前页面的系统。
用户名: {username}
密码: {password}
步骤:
1. 先截图查看当前页面状态
2. 找到用户名输入框并输入
3. 找到密码输入框并输入
4. 点击登录按钮
5. 截图确认登录成功(查看是否有欢迎信息或进入了主界面)
如果看到验证码,停止并告知我需要手动处理。
"""
# 导航到登录页面
await agent.page.goto("https://example.com/login")
# 启动 Computer Use Agent 执行登录
result = await run_browser_agent(agent, task)
return "登录成功" in result or "welcome" in result.lower()
会话状态持久化
async def create_browser_with_session(session_file: str):
"""创建浏览器,复用已保存的会话状态(避免每次重新登录)"""
async with async_playwright() as p:
browser = await p.chromium.launch()
# 如果有保存的会话,直接使用
if Path(session_file).exists():
context = await browser.new_context(
storage_state=session_file
)
else:
context = await browser.new_context()
page = await context.new_page()
# ... 执行任务 ...
# 任务完成后保存会话状态
await context.storage_state(path=session_file)
await browser.close()
错误恢复机制
常见失败场景与恢复策略
页面加载超时
实现重试逻辑:等待 2 秒后重新导航,最多重试 3 次。如果仍然失败,截图并告知 Claude 页面无法加载。
元素点击失败
点击后截图确认。如果页面没有变化,可能是坐标偏移——告知 Claude 重新定位目标元素。
意外弹窗
截图时检测是否有弹窗。Cookie 通知、广告弹窗、确认对话框——让 Claude 根据截图判断如何处理。
CAPTCHA
当检测到 CAPTCHA 时,暂停执行,通过回调通知人类处理,然后继续。可以集成 2captcha 等服务自动解决简单 CAPTCHA。
云端浏览器服务
Browserbase 集成
Browserbase 是专为 AI Agent 设计的云端浏览器服务,解决了本地运行 Playwright 的几个痛点:无头服务器上运行 GUI、IP 轮换、反爬虫绕过、会话录制回放。
from browserbase import Browserbase
from playwright.async_api import async_playwright
async def use_browserbase(task: str):
bb = Browserbase(api_key="bb_...")
# 创建云端浏览器会话
session = bb.sessions.create(project_id="your_project_id")
connect_url = session.connect_url
async with async_playwright() as p:
# 连接到云端浏览器,而不是本地
browser = await p.chromium.connect_over_cdp(connect_url)
page = browser.contexts[0].pages[0]
# 正常使用 Playwright API
await page.goto("https://example.com")
# ... Computer Use Agent 逻辑 ...
何时使用云端浏览器
本地 Playwright 适合开发和测试阶段。生产环境建议使用云端浏览器服务(Browserbase、Steel.dev),原因:IP 不固定、无需维护服务器 GUI 环境、更好的并发支持、内置操作录像功能(便于调试)。
反爬虫检测与绕过
为什么 Playwright 默认会被检测为机器人
现代网站使用多种技术检测自动化工具,了解这些机制有助于构建更稳健的浏览器 Agent:
Webdriver 属性
Playwright 启动的浏览器中,
navigator.webdriver 属性默认为 true,这是最直接的机器人标识。可以通过 Playwright 的隐身模式或注入脚本将其设为 undefined。User Agent 异常
Playwright 使用的 User Agent 包含 "HeadlessChrome" 字样,很多网站会拒绝此类请求。应手动设置为真实浏览器的 User Agent 字符串。
行为指纹
鼠标轨迹完全直线(无加速度变化)、点击间隔毫秒级精确、从不滚动——这些都是机器人的行为特征。增加随机延迟和鼠标轨迹变化可以降低被检测的概率。
Canvas/WebGL 指纹
无头浏览器的 Canvas 渲染结果与真实浏览器有细微差异,反爬虫系统会比对这个"指纹"来识别机器人。使用 Playwright 的有头模式(headless=False)可以避免此问题。
from playwright.async_api import async_playwright
async def create_stealth_browser():
"""
创建带反检测配置的浏览器实例。
注意:这些技术仅用于合法的自动化用途(如测试自己的网站)。
"""
playwright = await async_playwright().start()
browser = await playwright.chromium.launch(
headless=False, # 有头模式,避免 headless 检测
args=[
"--disable-blink-features=AutomationControlled", # 禁用自动化控制标识
"--no-sandbox", # Docker 容器中需要
"--start-maximized"
]
)
context = await browser.new_context(
# 使用真实 User Agent
user_agent=(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/121.0.0.0 Safari/537.36"
),
viewport={"width": 1280, "height": 800},
locale="zh-CN",
timezone_id="Asia/Shanghai",
)
page = await context.new_page()
# 注入脚本:隐藏 webdriver 属性
await page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// 模拟真实的插件列表
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
""")
return browser, context, page
页面状态识别模式
如何判断页面处于什么状态
浏览器 Agent 需要识别页面的多种状态,并做出相应的处理决策。以下是常见状态的识别模式:
from playwright.async_api import Page
import base64
class PageStateAnalyzer:
"""浏览器页面状态分析器"""
def __init__(self, page: Page):
self.page = page
async def is_loading(self) -> bool:
"""检查页面是否正在加载"""
try:
return await self.page.evaluate("document.readyState !== 'complete'")
except:
return True
async def has_error_page(self) -> bool:
"""检查是否是错误页面(404、503、网络错误等)"""
url = self.page.url
title = await self.page.title()
error_indicators = [
"ERR_", "404", "503", "Not Found",
"Access Denied", "Forbidden"
]
return any(ind in title or ind in url for ind in error_indicators)
async def has_login_required(self) -> bool:
"""检查是否需要登录"""
# 方法1:检查 URL 是否包含 login 关键词
url = self.page.url
if any(k in url.lower() for k in ["login", "signin", "auth"]):
return True
# 方法2:检查页面是否有密码输入框
password_input = await self.page.query_selector("input[type='password']")
return password_input is not None
async def detect_captcha(self) -> bool:
"""检测常见的 CAPTCHA 类型"""
# reCAPTCHA
recaptcha = await self.page.query_selector(".g-recaptcha, #recaptcha")
# hCaptcha
hcaptcha = await self.page.query_selector(".h-captcha")
# Cloudflare Turnstile
turnstile = await self.page.query_selector(".cf-turnstile")
return any([recaptcha, hcaptcha, turnstile])
async def get_page_summary(self) -> str:
"""
生成页面摘要(非截图),用于提示词中帮助 Claude 理解页面结构。
比截图更省 token,适合在高频判断场景使用。
"""
title = await self.page.title()
url = self.page.url
# 获取页面可见文本(截取前 500 字)
text = await self.page.evaluate("document.body.innerText")
text_preview = text[:500] if text else ""
return f"""页面状态摘要:
URL: {url}
标题: {title}
文本预览(前500字): {text_preview}
加载状态: {"加载中" if await self.is_loading() else "已完成"}
"""
章节小结
本章讲解了浏览器自动化 Agent 的设计模式和关键技术。核心要点:
- 混合模式(Playwright 截图 + DOM 感知)比纯视觉模式更高效可靠
- 会话持久化(
storage_state)可以复用登录状态,避免每次重新登录 - 反爬虫检测的核心是
navigator.webdriver属性和行为指纹,有头模式可降低被检测概率 - 页面状态识别(加载中/错误/需登录/验证码)是容错设计的基础
- 生产环境推荐使用 Browserbase 等云端浏览器服务,避免维护本地 GUI 环境