news 2026/4/25 2:55:25

Page Agent:基于文本化DOM的网页AI智能体实现原理与应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Page Agent:基于文本化DOM的网页AI智能体实现原理与应用

1. 项目概述:一个活在网页里的GUI智能体

最近在折腾如何让AI更自然地与Web界面交互时,我遇到了一个非常有意思的开源项目——Page Agent。简单来说,它就是一个纯JavaScript库,能让你用自然语言直接控制当前打开的网页。想象一下,你对着一个复杂的后台管理系统说“帮我把上个月销售额超过10万的订单都导出成Excel”,然后它就能自动帮你点选筛选框、输入条件、找到导出按钮并点击。这听起来像是需要复杂后端服务和浏览器扩展才能实现的功能,但Page Agent的核心思路是:一切都在浏览器页面内完成

这个由阿里巴巴开源的库,其最大的魅力在于“轻量”和“直接”。它不需要你安装浏览器插件,也不需要启动一个无头浏览器(Headless Browser)服务,更不需要用Python写一堆爬虫脚本。你只需要在网页中引入一段JS代码,它就能利用大语言模型(LLM)理解你的指令,并操作网页的DOM(文档对象模型)来完成任务。这对于前端开发者、产品经理,或者任何想为自己的SaaS产品快速添加一个“AI副驾驶”功能的人来说,简直是一个“开箱即用”的利器。无论是想简化内部系统的操作流程,还是为残障人士提供语音控制网页的辅助功能,Page Agent都提供了一个极其优雅的解决方案。

2. 核心设计思路:为何“文本化DOM”是破局关键

2.1 摒弃传统自动化方案的沉重包袱

在Page Agent出现之前,要实现网页的自动化操作,主流路径无外乎以下几种,但各有各的“痛点”:

  1. Selenium/Puppeteer等无头浏览器方案:功能强大,但依赖后端服务,环境搭建复杂,运行资源消耗大,且难以与用户正在交互的真实网页上下文结合。
  2. 浏览器扩展(Chrome Extension)方案:可以操作真实浏览器页面,但需要用户手动安装,存在权限和安全提示,分发和更新成本高。
  3. 基于图像识别的RPA方案:需要截图、调用多模态大模型识别元素,响应慢、成本高,且受屏幕分辨率、样式变化影响大。

Page Agent选择了一条截然不同的路:完全基于文本的DOM操作。它不“看”网页的截图,而是直接“阅读”网页的DOM树和计算后的样式信息,将其转化为结构化的文本描述,送给大语言模型去理解。LLM根据这个文本化的“网页地图”和你的自然语言指令,规划出操作步骤(如:点击ID为‘submit-btn’的按钮),再由Page Agent执行对应的DOM API。

为什么这个思路更优?首先,它极度轻量,所有计算发生在用户本地浏览器中,无需额外的服务器进行图像处理。其次,它精准,直接操作DOM元素,避免了图像识别可能带来的坐标偏移或元素误判。最后,它隐私性更好,因为敏感的用户页面内容无需被截图并发送到远程服务器。

2.2 架构拆解:三层核心模块如何协同工作

理解Page Agent,可以把它看作一个在浏览器中运行的微型“感知-思考-执行”机器人。其核心架构可以分为三层:

  1. 感知层(Observer):负责收集当前网页的状态。这不是截图,而是通过MutationObserver监听DOM变化,并提取关键信息。它会构建一个精简的、富含语义的DOM树文本表示,包括:

    • 元素的标签名(如button,input)。
    • 关键属性(如id,class,name,type,placeholder,aria-label)。
    • 可见的文本内容。
    • 计算后的样式信息(如是否可见display: none,是否可点击pointer-events)。
    • 元素在视口中的大致位置信息(用于后续的滚动操作)。
  2. 思考层(Planner & Interpreter):这是LLM发挥作用的地方。Page Agent将感知层收集到的“网页状态描述”和用户的“自然语言指令”一起,构造为一个精心设计的Prompt,发送给配置好的LLM(如通义千问、GPT等)。LLM的任务是理解指令,并输出一个结构化的“操作序列”。这个序列不是自然语言,而是一种定义好的JSON格式的动作指令,例如{“action”: “click”, “args”: {“elementId”: “login-btn”}}

  3. 执行层(Actor):接收思考层下发的JSON指令,并将其转化为真实的浏览器DOM操作。它内置了一系列安全的“原子操作”,如:

    • click(element): 模拟点击。
    • type(element, text): 在输入框输入文本。
    • scroll(x, y): 滚动页面。
    • wait(ms): 等待。
    • extract(): 提取页面信息。 执行器会严格按顺序执行这些动作,并在每一步之后,触发感知层重新观察页面变化,形成闭环。

这种架构的优势在于清晰的职责分离和可替换性。例如,你可以轻松更换“思考层”所使用的LLM提供商(只需修改API Key和Base URL),而无需改动感知和执行逻辑。

3. 从零开始集成与实战:打造你的第一个网页智能体

3.1 环境准备与两种集成方式

Page Agent提供了极其灵活的集成方式,你可以根据场景选择最快上手的CDN引入,或者更适合现代前端工程的NPM包引入。

方案一:CDN快速体验(用于原型验证)

这是最快捷的方式,适合在静态页面、Demo或简单的工具页面中快速集成。Page Agent甚至提供了一个内置了免费测试API的Demo版本,让你无需自己准备LLM API Key就能立即体验。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>My Page with AI Agent</title> </head> <body> <h1>欢迎使用智能助手</h1> <button id="demo-btn">这是一个测试按钮</button> <input type="text" placeholder="试试让我在这里输入..." id="demo-input"> <div id="output"></div> <!-- 引入 Page Agent Demo 脚本 --> <script src="https://cdn.jsdelivr.net/npm/page-agent@1.7.1/dist/iife/page-agent.demo.js" crossorigin="true"></script> <script> // 脚本加载后,全局变量 `PageAgent` 可用 document.addEventListener('DOMContentLoaded', async () => { const agent = new PageAgent({ language: 'zh-CN', // 设置指令语言为中文 }); // 示例:让Agent点击按钮并在输入框打字 try { await agent.execute('首先,点击那个写着“这是一个测试按钮”的按钮。'); await agent.execute('然后,在输入框里输入“Hello, Page Agent!”。'); document.getElementById('output').innerHTML = '<p>✅ 任务执行完毕!</p>'; } catch (error) { console.error('Agent执行出错:', error); document.getElementById('output').innerHTML = `<p>❌ 出错: ${error.message}</p>`; } }); </script> </body> </html>

重要提示:Demo版本使用的免费测试API仅用于技术评估,有速率和次数限制,且不能用于生产环境。务必阅读并遵守其相关 条款 。

方案二:NPM安装(用于生产或正式项目)

对于Vue、React、Next.js等现代前端项目,使用NPM包是更规范的选择。

# 在你的项目根目录下执行 npm install page-agent # 或使用 yarn/pnpm yarn add page-agent

安装后,你可以在组件或模块中按需引入:

// 在你的JS/TS文件中,例如 agent.js import { PageAgent } from 'page-agent'; // 初始化Agent,这里需要配置你自己的LLM API信息 const agent = new PageAgent({ model: 'qwen-plus', // 例如使用通义千问Plus模型 baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', // 阿里云灵积平台兼容模式端点 apiKey: 'sk-your-own-api-key-here', // 你的真实API Key,务必从环境变量读取,不要硬编码! language: 'zh-CN', // 可选:设置指令超时时间(毫秒) timeout: 60000, }); // 导出agent实例供其他模块使用 export default agent;

3.2 核心配置项详解与LLM选型

初始化PageAgent时,配置对象是关键。下面我详细拆解每个参数的意义和配置心得:

const agent = new PageAgent({ // 【必需】模型标识符。取决于你使用的LLM服务提供商。 model: 'gpt-4o-mini', // 【必需】LLM API的基础URL。指向你使用的服务商端点。 baseURL: 'https://api.openai.com/v1', // 例如OpenAI // baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', // 例如阿里云通义千问 // 【必需】API密钥。这是最重要的安全信息! apiKey: process.env.LLM_API_KEY, // 强烈推荐从环境变量读取 // 【必需】指令的自然语言。影响LLM对指令的理解和回复语言。 language: 'zh-CN', // 可选 'en-US', 'ja-JP' 等 // 【可选】最大重试次数。当单次操作失败(如元素未找到)时,Agent会尝试重新理解并执行。 maxRetries: 3, // 【可选】每次指令执行的超时时间(毫秒)。网络慢或任务复杂时需要调高。 timeout: 120000, // 2分钟 // 【可选】是否在控制台输出详细的调试日志,包括发送给LLM的Prompt和执行步骤。 verbose: true, // 【可选】自定义用于提取页面信息的CSS选择器。默认会提取所有可见元素,但你可以聚焦于主内容区以提升效率。 rootSelector: '#app-main, .content-area', // 【可选】高级配置:自定义LLM的调用函数。这给了你极大的灵活性,可以对接任何HTTP API或本地模型。 // fetchFn: async (prompt) => { // // 你的自定义调用逻辑,返回一个符合Page Agent预期的JSON响应 // const response = await myCustomLLMClient.chat(prompt); // return response; // } });

关于LLM选型的实战经验:

  • 精度优先(复杂任务):选择能力更强的模型,如gpt-4oqwen-max。它们在理解复杂指令、处理模糊描述(如“点击那个红色的大的按钮”)方面表现更好,但成本较高、速度可能稍慢。
  • 速度与成本优先(简单任务):选择轻量级模型,如gpt-4o-miniqwen-plus。对于明确的表单填写、按钮点击等任务,它们完全够用,且响应更快、费用更低。
  • 隐私与合规要求:如果需要数据不出境,应选择国内云服务商的模型,并确保baseURL指向国内端点。
  • 测试阶段:充分利用Page Agent提供的免费测试API(仅限Demo脚本)进行原型验证,避免在初期消耗自己的API额度。

3.3 基础操作与复杂任务编排

初始化好Agent后,就可以让它为你工作了。execute方法是核心。

基础单指令执行:

// 点击一个元素 await agent.execute('点击登录按钮'); // 或更精确的描述 await agent.execute('点击ID为submit的按钮'); // 在输入框输入文本 await agent.execute('在搜索框里输入“人工智能最新进展”'); // 滚动页面 await agent.execute('向下滚动两屏'); await agent.execute('滚动到页面底部'); // 等待 await agent.execute('等待3秒钟,直到页面加载完成'); // 提取信息 const result = await agent.execute('把当前页面所有文章的标题和链接提取出来,做成一个JSON数组'); console.log(result.extracted); // 提取的信息会在这里

复杂多步骤任务编排:

真正的威力在于用一句指令完成一连串操作。这完全依赖于LLM对指令的分解和规划能力。

// 示例:自动化一个简单的注册流程 try { const finalResult = await agent.execute(` 请帮我完成用户注册: 1. 找到“用户名”输入框,输入“test_user_${Date.now()}”。 2. 找到“邮箱”输入框,输入一个格式正确的测试邮箱。 3. 找到“密码”输入框,输入“SecurePass123!”。 4. 找到“确认密码”输入框,再次输入“SecurePass123!”。 5. 勾选“我已阅读并同意用户协议”复选框。 6. 最后,点击“立即注册”按钮。 完成后,告诉我是否看到了“注册成功”的提示。 `); if (finalResult.extracted?.includes('成功')) { console.log('✅ 注册流程自动化执行成功!'); } else { console.log('⚠️ 流程执行完毕,但未检测到明确成功提示。'); } } catch (error) { console.error('❌ 任务执行失败:', error); // 这里可以加入重试或fallback逻辑 }

与页面现有逻辑交互:

Page Agent并非运行在真空里,它可以与你页面原有的JavaScript逻辑配合。

// 假设页面上有一个触发搜索的函数 function performSearch(keyword) { // ... 你的搜索逻辑 console.log(`搜索关键词: ${keyword}`); } // 让Agent先操作DOM,然后你接管 await agent.execute('在顶部的搜索框输入“Page Agent文档”'); // Agent输入后,你可以手动触发搜索按钮点击,或者直接调用你的函数 document.querySelector('#search-btn').click(); // 或 performSearch('Page Agent文档'); // 另一种模式:你告诉Agent目标,让它去操作 const targetKeyword = '如何配置LLM'; await agent.execute(`在搜索框里输入“${targetKeyword}”然后点击搜索按钮`);

4. 高级特性与生态集成:突破单页限制

4.1 Chrome扩展:实现跨页面自动化

Page Agent的核心库专注于单个页面内的自动化。但很多真实场景涉及多个页面或标签页的跳转(例如:从商品列表页点进去查看详情,再跳回)。为此,Page Agent项目提供了一个可选的Chrome扩展

它的工作原理是:扩展作为一个“中枢”,运行在浏览器后台。你的主网页中的Page Agent实例可以通过扩展提供的API,向其发送指令。扩展则拥有更高的权限,可以操作浏览器中所有的标签页(Tab),包括打开新页、切换页签、跨页面执行任务等。

集成步骤:

  1. 安装扩展:从Chrome Web Store(如果已上架)或项目仓库手动加载未打包的扩展程序。
  2. 在网页中连接扩展:在你的主页面代码中,需要以特定方式初始化Agent,使其知道通过扩展通信。
// 在你的主页面脚本中 import { PageAgent } from 'page-agent'; // 配置中指定使用扩展通信 const agent = new PageAgent({ model: 'gpt-4o', baseURL: 'https://api.openai.com/v1', apiKey: 'your-api-key', language: 'en-US', // 关键配置:启用扩展模式 useExtension: true, // 或具体的扩展ID }); // 现在,指令可以跨越页面了 await agent.execute('打开一个新标签页,访问 https://github.com, 在搜索框搜索“page-agent”,然后打开第一个仓库结果。');

这个特性将Page Agent从一个“页面内助手”升级为了一个“浏览器级助手”,非常适合实现复杂的、流程固定的跨页面数据收集或操作任务。

4.2 MCP服务器(Beta):被更广泛的AI智能体生态调用

MCP(Model Context Protocol)是一个新兴的协议,旨在标准化AI应用与各种工具、数据源之间的连接。Page Agent的MCP服务器特性,可以将其变成一个标准的MCP工具

这意味着什么?这意味着任何兼容MCP的AI智能体平台或客户端(例如某些先进的AI桌面应用或工作流工具),都可以像调用一个普通函数一样,远程控制安装了Page Agent扩展的浏览器。

典型使用场景:你正在一个本地的AI智能体开发环境中工作,这个智能体可以编写代码、查询数据库。现在,通过MCP,你还可以直接命令它:“去我的浏览器里,打开公司内部报表系统,把今天的销售数据下载下来,然后分析一下。” Page Agent MCP服务器就充当了这个“手”和“眼”的角色。

配置简述(目前为Beta版)

  1. 确保Chrome扩展已安装并运行。
  2. 从项目代码中启动MCP服务器(通常是一个Node.js脚本)。
  3. 在你的MCP客户端配置中,添加这个服务器地址。
  4. 之后,你就可以在客户端的自然语言指令中,直接包含对浏览器的操作了。

这为Page Agent打开了更广阔的生态集成可能性,使其不再局限于Web前端,而是成为了整个AI智能体工作流中的一个强大执行终端。

5. 实战避坑指南与性能优化

在实际项目中集成Page Agent,我踩过不少坑,也总结了一些让它们更稳定、更高效运行的经验。

5.1 常见问题与排查清单

问题现象可能原因排查步骤与解决方案
Agent找不到元素1. 元素是动态加载的,初始DOM中不存在。
2. 元素被CSS隐藏(display: none,visibility: hidden)。
3. 描述过于模糊,LLM无法准确定位。
1.增加等待:在执行指令前或指令中加入等待加载完成
2.使用更精确的描述:优先使用id,其次是独特的classaria-label。例如用“点击id为‘submit’的按钮”代替“点击提交按钮”。
3.检查rootSelector:确保目标元素在可观察的根节点之内。
指令执行超时1. 网络慢,LLM API响应时间长。
2. 任务过于复杂,LLM需要长时间思考。
3. 页面状态复杂,DOM描述文本过长。
1.增加timeout配置,如设为120000(2分钟)。
2.拆分复杂指令:将一个长任务拆成多个agent.execute()调用。
3.启用verbose模式,查看具体卡在哪一步。
LLM返回非预期操作1. Prompt理解偏差。
2. 模型能力不足或“幻觉”。
3. DOM描述信息不够清晰。
1.优化指令表述:清晰、简洁、分步骤。多用“点击”、“输入”、“滚动到”等明确动词。
2.升级模型:尝试更强的模型(如从qwen-plus换到qwen-max)。
3.提供上下文:对于复杂页面,可以先让Agent提取当前有哪些可操作项,再下达具体指令。
跨域iframe无法操作Page Agent默认无法访问跨域iframe内的DOM,这是浏览器的安全限制。无完美解决方案。如果iframe与你主站同源,可以确保Agent在iframe加载完成后初始化在其中。如果跨域,则需要考虑其他自动化方案,或与iframe提供方协商。
免费Demo API报错或限速达到调用频率或次数限制。切换到自己的付费API。Demo API仅用于POC验证,生产环境必须使用自己的LLM服务。

5.2 提升稳定性与性能的实战技巧

  1. 给元素加上“语义化”属性:这是提升Agent识别准确率最有效的一步。作为开发者,为你希望被AI操作的元素添加清晰的idaria-label><!-- 模糊 --> <button>提交</button> <!-- 清晰 --> <button id="user-profile-save-btn" aria-label="保存用户资料">提交</button>

  2. 实施“检查点”与重试机制:对于关键流程,不要完全依赖一次execute调用。可以在关键步骤后,让Agent验证结果。

    const maxAttempts = 3; let attempt = 0; let success = false; while (attempt < maxAttempts && !success) { try { await agent.execute('点击保存草稿按钮'); const result = await agent.execute('页面上是否出现了“保存成功”的绿色提示?'); if (result.extracted?.includes('是')) { success = true; break; } } catch (e) { console.warn(`第${attempt + 1}次尝试失败:`, e); } attempt++; await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒再重试 }
  3. 优化DOM观察范围:默认情况下,Agent会观察整个document.body。对于大型单页应用(SPA),这会产生巨大的文本描述,拖慢LLM处理速度并增加Token消耗。通过设置rootSelector,将其限制在主要交互区域。

    const agent = new PageAgent({ // ... 其他配置 rootSelector: '#main-content, .modal-active', // 只观察主内容区和当前激活的弹窗 });
  4. 处理动态内容与SPA路由:在Vue/React等框架开发的应用中,页面内容是动态渲染的。需要确保在页面内容稳定后再初始化或调用Agent。通常在mounteduseEffect或路由切换完成的回调函数中执行。

    // 在React组件中 useEffect(() => { if (pageLoaded) { // 你的页面加载状态 const initAgent = async () => { await agent.execute('开始今天的任务'); }; initAgent(); } }, [pageLoaded]);
  5. 成本控制:LLM API调用是主要成本。可以通过以下方式控制:

    • 使用轻量模型处理简单任务。
    • 缓存DOM描述:对于短时间内页面结构不变的操作,可以尝试缓存第一次获取的DOM描述文本,在后续指令中复用(但这需要修改Page Agent内部逻辑,属于高级用法)。
    • 设置预算和监控:在服务端对API Key的调用进行频次和费用监控。

6. 安全、隐私与生产环境部署考量

将这样一个能“操控”页面的AI集成到产品中,安全性和隐私是重中之重。

1. 指令注入与权限控制:Page Agent本身只是一个执行工具,它不会主动做“坏事”,但它会忠实地执行LLM解析出来的指令。因此,核心风险在于你传递给它的“指令”是否被恶意篡改

  • 防御措施:永远不要将未经清洗的用户输入直接传给agent.execute()。所有来自前端的用户指令,必须经过后端的校验和过滤。例如,可以建立一个允许的操作白名单(“点击”、“输入文本到[特定字段]”、“提取[公开信息]”),拒绝任何涉及跳转外链、下载文件、操作文件系统等高风险指令。

2. API密钥前端暴露:这是最危险的一点。如果按照最简单的做法,将API Key硬编码在JS文件里,那么任何用户查看网页源码都能窃取它,并用你的额度进行滥用。

  • 绝对正确的做法永远通过后端代理来调用LLM API。前端只与你的自家后端服务器通信,由后端服务器持有API Key,并负责转发请求和响应。这样,Key得到了保护,你还可以在后端加入速率限制、审计日志等更多安全措施。
    // 前端 - 错误做法(绝对禁止!) // const agent = new PageAgent({ apiKey: 'sk-xxx...' }); // 前端 - 正确做法 const agent = new PageAgent({ // 配置一个指向你自己后端端点的自定义 fetchFn fetchFn: async (promptMessages) => { const response = await fetch('/api/your-agent-proxy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: promptMessages }), }); return await response.json(); }, // model, baseURL 等配置也可以在后端决定 });
    // 后端(Node.js示例) - /api/your-agent-proxy app.post('/api/your-agent-proxy', async (req, res) => { // 1. 验证用户身份与权限 // 2. 清洗和校验用户指令(req.body.messages) // 3. 附加系统Prompt,限制Agent能力范围 // 4. 使用安全的API Key调用真实的LLM服务(如OpenAI) // 5. 将LLM的响应返回给前端 });

3. 隐私数据泄露:Agent在提取页面信息时,可能会将用户可见的所有文本(可能包含个人数据)发送给LLM API。

  • 应对策略:在后端代理层,对发送给LLM的DOM描述文本进行数据脱敏。使用正则表达式或预定义规则,过滤掉手机号、邮箱、身份证号等敏感信息,或用占位符(如[PHONE])替换。

4. 用户体验与预期管理:AI并非100%可靠。需要设计良好的UI/UX来管理用户预期。

  • 提供明确的状态反馈:当Agent在执行时,显示“AI正在处理中...”,并可能展示当前步骤。
  • 允许用户中断:提供“停止”按钮,调用Agent的取消方法。
  • 优雅的错误处理:当任务失败时,给出友好的提示,并可能提供手动操作的备选方案,而不是一个晦涩的控制台错误。

将Page Agent从酷炫的技术Demo变为一个可靠的生产力工具,关键在于围绕它构建一整套安全、健壮、用户体验良好的工程体系。这比单纯调用API要复杂,但却是必经之路。

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

数据标准:梳理业务主题、对象和事件的粒度应如何把握(干货)

很多企业在做数据治理时&#xff0c;一上来就建模型、定字段&#xff0c;结果标准落不了地、权责扯不清。其实&#xff0c;构建数据标准的第一步不是画表&#xff0c;而是把业务本身梳理清楚——也就是搞清楚业务域、业务主题、业务对象、业务事件这四层关系。今天这篇文章&…

作者头像 李华
网站建设 2026/4/25 2:50:06

从激光雷达到BIM验收:手把手用CloudCompare搞定点云距离分析全流程

从激光雷达到BIM验收&#xff1a;手把手用CloudCompare搞定点云距离分析全流程 在建筑信息化和工程验收领域&#xff0c;点云技术正成为连接设计与现实的桥梁。当BIM模型遇上现场扫描的激光雷达数据&#xff0c;如何精准量化两者差异成为工程质量把控的关键。本文将带您深入Clo…

作者头像 李华
网站建设 2026/4/25 2:48:39

专知智库发布全球首个《数字内容资产成熟度认证白皮书》——三维生态模型破解“唯流量论”困境,五级成熟度等级重塑内容价值标尺

专知智库发布全球首个《数字内容资产成熟度认证白皮书》——三维生态模型破解“唯流量论”困境&#xff0c;五级成熟度等级重塑内容价值标尺 &#xff08;2026年4月成都&#xff09; 在世界知识产权日到来之际&#xff0c;专知智库数字内容资产研究中心联合专知智库OPC研究院&…

作者头像 李华
网站建设 2026/4/25 2:37:50

1920. 基于排列构建数组

题目链接 1920. 基于排列构建数组 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个 从 0 开始的排列 nums&#xff08;下标也从 0 开始&#xff09;。请你构建一个 同样长度 的数组 ans &#xff0c;其中&#xff0c;对于每个 i&#xff08;0 < i < nums.l…

作者头像 李华