基于Playwright的浏览器自动化技能库:从模块化封装到实战应用

发布时间:2026/6/28 19:09:22

基于Playwright的浏览器自动化技能库:从模块化封装到实战应用 1. 项目概述一个浏览器技能库的诞生最近在整理个人工作流时我发现自己积攒了大量与浏览器相关的“小技巧”——从自动化表单填写、页面监控到数据抓取和性能调试。这些脚本和工具散落在各个书签、本地文件和笔记里用的时候经常找不到。我相信很多前端开发者、测试工程师甚至是对效率有追求的普通用户都有类似的痛点。于是我萌生了一个想法为什么不把这些零散的“技能”系统化地整理成一个开源库呢这就是browser-act/skills项目的初衷。简单来说browser-act/skills是一个旨在收集、标准化和分享浏览器自动化与增强技能的代码仓库。它不是一个庞大的框架而更像一个“工具箱”或“食谱集”。其核心价值在于将那些在真实工作场景中反复验证过的、解决特定问题的浏览器操作代码片段进行模块化封装和文档化让任何人包括未来的我自己都能快速复用而无需再从零开始搜索和调试。这个项目适合谁首先当然是前端和测试工程师你们是浏览器自动化的一线使用者。其次是数据分析师或运营人员需要定期从网页获取数据。甚至对于产品经理如果想快速验证某个网页交互流程的可行性这里也可能有现成的“轮子”。无论你是想学习现代浏览器 API如 Puppeteer, Playwright的最佳实践还是只想找个脚本解决手头的重复性网页操作这个项目都试图提供一个清晰的入口和可靠的示例。2. 核心设计思路从散兵游勇到标准化军团启动这个项目我首先思考的不是技术选型而是如何组织内容。一个杂乱无章的代码堆砌库是毫无价值的。我的核心设计思路围绕三个关键词展开场景化、模块化和可复现。2.1 场景化分类按问题而非技术划分传统的技术库喜欢按技术栈分类比如“Puppeteer 示例”、“Selenium 示例”。但用户通常带着一个具体问题而来比如“如何批量下载某个图片网站的所有图片”或“如何自动登录并导出某个后台的数据”。因此browser-act/skills的首要分类原则是场景。我将技能初步规划为几个大的场景模块数据获取与抓取包含静态页面爬取、动态内容等待与提取、分页处理、规避反爬策略等。自动化测试与交互涵盖表单自动填写、流程录制与回放、UI 状态断言、跨页面操作等。性能与体验分析包括页面性能指标采集、资源加载监控、用户体验关键路径的自动化检测。浏览器增强与效率工具例如批量书签处理、自定义快捷键脚本、页面内容修改与样式注入等。这种分类方式让用户能像查字典一样根据自己遇到的实际问题快速定位到相关的技能集。2.2 模块化封装统一接口即插即用每个技能Skill都被设计成一个独立的、功能完整的模块。一个理想的技能模块应该像乐高积木一样有清晰的输入和输出接口可以单独运行也可以轻松组合到更复杂的流程中。为此我为每个技能定义了一个标准结构skill-name/ ├── index.js // 主实现文件导出核心函数 ├── example.js // 使用示例 ├── README.md // 详细说明文档场景、参数、返回值、注意事项 └── package.json // 依赖声明如果需要核心函数遵循一致的签名约定例如一个数据抓取技能可能长这样/** * 抓取某电商网站商品列表 * param {Object} browser - Playwright/Puppeteer 浏览器实例 * param {string} keyword - 搜索关键词 * param {number} maxPages - 最大翻页数 * returns {PromiseArray} 商品信息数组 */ async function scrapeEcommerceProducts(browser, keyword, maxPages 5) { // 实现逻辑... }这种设计保证了技能的纯粹性和可移植性。使用者不需要理解技能内部的全部细节只需关注输入参数和输出结果降低了使用门槛。2.3 可复现性保障依赖锁定与环境说明浏览器自动化极易受环境因素影响浏览器版本、操作系统、页面结构变动。为了确保技能在任何时候都能被可靠地复现项目强调对环境的严格管理。依赖明确化每个技能的package.json会精确锁定其依赖的浏览器自动化库版本如playwright: ^1.40.0。容器化支持提供 Dockerfile 或引用 Docker 镜像确保运行环境一致。页面快照与模拟对于依赖特定线上页面的技能会配套提供静态 HTML 快照或使用本地 Mock 服务器避免因线上页面改版导致示例失效。变更检测机制计划集成简单的监控当技能依赖的目标页面 DOM 结构发生重大变化时能触发告警提醒维护者更新技能。注意浏览器自动化脚本具有“脆弱性”页面结构的微小变动可能导致脚本失效。因此在技能文档中我会明确标注该技能最后测试通过的时间、针对的网站或页面特征并建议使用者将其作为参考模板而非一成不变的解决方案。3. 技术栈选型与核心技能解析确定了项目结构接下来就要选择实现这些技能的“武器”。浏览器自动化领域主要有两大现代流派PuppeteerChrome 官方团队维护和 Playwright微软出品支持 Chromium, Firefox, WebKit。经过综合考量我决定以Playwright作为browser-act/skills项目的首选技术栈。3.1 为什么选择 Playwright虽然 Puppeteer 出道更早、生态成熟但 Playwright 在以下几个方面展现出了更胜一筹的优势尤其适合作为一个技能库的底层基础多浏览器原生支持Playwright 为 Chromium、Firefox 和 WebKitSafari 内核都提供了高质量的 API 支持且保证 API 一致性。这意味着一个技能脚本稍作调整甚至无需调整就能在三大浏览器引擎上运行。对于需要跨浏览器验证的场景如兼容性测试这是巨大的便利。自动等待机制更智能Playwright 内置的自动等待auto-waiting机制非常强大。在执行如click、fill等操作前它会自动检查元素是否可交互可见、启用、稳定。这避免了在脚本中编写大量显式的sleep或自定义等待条件让代码更简洁、更健壮。网络拦截与 Mock 能力Playwright 提供了精细的网络请求拦截route和响应模拟fulfill功能。这对于构建测试数据、模拟后端接口、加速测试拦截不必要的资源或实现特定抓取逻辑至关重要。相关 API 设计得直观易用。强大的录制器与代码生成Playwright 自带一个优秀的录制工具Codegen可以录制用户操作并生成对应代码。这对于快速创建技能原型或者让不熟悉 API 的用户入门非常有帮助。生成的代码质量也较高可直接作为技能的基础。活跃的社区与良好的文档Playwright 背后有微软支持社区活跃更新迅速文档详尽且示例丰富。这对于一个开源项目长期维护的可持续性非常重要。基于以上原因browser-act/skills中的大部分技能将基于 Playwright 实现。当然这并不排斥未来收录基于 Puppeteer 或其他工具如 Cypress的优秀技能但会以 Playwright 为主流和推荐标准。3.2 核心技能模式剖析无论技能的具体场景是什么其内部实现往往遵循一些通用模式。理解这些模式有助于我们更好地编写和使用技能。模式一页面导航与生命周期管理这是所有技能的起点。关键点在于正确处理页面加载状态。const { chromium } require(playwright); (async () { const browser await chromium.launch({ headless: false }); // 调试时可设为非无头模式 const context await browser.newContext(); const page await context.newPage(); // 关键使用 waitForLoadState 确保页面加载到所需状态 await page.goto(https://example.com); await page.waitForLoadState(networkidle); // 等待网络基本空闲 // ... 执行技能操作 ... await browser.close(); })();实操心得networkidle状态在单页应用SPA中可能不准确因为 SPA 会频繁发起 Ajax 请求。此时更可靠的方法是等待某个特定元素出现例如await page.waitForSelector(#main-content)。模式二元素定位与交互稳定地定位元素是自动化的基石。Playwright 提供了多种定位器Locator推荐使用getByRole,getByText,getByLabel等语义化方式它们比 CSS 选择器更具可读性和抗变性。// 不推荐脆弱的 CSS 选择器 await page.click(div.container form input:nth-child(2)); // 推荐使用语义化定位器 await page.getByRole(textbox, { name: 用户名 }).fill(myUser); await page.getByRole(button, { name: 登录 }).click();如果页面没有良好的可访问性属性可以结合使用getByTestId需要开发者在元素上添加>// 提取多个元素文本 const items await page.locator(.product-item).all(); const productData []; for (const item of items) { const name await item.locator(.name).innerText(); const price await item.locator(.price).innerText(); // 数据清洗去除空格、货币符号转换为数字 const priceNum parseFloat(price.replace(/[^\d.]/g, )); productData.push({ name, price: priceNum }); } // 或者使用 evaluate 直接在浏览器上下文执行JS处理复杂结构 const chartData await page.evaluate(() { const scriptTag document.querySelector(#chart-data); return scriptTag ? JSON.parse(scriptTag.innerText) : null; });注意事项page.evaluate中执行的代码是在浏览器环境中运行的无法直接使用外部变量。需要传递参数需通过第二个参数传入例如page.evaluate((arg) {...}, externalArg)。模式四处理弹窗、新标签页与框架现代网页交互复杂需要妥善处理这些情况。// 1. 监听并处理弹窗如 alert, confirm page.on(dialog, async dialog { console.log(弹窗信息: ${dialog.message()}); await dialog.accept(); // 或 .dismiss() }); // 2. 等待新标签页打开并切换上下文 const [newPage] await Promise.all([ context.waitForEvent(page), page.click(a[target_blank]) // 点击一个打开新窗口的链接 ]); await newPage.waitForLoadState(); console.log(await newPage.title()); // 操作完成后可切换回原页面 await page.bringToFront(); // 3. 操作 iframe 内的元素 const frame page.frame({ name: my-iframe }); if (frame) { await frame.getByRole(button).click(); }4. 实战构建一个完整的“商品价格监控”技能让我们通过一个完整的例子将上述设计思路和技术要点串联起来。假设我们要构建一个名为monitor-ecommerce-price的技能用于监控某电商网站特定商品的价格变化。4.1 技能定义与初始化首先创建技能目录结构并初始化package.json声明对 Playwright 的依赖。mkdir skills/monitor-ecommerce-price cd skills/monitor-ecommerce-price npm init -y npm install playwright接着创建主文件index.js定义技能函数。这个函数需要接收商品URL、监控频率等参数并返回价格信息。// index.js const { chromium } require(playwright); /** * 监控电商商品价格 * param {string} productUrl - 商品详情页URL * param {Object} [options] - 可选配置 * param {boolean} [options.headlesstrue] - 是否无头模式 * param {number} [options.timeout30000] - 页面加载超时时间(毫秒) * returns {Promise{price: number, currency: string, title: string, timestamp: Date, url: string}} 商品信息 * throws {Error} 当页面访问失败或元素未找到时抛出错误 */ async function monitorPrice(productUrl, options {}) { const { headless true, timeout 30000 } options; const browser await chromium.launch({ headless }); const context await browser.newContext({ userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., // 模拟真实浏览器 }); const page await context.newPage(); page.setDefaultTimeout(timeout); try { console.log(正在访问: ${productUrl}); await page.goto(productUrl, { waitUntil: domcontentloaded }); // 等待关键价格元素出现增加鲁棒性 await page.waitForSelector([data-testidproduct-price], .price, [itempropprice], { state: attached }); // 提取数据 - 这里需要根据目标网站的实际结构调整选择器 const title await page.title(); // 尝试多种可能的价格选择器 const priceText await page.evaluate(() { const selectors [ [data-testidproduct-price], .price, [itempropprice], .product-price, .current-price ]; for (const sel of selectors) { const el document.querySelector(sel); if (el el.innerText) return el.innerText.trim(); } return null; }); if (!priceText) { throw new Error(无法在页面上找到价格信息可能需要更新选择器。); } // 清洗价格文本提取数字和货币符号 const priceMatch priceText.match(/([£$€¥]?)\s*([\d,]\.?\d*)/); if (!priceMatch) { throw new Error(无法解析价格文本: ${priceText}); } const [, currencySymbol, priceNumStr] priceMatch; const price parseFloat(priceNumStr.replace(/,/g, )); // 确定货币 const currencyMap { $: USD, £: GBP, €: EUR, ¥: CNY }; const currency currencyMap[currencySymbol] || currencySymbol || 未知; const result { price, currency, title: title.split(|)[0].trim(), // 简单清理标题 timestamp: new Date(), url: productUrl }; console.log(监控成功: ${result.title} - ${result.currency} ${result.price}); return result; } catch (error) { console.error(监控失败 (${productUrl}):, error.message); // 可以在这里添加截图功能便于调试 // await page.screenshot({ path: error-${Date.now()}.png, fullPage: true }); throw error; // 重新抛出错误让调用者处理 } finally { await browser.close(); // 确保浏览器被关闭避免资源泄漏 } } module.exports { monitorPrice };4.2 编写使用示例与文档创建example.js展示如何调用这个技能并处理可能的结果。// example.js const { monitorPrice } require(./index); (async () { // 示例商品URL此处为虚构实际使用时替换 const productUrl https://www.example-store.com/product/awesome-thing-123; try { const productInfo await monitorPrice(productUrl, { headless: true }); console.log(获取到的商品信息:); console.log(JSON.stringify(productInfo, null, 2)); // 这里可以添加后续逻辑比如 // 1. 与数据库中的历史价格比较 // 2. 如果价格低于阈值发送邮件或钉钉通知 // 3. 将数据存入JSON文件或数据库 if (productInfo.price 100) { console.log( 价格低于100可以考虑入手); } } catch (error) { console.error(执行监控任务时出错:, error); process.exit(1); // 非正常退出 } })();最后创建详细的README.md文档这是技能能否被他人轻松使用的关键。# 技能电商商品价格监控 (monitor-ecommerce-price) ## 场景描述 自动访问指定的电商商品页面提取当前价格、商品标题等信息。适用于价格追踪、比价、库存监控等场景。 ## 安装与依赖 确保已安装 Node.js (16)。在本技能目录下运行 bash npm install 本技能依赖 playwright。首次运行会自动下载浏览器内核。 ## 使用方法 javascript const { monitorPrice } require(./index); (async () { const info await monitorPrice(https://www.your-store.com/product/123); console.log(info); })(); ## 参数说明 - productUrl (字符串必需): 商品详情页的完整URL。 - options (对象可选): - headless (布尔值): 默认为 true。设为 false 可打开浏览器界面便于调试。 - timeout (数字): 页面加载超时时间单位毫秒默认30000。 ## 返回值 返回一个 Promise解析为包含以下字段的对象 javascript { price: 299.99, // 价格数字 currency: USD, // 货币代码 title: 商品名称, // 清理后的商品标题 timestamp: 2023-10-27T..., // 抓取时间戳 url: ... // 原商品URL } ## 注意事项与适配指南 1. **页面结构变动**此技能内置了多种常见价格选择器但电商网站经常改版。如果抓取失败你需要 - 打开目标页面使用浏览器开发者工具检查价格元素的CSS选择器或data-testid。 - 更新 index.js 中 page.evaluate 函数内的 selectors 数组。 2. **反爬虫机制**频繁访问同一网站可能触发反爬。建议 - 在 context 配置中设置合理的 userAgent本技能已设置一个示例。 - 添加随机延迟await page.waitForTimeout(Math.random() * 3000 1000)。 - 考虑使用代理IP轮询高级功能需自行扩展。 3. **登录与验证码**本技能不处理需要登录或验证码的页面。此类场景需要更复杂的技能如处理登录会话。 4. **运行频率**避免过高频率访问尊重网站的 robots.txt 规则。 ## 扩展思路 - 将结果保存到数据库如SQLite、MongoDB并绘制价格历史曲线。 - 集成通知服务如邮件、Telegram Bot、Server酱在价格达到设定阈值时报警。 - 批量监控多个商品并生成比价报告。4.3 技能的组合与进阶构建监控系统单一技能价值有限但多个技能组合就能形成解决方案。我们可以基于monitor-price技能构建一个简单的定时监控系统。创建一个新的文件monitor-system.js它可能位于项目根目录作为使用技能的示范// monitor-system.js const { monitorPrice } require(./skills/monitor-ecommerce-price); const fs require(fs).promises; const path require(path); // 监控列表 const PRODUCT_LIST [ { url: https://www.store-a.com/product/1, name: 商品A, threshold: 50 }, { url: https://www.store-b.com/product/2, name: 商品B, threshold: 200 }, ]; // 历史数据文件 const HISTORY_FILE path.join(__dirname, price-history.json); async function loadHistory() { try { const data await fs.readFile(HISTORY_FILE, utf8); return JSON.parse(data); } catch { return {}; // 文件不存在则返回空对象 } } async function saveHistory(history) { await fs.writeFile(HISTORY_FILE, JSON.stringify(history, null, 2), utf8); } async function checkAndNotify(product, currentInfo) { const history await loadHistory(); const productHistory history[product.url] || []; const lastRecord productHistory[productHistory.length - 1]; if (lastRecord currentInfo.price lastRecord.price) { console.log( [降价提醒] ${product.name} 价格从 ${lastRecord.price} 降至 ${currentInfo.price}!); // 此处可调用发送邮件/消息的函数 } // 记录新数据 productHistory.push({ price: currentInfo.price, date: currentInfo.timestamp.toISOString().split(T)[0], }); // 只保留最近30天数据 if (productHistory.length 30) { productHistory.shift(); } history[product.url] productHistory; await saveHistory(history); } async function runMonitor() { console.log(开始执行价格监控共 ${PRODUCT_LIST.length} 个商品...); for (const product of PRODUCT_LIST) { try { // 随机延迟模拟人类操作避免请求过于密集 await new Promise(resolve setTimeout(resolve, Math.random() * 5000 2000)); const info await monitorPrice(product.url, { headless: true }); await checkAndNotify(product, info); } catch (error) { console.error(监控商品 ${product.name} 失败:, error.message); } } console.log(本轮监控完成。); } // 可以配合cron job定时执行例如每6小时一次 runMonitor();这个系统展示了如何将核心技能嵌入到一个更大的、实用的工作流中包括数据持久化、变化检测和简单的通知逻辑。5. 开发、调试与维护实战指南构建和维护一个高质量的技能库离不开高效的开发调试流程和长期的维护策略。5.1 高效的开发与调试技巧利用 Playwright 录制器Codegen快速生成脚本骨架 这是入门和探索新页面交互的神器。在项目目录下运行npx playwright codegen [网址]会打开一个浏览器和一个录制窗口。你的所有操作都会被实时转换成 Playwright 代码。这能帮你快速获得正确的元素选择器和操作序列极大提升开发效率。调试时关闭无头模式并启用慢动作const browser await chromium.launch({ headless: false, // 显示浏览器 slowMo: 500, // 每个操作延迟500毫秒方便观察 });善用截图和录屏定位问题// 在关键步骤或出错时截图 await page.screenshot({ path: step1.png, fullPage: true }); // 录制整个操作过程需要配置 await context.tracing.start({ screenshots: true, snapshots: true }); // ... 执行操作 ... await context.tracing.stop({ path: trace.zip }); // 使用 playwright show-trace trace.zip 命令可视化回放在浏览器开发者工具中测试选择器 在打开的浏览器页面中直接按 F12 打开开发者工具在 Console 里用$()(相当于document.querySelector) 或$$()(相当于document.querySelectorAll) 测试你的 CSS 或 XPath 选择器是否准确这比反复运行脚本要快得多。5.2 编写健壮技能的要点防御性编程永远不要假设页面元素一定存在。使用waitForSelector并设置合理的超时时间。对page.evaluate的返回值进行判空处理。错误处理与重试机制网络不稳定或页面临时加载失败很常见。为关键操作如page.goto,click添加重试逻辑。async function retryOperation(operation, maxRetries 3, delay 1000) { for (let i 0; i maxRetries; i) { try { return await operation(); } catch (error) { if (i maxRetries - 1) throw error; console.log(操作失败第${i1}次重试...); await page.waitForTimeout(delay); } } } // 使用 await retryOperation(() page.goto(url));环境隔离每个技能或任务应使用独立的browserContext避免 cookie、localStorage 相互污染。测试完成后务必调用browser.close()清理资源。5.3 技能库的维护与协作版本管理每个技能目录独立拥有自己的package.json便于独立管理依赖和版本。测试套件为关键技能编写简单的测试用例确保核心功能在修改后依然正常工作。可以使用 Jest 或 Mocha 配合 Playwright 的测试运行器。贡献指南在项目根目录提供清晰的CONTRIBUTING.md说明如何提交新的技能、代码规范、文档要求等鼓励社区贡献。持续集成配置 GitHub Actions 或类似 CI 工具在提交代码时自动运行测试检查代码格式确保仓库质量。6. 常见问题排查与技巧实录在实际使用和开发浏览器自动化技能时你会遇到各种各样的问题。下面是我踩过的一些坑和总结的解决方案。6.1 元素找不到或操作超时这是最常见的问题其根源通常在于页面状态未就绪或元素定位器不稳定。排查步骤确认页面是否加载完成page.goto后使用page.waitForLoadState(networkidle)或等待特定元素出现。检查选择器是否正确在浏览器开发者工具中使用$(your-selector)验证。注意Playwright 运行在真实的浏览器环境中看到的 DOM 结构和你在开发者工具中看到的一致。但要注意是否有 Shadow DOM 或 iframe。元素是否在 iframe 或 Shadow DOM 中如果在 iframe 中需要使用page.frame()获取 frame 对象后再查找。对于 Shadow DOM需要使用element.locator( .inner-element)这样的穿透选择器Playwright 支持和/deep/。等待元素可交互元素可能已加载但被其他元素遮挡或样式设置为disabled。使用page.waitForSelector(selector, { state: visible })或page.waitForSelector(selector, { state: attached })。增加超时时间对于慢速网络或复杂页面默认 30 秒可能不够。在page.goto或waitForSelector中设置{ timeout: 60000 }。技巧使用locator的filter和first/last/nth方法处理多个匹配项。// 找到第三个具有“购买”文本的按钮 const buyButton page.getByText(购买).nth(2); // 找到包含特定类名的第一个可见元素 const firstVisibleItem page.locator(.item).filter({ hasText: 现货 }).first();6.2 处理动态内容与无限滚动许多现代网站使用 JavaScript 动态加载内容。解决方案等待网络请求监听特定的 XHR 或 Fetch 请求完成。// 等待包含‘api/products’的请求完成 await page.waitForResponse(response response.url().includes(api/products) response.status() 200 );滚动触发加载模拟人类滚动行为。let previousHeight; while (true) { previousHeight await page.evaluate(document.body.scrollHeight); await page.evaluate(window.scrollTo(0, document.body.scrollHeight)); await page.waitForTimeout(2000); // 等待新内容加载 const newHeight await page.evaluate(document.body.scrollHeight); if (newHeight previousHeight) break; // 高度不再变化说明已到底部 }等待特定数量的元素出现await page.waitForFunction( (selector, minCount) document.querySelectorAll(selector).length minCount, { timeout: 10000 }, .product-item, 20 // 等待至少20个商品项出现 );6.3 绕过简单的反爬措施一些网站会检测自动化脚本。应对策略需遵守网站规则模拟真人浏览器指纹创建 context 时设置userAgent、viewport、locale、timezoneId等。const context await browser.newContext({ userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..., viewport: { width: 1920, height: 1080 }, locale: zh-CN, timezoneId: Asia/Shanghai, });禁用 WebDriver 属性有些网站通过检测navigator.webdriver属性来识别自动化工具。Playwright 默认会尝试隐藏此特征但可以显式设置await page.addInitScript(() { Object.defineProperty(navigator, webdriver, { get: () false }); });使用代理IP对于需要高匿名的场景可以在启动浏览器时配置代理。const browser await chromium.launch({ proxy: { server: http://your-proxy:port } });重要提示使用自动化工具访问网站必须遵守该网站的robots.txt协议和服务条款。本技能库仅用于学习和合法的自动化场景如测试自家网站、监控公开数据等严禁用于任何可能对目标网站造成负担或违反其使用条款的行为。6.4 性能优化与资源管理长时间运行多个技能可能导致内存泄漏或性能下降。优化建议复用浏览器实例如果连续执行多个任务不要为每个任务都启动和关闭浏览器而是复用同一个浏览器实例为每个任务创建新的 context 和 page。const browser await chromium.launch(); // 任务1 const context1 await browser.newContext(); const page1 await context1.newPage(); // ... 执行任务1 ... await context1.close(); // 任务2 const context2 await browser.newContext(); // ... 执行任务2 ... await context2.close(); await browser.close(); // 所有任务完成后关闭及时清理确保page.close()和browser.close()被调用最好放在try...catch...finally块中。限制并发如果同时监控大量页面不要一次性打开几十个 page这会导致内存激增。使用队列或Promise.all控制并发数例如同时最多5个页面。拦截不必要的资源如果技能只关心页面结构和文本可以拦截图片、字体、样式表等大幅提升加载速度。await page.route(**/*.{png,jpg,jpeg,svg,gif,woff,woff2,css}, route route.abort());构建browser-act/skills这类项目最大的收获不是积累了多少代码片段而是形成了一套解决浏览器自动化问题的系统化思维。从识别场景、设计接口到处理边界情况和优化性能每一个环节都需要仔细考量。最深的体会是鲁棒性远比功能强大更重要。一个能在各种网络波动和页面微调下稳定运行80%功能的脚本远比一个功能全面但动不动就崩溃的脚本有价值。因此在贡献每一个技能时我都会问自己如果页面某个非关键样式变了它还能工作吗如果网络慢一点它会超时吗把这些问题的答案写在代码和文档里才是对使用者最大的负责。

相关新闻