尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

把任意网站变成AI Agent的CLI:大幅降低token消耗的工程实践

把任意网站变成AI Agent的CLI:大幅降低token消耗的工程实践 AI Agent 直接访问网站时最容易忽略的成本是 token。一个普通页面上真正有用的功能入口可能只占全部 HTML 的一小部分但模型仍然要为整份文档付费。更麻烦的是HTML 是给人眼渲染设计的不是给模型调用设计的。页面上的按钮、表单、跳转规则看起来直观但对大语言模型来说从一堆标签、属性和内联脚本里推理出“下一步该做什么”既浪费上下文也容易出错。最近出现一类做法把任意网站改造成 AI Agent 可以直接调用的 CLI。与其让 Agent 抓取 HTML 并自行理解页面结构不如先把网站的功能抽象成一组命令。Agent 只需要知道命令名、参数和返回格式就能像使用命令行工具一样操作网站。标题里展示的项目声称相比直接把 HTML 塞给模型这种方式可以减少约 142 倍的 token 消耗。这个数字是否在任何站点都成立另说但背后的拆解思路值得完整过一遍。下面从原理、实现、测量、排错四个部分梳理这条技术路线并给出一个最小可运行的 site2cli 示例。1. 为什么 AI 代理读 HTML 是一件既贵又脆的事情1.1 一个普通 HTML 页面里到底有哪些 token 消耗点当 Agent 请求一个网页时它拿到的往往是一整份浏览器渲染素材而不是人类最终看到的内容。一个典型的 HTML 页面大致包含这些部分!DOCTYPE html、html、head、meta等文档结构标签。title、描述、关键字等 SEO 元信息。style或内联样式。script或内联 JavaScript。导航菜单、页脚、侧边栏、推荐位。Cookie 弹窗、广告位、埋点脚本。真正与业务相关的正文、表单、按钮和链接。下面这段 HTML 是很典型的结构!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title示例站点/title style.nav { display: flex; } .footer { color: gray; }/style /head body nav a href/home首页/a a href/products商品/a a href/about关于/a /nav form action/search methodget input typetext nameq required button typesubmit搜索/button /form footer备案信息与版权声明/footer /body /html这段代码里对 Agent 完成具体操作有直接价值的只有标题、表单字段和提交地址。其余部分都只是为了让浏览器把页面画出来。模型要为每个标签、每个属性、每条样式规则付 token而这些内容对“执行操作”几乎没有任何贡献。下面是一组典型量级实际数值取决于页面复杂度和 tokenizer但能说明问题内容块字符量约token 量约对完成任务的贡献DOCTYPE / head / meta2 KB500 - 800几乎无CSS 与内联样式10 KB2500 - 3500几乎无JavaScript 与埋点20 KB5000 - 7000几乎无导航、页脚、推荐位8 KB2000 - 3000少量正文内容区5 KB1200 - 1800核心表单与操作入口3 KB700 - 900核心合计48 KB12000 - 17000-也就是说一个业务功能很简单的页面Agent 也可能要吃掉一万以上的 token只为了在底部找到那个form。1.2 token 不只是钱的问题它还压缩了模型的思考空间token 消耗首先直接影响费用。按当前主流模型服务商的定价口径每百万输入 token 的成本从几元到几十元不等虽然单个页面看起来不贵但 Agent 通常要连续访问多个页面一个任务下来总量就上去了。更大的问题是上下文窗口和速率限制。上下文窗口是有限的Agent 需要把系统提示、历史对话、工具输出、中间推理都塞进同一个上下文。如果每个页面的 HTML 都要占掉 1 万到 2 万 token那留给模型真正“思考”的空间就很小连续访问五个页面后窗口基本满了。速率限制方面很多 Agent 运行环境按 TPMtokens per minute限制用量通常指每分钟输入 token 加输出 token 的总和。同样一分钟内如果 Agent 每次都要读取大段 HTML它能完成的工具调用次数就会显著下降任务吞吐量自然上不去。所以减少网站输入 token 不只是省钱也是在给 Agent 腾出上下文空间、提高任务成功率和执行速度。1.3 方向把“页面”变成“接口”回到根本问题Agent 访问网站不是为了“看页面”而是为了“完成操作”。它需要知道的是这个网站能做什么。每个操作需要什么参数。调用后返回什么结果。这三件事完全可以用一组命令描述清楚。与其把整份 HTML 交给模型不如先把网站抽象成一个“命令表”Agent 只读命令表和命令输出。这也是“把任意网站变成 CLI”的核心思路。标题里那个 142 倍的数字需要在具体站点上重新测量但它背后的优化方向是对的去掉与任务无关的渲染信息保留与操作相关的接口信息。2. 把网站抽象成 CLI核心机制和设计原则2.1 CLI 对 AI Agent 意味着什么CLI 对 Agent 来说是一种机器友好接口。它通常包含三个部分命令名表达一个动作比如search_products。参数约束输入比如关键词、页码、排序方式。返回值通常是结构化文本比如 JSON。Agent 拿到命令表后相当于知道了一个网站的“操作手册”。它不再需要从一堆div里猜这个按钮是什么意思而是直接选择命令、填参数、解析输出。一个最小的 Agent 提示词可以设计成这样你是一个网站操作代理。你可以使用以下命令 - search_products(keyword: string, page: integer): 按关键词搜索商品返回商品列表。 - get_product_detail(product_id: string): 获取商品详情。 - list_categories(): 获取商品分类列表。 输出必须是结构化 JSON。执行命令失败时先读取错误信息再决定是否重试。这个提示词非常短却包含了完成一组网站操作所需的全部接口信息。相比塞进整页 HTML模型要做的事情简单得多。2.2 网站的哪些能力可以映射成命令不是每个页面都适合做成一堆命令。需要先按网站能力分类再决定映射方式。下面是比较常见的映射关系网站能力HTML 中的表现建议命令搜索form action/search methodgetsearch(...)分页下一页链接、页码参数list(..., pageN)内容详情详情页链接和正文区块get_detail(id)表单提交form methodpostcreate_xxx(...)、update_xxx(...)状态查询页面上的状态区域get_status()登录态相关操作Cookie、Session、CSRF 字段login()、logout()映射时要注意粒度。如果一个命令太粗Agent 拿不到足够的细节比如“获取整个首页”仍然会返回大量无关信息。如果一个命令太细比如把每个链接都拆成一个命令命令表本身会变得很长token 优势就消失了。合理的粒度是一个命令对应一个“可复用的业务操作”而不是对应一个 DOM 节点。2.3 与 MCP、Agent CLI 标准的关系这套思路和 MCPModel Context Protocol是天然互补的。MCP 提供了一种标准方式让模型应用通过“工具”调用外部能力。把网站变成 CLI 后可以很自然地再把命令包装成 MCP 工具这样 Claude Code CLI、Codex CLI 这类支持工具调用的 Agent 环境就能直接使用。实际落地时不一定非要实现完整的 MCP Server。可以先做一层本地 CLI让 Agent 通过命令调用再把命令表翻译成 MCP tool 定义。两种方式的区别在于CLI 适合一个人或一个脚本在终端里调用MCP 适合把工具注册到 Agent 的运行时里。无论用哪种方式核心都是“先抽象命令表再做协议适配”。社区里关于 Agent CLI 通用标准的讨论也很多这说明大家已经意识到Agent 不应该被绑定在某一种专有接口上。命令表加协议适配层是当前比较务实的一种做法。3. 动手做一个最小版 site2cli环境与项目结构3.1 环境准备和依赖为了快速演示这里用 Python 实现。Python 生态里抓取 HTML、解析 DOM、统计 token 都有现成库适合做原型验证。需要准备的环境Python 3.10 或更高版本。一个可以正常访问的目标网站建议先从静态内容较多的站点开始。pip 安装依赖pip install requests beautifulsoup4 lxml tiktoken如果你更熟悉 Node.js也可以用 cheerio 替代 BeautifulSoup、用 js-tiktoken 替代 tiktoken思路完全一样。下面示例用于说明实现方法实际项目要结合自己的包名、路径和依赖版本调整。3.2 项目结构和文件职责先设计一个最小项目结构site2cli/ ├── schemas/ │ └── commands.json ├── fetchers/ │ └── html_fetcher.py ├── parsers/ │ └── html_parser.py ├── cli/ │ └── dispatch.py ├── metrics/ │ └── token_counter.py └── main.py各文件职责如下schemas/commands.json网站功能的命令表是整条链路的中心。fetchers/html_fetcher.py负责抓取 HTML处理编码、超时、重试。parsers/html_parser.py从 HTML 中提取表单、输入框、链接生成命令候选。cli/dispatch.py把命令表转成 argparse 子命令真正执行请求。metrics/token_counter.py统计 HTML 与命令表的 token 差异。main.py入口串联整个流程。4. 核心实现从 HTML 到命令表再到可调用 CLI4.1 定义命令表 schema命令表是整个方案的核心。下面是一个最小但完整的命令表示例{ commands: [ { name: search_products, description: 按关键词搜索商品返回商品列表, method: GET, endpoint: https://example.com/search, arguments: [ { name: keyword, type: string, required: true, description: 搜索关键词 }, { name: page, type: integer, required: false, default: 1, description: 页码从 1 开始 } ], params_mapping: { keyword: q, page: page } }, { name: get_product_detail, description: 获取商品详情, method: GET, endpoint: https://example.com/detail, arguments: [ { name: product_id, type: string, required: true, description: 商品 ID } ], params_mapping: { product_id: id } } ] }字段含义如下字段作用注意点name命令名Agent 调用时使用用动词开头避免歧义description告诉 Agent 这个命令什么时候该用要写清输入含义和影响范围methodHTTP 方法GET 与 POST 的请求体处理不同endpoint请求地址建议写完整 URL避免拼接错误arguments参数定义区分必填和非必填params_mapping参数名到请求参数的映射网站字段名可能和命令名不一致这个 JSON 本身就是 Agent 上下文里最值钱的部分。它的体积远小于页面 HTML却包含了 Agent 完成操作所需的全部接口信息。4.2 从 HTML 表单和链接自动提取命令写一个解析器把页面里的form转成命令候选。下面代码用于说明思路from bs4 import BeautifulSoup def extract_forms(html_text: str) - list[dict]: soup BeautifulSoup(html_text, html.parser) commands [] for form in soup.find_all(form): name form.get(id) or form.get(name) or submit_form method (form.get(method) or get).lower() action form.get(action) or / arguments [] for field in form.find_all([input, select, textarea]): field_name field.get(name) or field.get(id) if not field_name: continue arg { name: field_name, required: field.get(required) is not None, default: field.get(value) or , } arguments.append(arg) commands.append({ name: name, method: method, endpoint: action, arguments: arguments, params_mapping: {arg[name]: arg[name] for arg in arguments}, }) return commands这段代码做了三件事遍历所有form。从action、method和表单字段中提取请求地址、方法和参数。生成一个结构化的命令候选。required属性在 HTML 中只要存在就算必填所以代码里用field.get(required) is not None判断。要注意自动提取出来的命令候选往往需要人工修正比如name可能没有语义、endpoint可能是相对路径、某些参数实际上是从隐藏字段带入的。链接和分页也可以类似处理。比如查找包含page或pagination特征的链接生成list(...)命令。自动提取适合作为第一版脚手架生产环境仍然建议人工评审。4.3 把命令表转成可调用的 CLI有了命令表下一步就是把它变成真正能执行的 CLI。这里用 argparse 动态生成子命令import argparse import json import requests def build_parser(schema: dict) - argparse.ArgumentParser: parser argparse.ArgumentParser(progsite2cli) subparsers parser.add_subparsers(destcommand, requiredTrue) for cmd in schema[commands]: sub subparsers.add_parser(cmd[name], helpcmd.get(description, )) for arg in cmd.get(arguments, []): options {help: arg.get(description, )} if arg.get(required): options[required] True if arg.get(default) not in (None, ): options[default] arg[default] if arg.get(type) integer: options[type] int sub.add_argument(-- arg[name], **options) return parser def run_command(schema: dict, args: argparse.Namespace) - dict: cmd next(c for c in schema[commands] if c[name] args.command) params {} for arg in cmd.get(arguments, []): value getattr(args, arg[name], None) if value is None: value arg.get(default) params[arg.get(params_mapping, arg[name])] value endpoint cmd[endpoint] if cmd.get(method, GET).upper() GET: resp requests.get(endpoint, paramsparams, timeout10) else: resp requests.post(endpoint, dataparams, timeout10) return { status: resp.status_code, command: args.command, data: resp.text[:2000], } if __name__ __main__: with open(schemas/commands.json, r, encodingutf-8) as f: command_schema json.load(f) parser build_parser(command_schema) cli_args parser.parse_args() result run_command(command_schema, cli_args) print(json.dumps(result, ensure_asciiFalse))运行效果如下python main.py search_products --keyword laptop --page 2输出示例{status: 200, command: search_products, data: 搜索结果 HTML 片段}这个示例里有两个明显简化一是参数校验只处理了必填和默认值没有处理类型错误二是返回内容直接截取了 HTML 前 2000 个字符实际项目应该让命令表声明“返回字段白名单”由解析器提取结构化结果。设计命令表时输出结构应当和输入结构一样明确这样 Agent 才能稳定解析。4.4 用 token 计数器量化前后差异要用数据验证优化效果需要统计前后 token 量。tiktoken 是一个可用的统计工具import tiktoken def count_tokens(text: str, model: str gpt-4o) - int: enc tiktoken.encoding_for_model(model) return len(enc.encode(text))写一个简单脚本对比整页 HTML 和命令表的 token 量import requests from metrics.token_counter import count_tokens url https://example.com/list html_text requests.get(url, timeout10).text html_tokens count_tokens(html_text) with open(schemas/commands.json, r, encodingutf-8) as f: schema_text f.read() schema_tokens count_tokens(schema_text) print(HTML tokens:, html_tokens) print(Schema tokens:, schema_tokens) print(Ratio:, round(html_tokens / max(schema_tokens, 1), 1))不同模型的 tokenizer 不同同一个文本用 gpt-4o 和 Claude 的 tokenizer 统计结果会有差别。测量时先固定一个模型口径前后对比才有效。5. 验证效果如何测量 token 节省倍数5.1 先搞清楚 142 倍这类数字是怎么算出来的这类“比 HTML 少 N 倍 token”的说法本质上是一个比值节省倍数 直接把页面 HTML 交给 Agent 的开销 / 通过 CLI 命令完成同一任务的开销分子可以是“整页 HTML 的 token 数”分母可以是“命令表的 token 数 命令调用返回结果的 token 数”。分母选什么基准会直接影响最终倍数。举例说明任务搜索商品并查看前两页结果。HTML 方案每次访问都把整页 HTML 塞进上下文两次大约 3 万 token。CLI 方案第一次读命令表约 300 token两次调用输出约 1000 token。比值30000 / 1300约 23 倍。如果页面里的 CSS、JS、导航和埋点特别多而业务入口很少比值会更高。142 倍这个数字说明原始页面的信息密度偏低而命令表又设计得比较紧凑。它不是通用结论而是某一类页面的实测结果。做自己的项目时要用同一套任务基准重新测量。5.2 一个可复现的测量实验建议在本地做一个可复现实验。选择目标网站的一个列表页统计三种输入原始 HTML 的 token 数。命令表的 token 数。一次实际命令调用返回结果结构化 JSON的 token 数。然后对比输入内容token 量示例说明整页 HTML15432包含样式、脚本、导航、正文命令表642只包含命令名、参数和描述单次命令返回 JSON830只包含业务字段在这个示例里HTML 与命令表加返回结果的比值约为 15432 / (642 830)约 10 倍。如果页面更重、返回结果裁剪更严格比值还可以更高。测量脚本要固定以下条件使用同一个 tokenizer 模型。同一个页面 URL。同一个任务目标。返回结果裁剪规则一致。否则测量结果没有可比性。5.3 从实验到真实场景还要看总 token 和 TPM比值是单一指标真实场景要看“完成一个任务的总 token 数”和“成功率”。CLI 方案虽然降低了单次输入 token但如果命令设计得不好Agent 要多调用几次才能完成任务总 token 反而可能上升。另外要考虑 TPM。Agent 运行环境通常限制每分钟输入加输出 token 的总量。HTML 方案里一个页面就吃掉 1.5 万 token模型还没来得及输出多少内容这一分钟的预算就用完了。CLI 方案输入小留给输出和反馈的预算更多单位时间内能执行的步骤也更稳定。所以验证时至少记录三个指标单次请求的平均 token 数。完成一个任务的总 token 数。任务成功率。只有这三个指标一起看才能判断是否真的值得切换到 CLI 抽象。6. 常见坑与排查路径6.1 抓取到的 HTML 里没有正文动态渲染页面如果目标网站是前端渲染的 SPA直接用requests.get()拿到的可能只是空壳 HTML正文和功能入口由 JavaScript 动态生成。此时自动提取命令会发现form为空或者endpoint缺失。排查路径先用浏览器打开页面右键查看源代码确认正文是否出现在原始 HTML 里。如果原始 HTML 里没有说明是动态渲染。查找网站是否提供数据 API很多 SPA 的列表和详情来自后端 JSON 接口。如果必须渲染页面可以引入 Playwright 或 Puppeteer 做无头渲染但这部分开销属于基础设施成本不影响 token 统计。预防建议是在解析前先检查 HTML 长度和关键标识避免生成空命令表。6.2 表单提交 403CSRF、Session 和字段映射POST 表单最常见的错误是提交时返回 403。原因通常是 CSRF token 缺失、Cookie 未保持、或者请求字段名和实际不一致。排查路径先看目标页面form里有没有隐藏输入字段比如_token、csrfmiddlewaretoken。用requests.Session()保持 Cookie先 GET 一次页面再从 HTML 中提取 token再 POST。对比浏览器 Network 面板里真实请求的请求头和请求体确认参数名。确认Content-Type默认application/x-www-form-urlencoded和 multipart 处理方式不同。下面是一个处理 CSRF 的最小示例import requests from bs4 import BeautifulSoup session requests.Session() page session.get(https://example.com/add, timeout10) soup BeautifulSoup(page.text, html.parser) token soup.find(input, {name: _token})[value] resp session.post( https://example.com/add, data{_token: token, title: example}, timeout10, ) print(resp.status_code)这类问题说明“自动提取命令”不能只解析字段名还要考虑会话状态和动态令牌。6.3 token 统计两边对不上有时自己统计的 token 数和模型 API 返回的 usage 字段对不上。常见原因是两边使用的 tokenizer 不同。tiktoken 按模型选择编码Claude 使用另一套 tokenizer开源模型又可能用别的分词方式。中英文混排时不同 tokenizer 的差异会更明显。排查路径确认统计脚本使用的模型名是否和 API 调用一致。如果 API 返回了 usage 字段以服务商返回的 input_tokens 为准。文档中记录统计模型避免读者复现时产生误解。预防建议是写一个统一的 token 统计函数所有对比都走同一套逻辑。6.4 schema 过期、命令粒度失控网站改版后命令表里的endpoint、参数名、返回字段可能失效。另一个常见问题是命令粒度失控要么把“获取首页”这种大命令保留下来Agent 仍然拿回大量噪声要么把每个链接都拆成命令命令表本身膨胀到失去意义。排查路径定期抓取目标页面检查页面中的表单和链接是否与命令表一致。对命令表建立版本管理每次修改记录变更原因。监控命令调用失败率失败率升高时优先检查 schema。对比命令表 token 量和 HTML token 量如果比值接近 1说明抽象失败。7. 生产环境落地的建议和可复用清单7.1 命令表设计规范经过多次实践命令表设计有以下几条可执行规范命令名统一用动词开头list、get、search、create、update、delete。description要写清“什么时候用、会产生什么影响”但不要写成大段文档。必填参数尽量少能通过默认值解决的不要交给 Agent 判断。返回结果不要返回整段 HTML要返回字段白名单。列表类命令必须支持分页和数量上限避免单次返回过多 token。命令表要版本化发布前和网站页面逐项核对。下面是一个返回结构设计的示例{ status: ok, command: search_products, count: 2, items: [ {id: p1001, title: laptop A, price: 4999}, {id: p1002, title: laptop B, price: 5999} ], next_page: 2 }字段越少Agent 解析越稳定token 也越省。7.2 安全与权限边界把网站暴露成 CLI 后Agent 能直接发起真实请求因此要特别注意操作边界。不要自动把所有 POST 表单都暴露成命令尤其是删除、转账、改名、发布等不可逆操作。写操作命令要有确认机制比如要求 Agent 在调用前先输出“将要执行的操作”供人工确认。输入参数要做校验和长度限制不能直接用用户输入拼接 URL 或请求体。尽量使用只读 Token 或最小权限账号不要让 Agent 拿到管理员凭证。对目标域名做限流避免命令被高频调用压垮目标站点。所有调用记录日志至少包含命令名、参数、状态码、耗时和 token 用量。7.3 学习环境与生产环境的差异维度学习 / 演示环境生产环境schema 生成自动提取后手动修正人工评审 版本管理认证免登录或测试账号会话管理、密钥托管返回结果直接打印 HTML 片段结构化 JSON 字段白名单限流与重试不关注按域名限流、指数退避安全本地演示输入校验、操作审批监控无token 用量、失败率、告警7.4 落地前检查清单[ ] 命令表与真实页面功能逐项核对。[ ] 认证、Cookie、CSRF 流程已处理。[ ] token 统计口径统一并记录使用的模型。[ ] 返回字段已裁剪列表接口已分页。[ ] 超时、重试、限流已配置。[ ] 写操作有人工确认机制。[ ] 调用日志和 token 用量已记录。[ ] schema 已纳入版本管理。先从一个小而具体的站点开始练习比如一个商品列表页或文档站把它的搜索、分页、详情三个功能做成命令再对比 HTML 方案和 CLI 方案在同一次任务里的 token 总量。然后把它接到 MCP 或 Agent 运行环境里观察成功率变化。当你能清晰回答“这个命令表为什么这么设计、返回结构为什么裁剪成这些字段”时这套思路才算真正掌握。
返回列表