从头开始构建计算机使用浏览器代理的循环
Lead Scraping Automation Engineer
TL;DR:
- 计算机使用代理可以简化为一个有限的感知–决策–行动循环。 浏览器捕获当前页面,视觉模型选择一个动作,然后 Playwright 执行该动作,循环再次开始。
- 动作模式保持模型输出可执行。 将每个决策限制为
click、type、scroll或done,使模型响应转变为一个小型确定性调度器。 - 最新的截图防止过时决策。 每次模型调用都能看到上一个动作后的页面,因此代理基于浏览器的当前状态进行推理。
- 步骤上限同时控制自主性和成本。 这个示例最多允许四次迭代,并在两次中完成:点击
Next,确认新的第一条报价,然后停止。 - 当决策逻辑需要保持可见时,自定义循环是最佳选择。 对于一次性提取,使用单个视觉调用;当循环不再需要频繁调整时,使用打包代理。
- 免费开始。 新的 Scrapeless 账户包括免费的抓取浏览器运行时 - 在 app.scrapeless.com 注册。
引言:计算机使用背后的小循环
计算机使用代理重复执行三件事情:查看屏幕、决定一个动作并执行该动作。像 OpenAI 的计算机使用工具和谷歌的 Gemini 计算机使用模式这样的产品,将这个循环包装在一个 API 调用后面。代理框架如 Skyvern 将其包装在一个任务运行器后面。在这两种情况之下,循环本身只是一小段代码——一张截图,一个限制在短动作列表中的视觉模型调用,以及一个执行模型选择的浏览器命令。
本指南直接编写这个循环,没有中间的打包代理。它通过 Chrome DevTools 协议连接到 Scrapeless 抓取浏览器,截取页面,给一个具有视觉能力的模型发送图像,提供四个选择——点击、输入、滚动或完成——并执行模型返回的任何内容,之后重复。以下的每一步都在真实页面上运行,并记录模型的实际决策。
循环概述
每次迭代都有一个真相来源:页面在最新截图中的外观。
| 阶段 | 输入 | 输出 | 实现 |
|---|---|---|---|
| 感知 | 当前浏览器页面 | 全页面 PNG 截图 | page.screenshot(full_page=True) |
| 决策 | 目标、动作模式、截图 | 一个 JSON 决策 | 视觉模型请求 |
| 行动 | 验证的决策 | 浏览器状态变化 | Playwright 点击、填写或滚动 |
| 停止或重复 | 新的浏览器状态 | done 或另一次迭代 |
max_steps 限制循环 |
本演练使用一个公共报价网站和一个狭窄的目标:仅在第一条报价是阿尔伯特·爱因斯坦所作时离开第一页,然后一旦出现不同的作者就停止。
为什么自己构建循环?
直接编写循环可以让您掌控感知格式、动作词汇和停止规则。
结构化快照代理和打包视觉框架在更高层次上解决了相同的问题。第一个可以将一个由WAI-ARIA 可访问性树构建的角色、标签和状态树交给 LLM,而不是像素,这在清晰标记的页面上更便宜、更准确。在抓取浏览器上运行的浏览器使用 采取了这条路径——它通过同一个 CDP 连接驱动同一个云 Chromium,但其代理对提取的页面文本进行推理,而不是图像。
另一种捷径将整个循环交给一个打包视觉框架,该框架以这种演练的方式截屏、推理和行动,除了提示、动作模式和决策代码都在库内部,而不是在调用脚本中。
自定义决策的每个部分都保持可见和可编辑:动作词汇,请求一个动作的提示、步骤上限以及将决策转化为 Playwright 调用的行。不同的目标、对象或模型可以在脚本的一部分中修改,而不是在整个框架配置界面中修改。
前提条件
您需要 Python 3.9 或更新版本,playwright 和 requests 包,以及用于浏览器会话的 Scrapeless API 密钥和用于视觉能力模型的密钥。在 app.scrapeless.com 的免费计划上获取 Scrapeless 密钥。下面的示例通过 OpenRouter 路由模型调用,该路由将多个模型提供者隐藏在一个密钥和一个端点后面。
设置项目
安装依赖项
bash
pip install playwright requests
配置 API 密钥
bash
export SCRAPELESS_API_KEY="your_scrapeless_api_key"
bash
export OPENROUTER_API_KEY="你的模型API密钥"
构建感知-决策-行动循环
完整的脚本连接一个远程浏览器,定义一个四个动作的合同,并运行该合同,直到模型返回done或达到步骤限制。
连接到远程浏览器
Scraping Browser通过WebSocket连接暴露一个CDP端点 —— WebSocket协议以双向传输Chrome DevTools协议流量。Playwright的connect_over_cdp以与连接到本地启动调试端口的Chromium实例相同的方式连接到该端点;浏览器本身在Scrapeless的云中运行,而不是在运行此脚本的机器上。Playwright代理指南更详细地介绍了相同的远程CDP连接模式。
定义决策合同
三个函数承担循环的三个部分:perceive获取截图,decide将其发送至模型并解析回复,而act将该回复转换为Playwright调用。提示要求模型在固定字段顺序中填写一个小JSON对象 —— 在选择行动之前先命名图像中显示的一个具体事实,可以使决策基于截图实际展示的内容,而不是对是否达成目标的开放式猜测。
运行完整示例
python
import os
import json
import base64
import requests
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright
GOAL = (
'目标:访问一个页面,其中第一个引用卡片的作者不是“阿尔伯特·爱因斯坦”。 '
'读取第一个引用卡片上的作者名称。如果是“阿尔伯特·爱因斯坦”,点击 '
'“下一页”分页链接。否则目标已经完成。'
)
SCHEMA_PROMPT = (
GOAL + "\n\n仅以 JSON 对象形式响应,依次填写每个字段:\n"
'{"first_quote_author": "<第一个引用卡片上的作者名称,从图像中读取>", '
'"action": "click" | "type" | "scroll" | "done", '
'"target": "<要点击或输入的元素的可见文本,滚动/完成时省略>", '
'"value": "<输入的文本,仅当动作是类型时>", '
'"reason": "<少于8个单词>"}'
)
def browser_url():
return "wss://browser.scrapeless.com/api/v2/browser?" + urlencode(
{"token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 180, "proxyCountry": "US"})
def perceive(page):
return page.screenshot(full_page=True)
def decide(png_bytes):
img = "data:image/png;base64," + base64.b64encode(png_bytes).decode()
resp = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}", "Content-Type": "application/json"},
json={"model": "google/gemini-2.5-flash-lite",
"messages": [{"role": "user", "content": [
{"type": "text", "text": SCHEMA_PROMPT},
{"type": "image_url", "image_url": {"url": img}}]}],
"temperature": 0, "max_tokens": 300},
timeout=120,
)
resp.raise_for_status()
return json.loads(resp.json()["choices"][0]["message"]["content"])
def act(page, decision):
action = decision["action"]
if action == "click":
page.get_by_text(decision["target"], exact=False).first.click(timeout=5000)
page.wait_for_load_state("networkidle")
elif action == "type":
page.get_by_text(decision["target"], exact=False).first.fill(decision["value"], timeout=5000)
elif action == "scroll":
page.mouse.wheel(0, 900)
page.wait_for_timeout(300)
return action
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(browser_url())
page = browser.new_page(viewport={"width": 1280, "height": 900})
page.goto("https://quotes.toscrape.com/", wait_until="networkidle")
max_steps = 4
for step in range(1, max_steps + 1):
decision = decide(perceive(page))
print(f"步骤 {step}: 作者={decision['first_quote_author']!r} "
f"动作={decision['action']} 原因={decision['reason']!r}")
if decision["action"] == "done":
break
act(page, decision)
print("最终网址:", page.url)
print("采取的步骤:", step)
browser.close()
在免费计划中获取你的API密钥:app.scrapeless.com
验证运行
针对实时页面,示例在首次导航后停止于下一个迭代:
text
步骤 1: 作者='阿尔伯特·爱因斯坦' 动作=点击 原因='第一个引用的作者是阿尔伯特·爱因斯坦,需要点击下一步。'
步骤 2: 作者='玛丽莲·梦露' 动作=完成 原因='第一个引用的作者不是阿尔伯特·爱因斯坦。'
最终 URL: https://quotes.toscrape.com/page/2/
采取的步骤: 2
跟踪双步决策
该跟踪展示了为什么循环需要在每次操作后进行新的截图。
步骤 1: 阅读第一张卡片并点击下一步
步骤 1 截取了首页的截图,模型从第一张引用卡片上读取到“阿尔伯特·爱因斯坦”——这是一项任何查看同一图像的人都会确认的事实——并返回 {"action": "click", "target": "Next →"}。act 函数通过 page.get_by_text("Next →", exact=False) 解析该目标,这与直接针对 DOM 编写的脚本使用的定位器模式相同,并点击它。点击后导航至 /page/2/。
步骤 2: 确认新状态并停止
步骤 2 截取了新页面的截图。第一张引用卡片现在属于玛丽莲·梦露,模型报告了这一事实,返回的操作是 done。循环在四个分配步骤中的两个步骤后自行退出——它从未需要达到上限,也从未需要滚动操作,因为 perceive 一次性捕捉整个页面,而不仅仅是可见的视口。对于在用户滚动时加载更多内容的页面——无限滚动、惰性加载的部分——完整页面捕捉无法看到尚未加载的内容,这就是为什么 scroll 始终在操作词汇表中的原因,尽管该目标从未触发它。type 分支的工作原理相同,将与 target 匹配的任何元素填充 value;本次演示的目标从未需要表单字段,因此该分支运行相同的调度逻辑,而没有特别的运行来执行它。
保持循环受限且当前
两个设计选择使循环避免失控。max_steps 限制意味着模型即使从未返回 done 仍会自行停止,而截图加单一决策循环意味着循环从未对过时信息采取行动——每个决策都是针对此时存在的页面做出的,而不是针对自上次查看以来发生的变化的假设。
选择正确的浏览器代理形态
正确的方法取决于任务是否需要一次观察或多次观察,以及您是否需要检查决策逻辑。
| 方法 | 最佳应用 | 决策控制 | 主要权衡 |
|---|---|---|---|
| 单一视觉调用 | 从一个稳定截图中读取值 | 高,但没有浏览器操作 | 在页面更改后无法适应 |
| 自定义截图循环 | 跨越多个步骤达到浏览器状态 | 对提示、操作和停止条件的完全控制 | 您拥有验证和编排 |
| 打包代理 | 在多个目标和页面中重用稳定循环 | 依赖于框架 | 更快的集成,对循环的可见性较低 |
单一视觉调用读取一个截图并返回结构化数据,而不进行任何操作。LLM 抓取器的作用 涵盖了更广泛的提取模式。当任务只是读取一个值时,请使用该形态。
自定义循环适用于需要多次查看以满足的目标。每个截图都依赖于先前的操作,确切的决策逻辑保持在可检查和更改的代码中。
打包代理框架将相同的循环(或文本基础变体,在 Browser Use 的情况下)封装在任务运行器后面。一旦循环的形态稳定,该交易是有用的,主要工作是在多个页面上运行多个目标。
结论:保持循环小且可观察
“计算机使用”代理背后的机制是一个截图,一个受限于短列表的视觉模型调用,以及执行结果的浏览器命令。该循环重复执行,直到模型报告目标已达成或达到步骤上限。
上述运行经过两次迭代,从以阿尔伯特·爱因斯坦为前景的页面移动到以玛丽莲·梦露为前景的页面。模型的决策在每一步都保持可见,而执行它的 Playwright 调用仍然是普通的应用程序代码。
查看运行时在 Scraping Browser 产品页面 提供的内容,并在 定价页面 权衡步骤计数与模型成本,然后再将这样的循环扩大到超过少数目标。
准备构建浏览器代理循环了吗?
加入我们的社区以获取免费套餐,并与其他构建浏览器代理的开发者交流笔记:Discord · Telegram。
在 app.scrapeless.com 注册以获取免费的爬虫浏览器运行时,并将有限循环调整为您可以逐步验证的公共页面和目标。
常见问题解答
问:什么是计算机使用风格的浏览器代理循环?
计算机使用风格的浏览器代理循环是一个重复三步的循环,直到达到目标:通过截图感知页面,通过将截图发送给具有视觉能力的模型决定一个动作,最后通过实际的浏览器命令执行该决策。OpenAI的计算机使用工具和谷歌的Gemini计算机使用模式将在托管API背后封装相同的循环。
问:这与在同一浏览器上运行Skyvern或Browser Use有什么不同?
这些框架内部运行相同类型的循环,但提示、动作模式和决策代码都存在于库内部。这个逐步指南直接编写这三部分,因此决策的每个部分在调用脚本中都是可见和可编辑的,而不是在框架的配置表面。
问:这与单次截图到JSON提取调用有什么不同?
单次视觉调用读取一张图像并返回数据,没有采取动作也没有循环。这个逐步指南重复感知、决策和行动,直到模型报告目标已达成,并且每个步骤的截图反映前一步骤动作的结果。
问:为什么将模型的回复限制为固定的动作模式,而不是自由形式的指令?
一个小而固定的词汇——点击、输入、滚动、完成——将模型的回复转化为调用代码可以确定性调度的内容,使用if/elif链。不受限制的回复需要自己的解析和解释层,然后才能安全地驱动浏览器。
问:为什么循环要让模型在选择动作之前陈述一个具体的事实?
要求模型从图像中陈述一个可检查的细节——例如,此示例中的第一张卡片上的作者——使动作与截图实际显示的内容保持一致。一个要求模型自由推理目标是否达成的字段更容易偏离面前的图像。
问:为什么要拍摄整个页面的截图,而不仅仅是可视视口?
这样模型可以看到折叠下方的元素,例如长列表底部的分页链接,而无需先进行专门的滚动步骤。一个页面只在用户滚动时加载更多内容——无限滚动或懒加载部分——是一个完整页面捕获无法满足的情况,而滚动动作则在循环中得以其位置。
问:为什么要限制步骤的数量?
max_steps限制单个目标可以运行多少次感知-决策-行动循环,因此一个从不返回done的模型仍然会停止,而不是无限期地行动。每一步是一个截图和一个模型调用,因此限制也限制了运行的成本。
问:示例使用哪个视觉模型,可以替换它吗?
该示例通过OpenRouter路由到google/gemini-2.5-flash-lite。任何通过相同聊天完成模式可以到达的视觉能力模型都可以通过更改model字段来替换它。
问:将这种循环指向真实网站是否安全?
是的,当目标是公共的,步骤计数是有限的,并且目标排除登录、支付和私人数据时。上面的示例在一个为爬虫练习而建立的公共引用网站上运行两个有限步骤。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



