news 2026/9/6 1:36:57

用Skill封装浏览器动作,优化Codex网页自动化的Token消耗与速度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Skill封装浏览器动作,优化Codex网页自动化的Token消耗与速度

直接说结论:Codex 操作浏览器变慢、Token 消耗偏高,很多时候不是模型能力问题,而是让模型反复用自然语言推理去完成“打开网页、点按钮、填表单、读结果”这一整条链路,每个中间步骤都在消耗 Token。更稳的做法是写一个 Skill,把浏览器操作封装成一组可复用动作,让 Codex 只负责定方向和看结果,不负责每一步的细节推理。这个思路我在本地任务里验证过,单轮 Token 能省下来不少,整体执行速度也明显更稳。

这篇文章适合两类人:一是刚开始用 Codex 做网页自动化,但发现它经常在页面交互上绕圈子的;二是已经写了几个 Claude Code 或 Codex 的 Skill,想搞清楚浏览器类型 Skill 到底该怎么设计结构和参数的。看完之后,你会知道一个最小可用的浏览器 Skill 长什么样,怎么接进 Codex 的单任务和批量任务,以及哪些报错其实不是 Codex 的问题,而是 Skill 本身的边界没理清。

1. 先搞清楚 Codex 操作浏览器慢和费 Token 的根源

1.1 Codex 默认方式:每一步都要大模型“想”出来

如果直接让 Codex 操作浏览器,它通常会先拆任务,再写代码,再执行,然后读取页面反馈接着判断下一步。听起来没问题,但实际跑起来会发现一个特点:模型在每一步都会收到页面状态、HTML 片段、控制台输出、代码报错等一系列信息,尤其是遇到需要等待页面加载、需要识别按钮状态、需要处理弹窗的场景,模型会反复生成新的操作代码。

这里的关键问题是,Codex 不是人,它不会“看到”页面,它只能通过接收到的文本信息来推测页面长什么样。页面越复杂,DOM 里无用的节点越多,模型需要处理的上下文就越长,Token 消耗自然上去了。速度变慢也顺着这个逻辑来:一次操作如果从发指令到拿到结果需要两轮或三轮模型推理,等于每一步都在排队等模型输出。

一句话总结:默认玩法下,浏览器操作被 Codex 当作“开放式推理任务”来处理,而不是“确定的工具调用任务”。这也是它又慢又费 Token 的第一层原因。

1.2 Skill 的根本变化:把“思考过程”变成“固定参数”

Skill 的做法完全不同。它把常见的浏览器动作拆成一批已经写好的函数,每个函数有明确的输入、输出和失败回调。Codex 拿到任务后,不需要每次重新发明按钮点击逻辑,也不需要反复读大量 DOM 结构来猜下一步,它只需要从 Skill 里挑出合适的动作,填入参数,然后等结果。

举个例子,让 Codex“打开 example.com 并截图保存”,默认方式可能是:先尝试写启动浏览器的代码,再写打开网页的代码,再写等待页面加载的代码,最后写截图的代码。遇到失败还要重新生成。而用 Skill 之后,Codex 只需要拿到一个参数列表,比如:

{ "action": "goto", "url": "https://example.com", "wait_seconds": 3, "then": "screenshot", "output_path": "screenshots/example.png" }

整个任务的复杂度从“生成一段交互式脚本”变成“匹配一个已封装动作”。模型要处理的 Token 量大幅下降,执行速度也会更快。再往深层说,Skill 的本质是减少大模型在低层级操作上的随机性和重复推理。

我观察到很多人对 Skill 有一个误解,以为它是给 Codex 写提示词,让模型“按提示词操作”。实际上 Skill 更像是提供一组标准化接口,Codex 是调用方,不是实现方。提示词那种方式还得让模型自己理解上下文,Skill 则尽量把上下文压缩成少量参数。

2. 浏览器 Skill 的核心设计:动作库、参数和错误回调

2.1 不要把 Skill 做成一个大而全的“浏览器操作百科”

很多第一次写浏览器 Skill 的人会犯同一个错误:想把所有浏览器功能一次性封装进去。打开页面、点击、输入、上传、下载、截图、切换标签、处理弹窗、读取 Cookie、模拟键盘……全写在一个文件里。结果就是配置特别长,Codex 在匹配动作时也会犹豫,Token 消耗反而上升。

我更建议从三类基础能力开始:

  • 页面导航:打开 URL、刷新、后退、前进。
  • 页面提取:获取标题、获取可见文本、截图、读取指定元素的属性。
  • 页面交互:点击按钮、输入文字、选择下拉框、提交表单、等待元素出现。

这三个类别已经能覆盖八成以上的网页自动化场景。需要更复杂的动作,再按需追加,不要一开始就铺开。

Skill 的目录里应该有独立的能力描述文件,让 Codex 知道什么时候该用哪个动作。描述可以用简短中文写清楚用途、输入参数、返回值、失败情况下会怎么做。这里描述越精确,Codex 选错动作的概率越低。

2.2 每个动作都必须有明确的输入输出约束

浏览器自动化的动作函数不能像普通脚本那样“跑通就行”,因为调用方是 Codex,必须让函数结果可被解析、可被判断。以 Selenium 或 Playwright 封装的函数为例,我会要求每个函数:

  • 输入参数全部有默认值,必要参数必须校验。
  • 返回值统一成 JSON 结构,包含成功标记、消息、耗时和数据。
  • 失败时返回结构化错误,不要只抛一个 Exception 让 Codex 猜。

一个最小动作实现大概是这个结构:

def goto(driver, url: str, wait_seconds: int = 3): try: driver.get(url) time.sleep(wait_seconds) return {"success": True, "title": driver.title, "url": driver.current_url} except Exception as e: return {"success": False, "error": str(e)}

Codex 拿到返回值后,只要看success字段就能决定下一步。如果返回的是纯文本或裸异常,Codex 又要做一层解析,Token 又浪费了。

2.3 页面等待参数是省 Token 的另一把钥匙

浏览器自动化最常见的隐性浪费是隐式等待过长。很多人习惯time.sleep(5)一写到底,问题在于每次等待都会拖慢任务,Codex 拿到反馈的时间变长,如果等待时间超过它的预期,模型可能还会再次生成动作,Token 消耗跟着翻倍。

我一般不用固定等待,而是优先用显式等待,比如等待某个元素出现,超时再报错。这样页面正常时任务走得快,页面异常时能快速失败。显式等待写成参数形式:

{ "action": "click", "selector": "#submit-button", "wait_for": "#result-panel", "timeout_ms": 10000 }

Codex 看到wait_for就知道这个动作的完成标志是#result-panel出现,而不是盲等。这里其实是 Skill 设计最容易被低估的部分:页面等待写得好,Token 消耗和任务速度都会有明显变化。

3. 自己动手写一个最小可用的浏览器 Skill

3.1 先设计目录和动作清单

我建议目录结构保持简单:

browser-skill/ ├── skill.json ├── actions.py ├── requirements.txt └── README.md

skill.json是给 Codex 看的动作描述文件,actions.py是实际执行的 Python 函数。不要在一个文件里塞太多东西,否则多了之后排错很难受。

skill.json可以这样写:

{ "name": "browser-actions", "description": "用于浏览器自动化的基础动作,包含页面导航、页面提取、页面交互三类能力。", "actions": [ { "name": "goto", "description": "打开指定网页,并返回页面标题和当前 URL。", "parameters": { "url": {"type": "string", "required": true}, "wait_seconds": {"type": "integer", "default": 3} } }, { "name": "extract_text", "description": "获取页面可见文本,用于内容提取和结果验证。", "parameters": { "selector": {"type": "string", "default": "body"} } }, { "name": "click", "description": "点击匹配选择器的页面元素,可等待目标出现后再返回。", "parameters": { "selector": {"type": "string", "required": true}, "wait_for": {"type": "string", "default": ""}, "timeout_ms": {"type": "integer", "default": 10000} } }, { "name": "fill_and_submit", "description": "在指定输入框填入文本并提交表单。", "parameters": { "selector": {"type": "string", "required": true}, "text": {"type": "string", "required": true}, "submit_selector": {"type": "string", "default": ""} } } ] }

这里有个细节:动作描述不要写“点击红色按钮”这种口语化描述,而要写“点击匹配选择器的页面元素”。Codex 需要的是明确的参数规则,不是视觉描述。

3.2 落一个能跑的actions.py示例

不重复造轮子的话,可以直接用 Selenium 做底层。先装依赖:

pip install selenium webdriver-manager

然后写一个最小实现:

import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def create_driver(): options = webdriver.ChromeOptions() options.add_argument("--headless=new") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") driver = webdriver.Chrome(options=options) return driver def goto(driver, url, wait_seconds=3): driver.get(url) time.sleep(wait_seconds) return {"success": True, "title": driver.title, "url": driver.current_url} def extract_text(driver, selector="body"): element = driver.find_element(By.CSS_SELECTOR, selector) return {"success": True, "text": element.text} def click(driver, selector, wait_for="", timeout_ms=10000): element = WebDriverWait(driver, timeout_ms / 1000).until( EC.element_to_be_clickable((By.CSS_SELECTOR, selector)) ) element.click() if wait_for: WebDriverWait(driver, timeout_ms / 1000).until( EC.presence_of_element_located((By.CSS_SELECTOR, wait_for)) ) return {"success": True, "clicked": selector} def fill_and_submit(driver, selector, text, submit_selector=""): input_element = driver.find_element(By.CSS_SELECTOR, selector) input_element.clear() input_element.send_keys(text) target = submit_selector or selector driver.find_element(By.CSS_SELECTOR, target).submit() return {"success": True}

这套代码背后的思路是:先把最容易出错的等待逻辑收拢到函数内部,Codex 调用时不需要关心 Selenium 的显式等待怎么写,只要给参数。

3.3 Codex 调用 Skill 的最小示例

Skill 文件放好后,在 Codex 任务里可以直接描述目标,让它从browser-actions中调用动作。一个典型任务是:

用 browser-skill 打开 https://example.com,获取页面文本,并截图保存到 output/example.png。

Codex 会根据skill.json的描述匹配gotoextract_text,甚至会和 actions.py 里预置的其他方法组合。但有一点要注意:Codex 的版本不同,Skill 的加载方式也有差异。我这里给出的结构是我本地验证过的通用组织方式,如果你的 Codex 版本对 Skill 有专门的目录约定,优先按它自带的模板来调整。

如果你只是手写一个提示词,把skill.json的内容直接贴进 Codex 的任务描述里,也能达到一部分效果。但这种做法的可维护性很差,动作一多,提示词会变得很长,反而比直接用 Skill 更费 Token。能装成正式 Skill 结构,就不要图省事塞提示词。

4. 接入实际工作流:从单任务到批量任务

4.1 单任务验证是第一步,不要直接上批量

Skill 写完后,第一件事不是让它一次处理几十个 URL,而是先跑通单条任务。我用一个本地测试页面做验证时,会按这个顺序看:

  1. 打开页面是否成功。
  2. 页面标题和 URL 是否正确返回。
  3. 需要点击的元素是否有明确的唯一选择器。
  4. 表单提交后页面是否出现预期结果。
  5. 截图输出路径是否存在,文件是否生成。

任何一步失败,都要先回到 Skill 本身排查,而不是继续堆任务。很多时候问题出在页面选择器不唯一、页面结构变化、未登录状态导致跳转,这些都不是 Codex 的问题。

单任务跑通后,再写批量逻辑。批量任务的核心不是并发数,而是输入列表、输出命名和失败重试。默认情况下,我建议先用 for 循环顺序处理,不要一上来就开多线程或异步并发。浏览器自动化任务和普通 API 请求不一样,每个任务都需要独立浏览器上下文,并发开太大,机器内存、CPU、浏览器实例都会扛不住。

4.2 批量任务里的 Token 策略

批量处理时,Token 消耗的主要来源不再是单个动作的解析,而是 Codex 每轮都要读取任务结果、判断下一步。如果要处理 20 个网址,最省 Token 的做法是让 Codex 只做一次任务规划,然后用脚本循环执行,而不是让 Codex 对每个网址都重新推理一遍。

一个可行的分工是:

  • Codex 负责生成 URL 列表和输出目录。
  • Python 脚本循环调用 Skill 里的动作。
  • 每处理一个 URL,记录成功或失败,结果写入日志。
  • Codex 只在最后读取日志,汇总失败项。

这样 Codex 参与推理的次数会大幅减少。如果每个 URL 都要 Codex 亲自操作,Token 消耗会随着任务数量线性增长,完全失去了 Skill 的意义。

4.3 输出命名和失败重试

处理批量任务时,最容易翻车的不是网页打不开,而是输出文件名冲突。建议输出文件名里带上任务序号或 URL 的哈希值,避免覆盖:

output_name = f"{index}_{hashlib.md5(url.encode()).hexdigest()[:8]}.png"

失败重试不要无限重试,控制在 2 到 3 次。每次重试前加一个短延时,同时记录失败原因。重试仍然失败的,把 URL 和错误信息写进一个failed.txt,最后统一给 Codex 看。这样才能判断是网站本身不可达,还是 Skill 里的选择器需要更新。

5. 性能判断:Token 省了多少、速度提升了多少,不能靠感觉

5.1 Token 消耗怎么测

不看 Token 用量就评估 Skill 的效果,很容易被“感觉快了”带偏。最简单的方法是分别跑两个任务:一个用默认方式让 Codex 直接操作浏览器,另一个用 Skill。输入同样一批网址,输出同一套结果,然后对比 Token 用量。

在我本地测试中,同样处理 5 个网页并截图,默认方式的 Codex 会频繁读取页面文本和 DOM 信息,Token 消耗明显偏高。使用 Skill 之后,模型主要处理的是动作参数和返回结果,Token 主要集中在任务描述和结果日志上。不同任务之间差异会很大,但方向是一致的:把动作封装得越厚,模型需要处理的中间上下文就越少。

5.2 速度提升怎么判断

速度不能只看总耗时,还要看模型推理轮数。Codex 操作浏览器时,如果总耗时 3 分钟,其中模型生成代码和解析页面可能占了 2 分半,浏览器本身只跑了 30 秒,那优化的重点应该在推理轮数和上下文长度。Skill 的价值就是把这里的推理轮数压下来。

你可以打开 Codex 的日志,观察一次任务里模型输出了多少轮。轮数越多,Token 消耗越高,执行时间也越长。Skill 用得好,任务轮数会明显减少,尤其是在重复动作比较多的场景。

5.3 稳定性比速度更重要

浏览器自动化里,稳定性通常比速度更有价值。一次跑通 20 个页面,比一次跑通 5 个但波动很大更有实际意义。稳定性可以从三个维度看:

  • 成功率:成功任务数除以总任务数。
  • 失败原因分布:是超时、选择器找不到、还是登录失效。
  • 可重复性:同一批任务间隔一段时间后再跑,结果是否一致。

如果成功率低于九成,先不要优化 Token,先把失败原因找出来。很多时候是 Skill 里页面等待不够,或者是页面结构偶发变化。

6. 常见报错、坑点和排查顺序

6.1 Codex 找不到 Skill 或动作描述失效

这个问题常见于第一次安装完 Codex,Skill 目录没有放对位置。排查顺序是:

  1. 确认 Skill 目录路径是否符合 Codex 当前版本的要求。
  2. 确认skill.json里的name字段是否清晰。
  3. 用最简单的动作让 Codex 调用一次,比如goto,看是否能正确匹配。
  4. 如果仍然匹配不到,把skill.json的内容精简后放进 Codex 的附加上下文里,先验证动作本身能跑通,再回头检查目录结构。

我遇到过很多次“动作没问题但 Codex 不调用”的情况,最后发现是描述里没有写清楚使用场景。描述写得太抽象,Codex 不知道什么时候该用。

6.2 浏览器驱动报错和页面元素找不到

这类问题要按环境、页面、等待三个层次排查:

  • 环境层:确认浏览器版本和 driver 版本是否匹配。用webdriver-manager能省很多事,但第一次运行时要能正常下载驱动,这里对网络环境有要求。
  • 页面层:打开页面后,先用extract_text获取页面文本,确认页面实际加载内容。很多时候是页面跳到了登录页、验证页或空白页。
  • 等待层:检查是等待超时,还是元素根本不在 DOM 里。如果元素是动态加载出来的,CSS 选择器参数要改成等待目标元素出现后再点击。

还有一个容易踩的坑:选择器不过唯一。页面上可能有多个按钮的 class 相同,Codex 传给 Skill 的选择器命中多个元素,Selenium 默认点击第一个,结果可能点错。能加id就用id,没有id就要用更精确的 XPath。

6.3 登录态、Token 状态相关报错

这类报错和 Skill 本身无关,常见于访问需要登录的页面时,登录信息没有持久化,或者访问目标提示 token 过期、地域访问限制。遇到这种情况,不要在 Skill 里硬写“模拟登录绕过”之类的逻辑,合规的做法是:

  • 先确认目标页面在当前网络和账号条件下是否能正常访问。
  • 登录类任务建议使用已保存的浏览器 Session,或使用官方提供的自动化测试账号。
  • 如果页面返回 token 相关错误,优先检查账号状态、Cookie 是否过期、请求是否带上必要的会话信息。
  • 如果目标站点明确限制某些地区的访问,应该尊重站点的访问策略,而不是写脚本绕过。

这里最要避免的是把 Skill 写成一个“专门用来规避限制”的工具,这种方向不论从合规角度还是从稳定性角度都不建议做。

6.4 并发开太高导致资源被占满

批量跑浏览器自动化时,进程卡住、页面打不开、截图全黑,很多时候不是代码逻辑问题,而是浏览器实例开太多,内存和 CPU 被耗尽。判断方法很简单:看任务期间的 CPU 占用和内存占用。如果内存长期在 80% 以上,就该降低并发数量。

一个通用建议是:顺序执行的稳定性优于并发执行。必须并发时,把并发数控制在 2 到 3 个浏览器实例以内,并且给每个实例独立配置 temp 目录和 user-data-dir,避免浏览器锁冲突。

7. 更进一步:Skill 的复用和扩展方向

7.1 从一个网站扩展到一类网站

很多人的浏览器自动化需求不是只跑一个站点,而是跑一类站点。比如下载页面截图、抓取页面正文、批量查询数据。这类需求可以把动作再抽象一层,从“网页操作”升级成“站点任务”。

例如做批量查询时,可以写一个search_and_extract动作,参数只有 URL 模式、关键词和结果选择器。Codex 只需要调用一个动作,就可以完成搜索、等待、提取三个步骤。这样做的好处是 Codex 的 Token 消耗进一步降低,代码也更稳定,缺点是需要针对具体站点的页面结构做适配。

7.2 和其他工具配合使用

Codex 不是唯一能用 Skill 思路的智能体,Claude Code 也有类似机制。如果你之前给 Claude Code 写过 Skill,可以借鉴同样的动作封装思路,把浏览器操作部分抽成独立的 Python 模块,让不同的智能体共用同一个函数库。这样维护成本更低。

还可以把浏览器 Skill 和外部数据源配合。比如从文件里读取 URL 列表,处理完成后把结果写回 CSV 或数据库。Codex 只负责组织流程,真正跑重复动作的是封装好的脚本。

7.3 什么情况下不需要写 Skill

不是所有浏览器任务都值得写 Skill。如果你只需要手动跑一次自动化脚本,写完即弃,那直接让 Codex 生成普通脚本就可以。只有当任务会反复执行、动作组合相对固定、Token 成本开始成为顾虑时,才值得投入时间做 Skill。

我个人的判断标准是:同一个任务跑超过三次,并且每次执行步骤几乎一样,就值得把它 Skill 化。如果每次都面对完全不同的页面结构,Skill 反而会成为负担,因为你要不断调整选择器和等待逻辑。

最后说一个不容易察觉但很关键的点:Skill 的维护不是“写完就不管”。网页结构会变,选择器会失效,等待时间需要调整。真正长期使用的 Skill,应该在某次任务失败后主动去改动作参数,而不是反复让 Codex 重试同一个动作。把这个习惯建立起来,Codex 操作浏览器的场景才会越来越省心。

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

音频剧制作技术全解析:从对话处理到环境音效设计

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

作者头像 李华
网站建设 2026/9/6 1:30:48

美的MRC828-3000反渗透净水器实测:从安装到滤芯维护全指南

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

作者头像 李华
网站建设 2026/9/6 1:30:07

PFC电感计算到验证全流程:从原理到实测

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

作者头像 李华
网站建设 2026/9/6 1:29:53

高中物理电场知识点归纳:从场强电势到题型破解

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

作者头像 李华
网站建设 2026/9/6 1:29:51

无线电规则第3卷决议与建议实用指南:703页文件的体系与查阅法

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

作者头像 李华