Crawlee(Apify SDK v1)升级指南:从 PuppeteerPool 到 BrowserPool、Crawling Context 与 launchContext 的全面迁移

Crawlee(Apify SDK v1)升级指南:从 PuppeteerPool 到 BrowserPool、Crawling Context 与 launchContext 的全面迁移 CrawleeApify SDK v1升级指南从 PuppeteerPool 到 BrowserPool、Crawling Context 与 launchContext 的全面迁移【免费下载链接】crawleeCrawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.项目地址: https://gitcode.com/GitHub_Trending/cr/crawlee导读本文基于 Crawlee 仓库 website/versioned_docs/version-3.13/upgrading/upgrading_v1.md 中的官方迁移文档系统讲解 Apify SDK v1 引入的破坏性变更与对应的迁移路径。读者将掌握如何安装 v1 并选择 Puppeteer/Playwright、handler 参数如何统一为 Crawling Context、PuppeteerPool如何被BrowserPool取代、gotoFunction/launchPuppeteerOptions/launchPuppeteerFunction等旧选项如何迁移以及新版 launch 函数的正确用法。文末会结合当前仓库中 packages/browser-pool 与 packages/puppeteer-crawler 的源码实现佐证这些设计背后的底层原理。为什么会有 v1稳定性与多浏览器支持经过 3.5 年的快速迭代和大量破坏性变更、弃用之后Apify SDK v1 正式发布。这次大版本有两个核心目标稳定性SDK 已被大量网页抓取和自动化项目使用团队承诺从 v1 开始每年最多只通过一次新的大版本发布引入破坏性变更为开发者提供稳定的运行环境。更多浏览器支持通过用新库browser-pool替换PuppeteerPool将原本仅支持 Puppeteer 的能力扩展到 PlaywrightFirefox、WebKit/Safari 等所有知名浏览器。browser-pool 继承了PuppeteerPool的核心思想并将其扩展为可管理多种浏览器自动化库。它的 API 与PuppeteerPool相似但不完全相同。Playwright 与 Puppeteer 接口几乎一致同时增加了实用特性并简化了常见任务即使如此你仍然可以在新的BrowserPool下继续使用 Puppeteer。从当前仓库的源码可以看到browser-pool已经独立演进为一个通用浏览器管理库通过PuppeteerPlugin和PlaywrightPlugin两个插件封装底层库BrowserPool负责启动、回收、关闭浏览器以及管理整个浏览器/页面生命周期见 browser-pool.ts。一个重要的破坏性变更不再捆绑浏览器库v1 之前SDK 直接捆绑了puppeteer包用户无需自行安装。v1 同时支持playwright为了不强迫用户同时安装两者也为了让安装更快、让用户自由选择库的版本v1 起puppeteer和playwright都不再随 SDK 捆绑需要用户自行安装。这也为未来支持更多库留出了空间。这一设计在当前仓库中依然延续crawlee/browser-pool的 README 明确说明它不预装任何浏览器自动化库由用户自行选择库及其版本见 packages/browser-pool/README.md。安装选择 Puppeteer 还是 Playwright按需安装。使用 Puppeteer与旧版本行为一致npm install apify puppeteer使用 Playwrightnpm install apify playwright需要注意v1 初始版本虽然覆盖了大部分核心功能但仍有一些工具函数或选项仅支持 Puppeteer 而不支持 Playwright。在 Apify Platform 上运行如果要在 Apify Platform 上使用 Playwright需要选择支持 Playwright 的 Docker 镜像官方已提供详见仓库 docs/deployment 目录下的相关部署文档。同时请务必注意你的package.json必须将puppeteer和/或playwright列为依赖。如果没有列出构建 actor 时这些库会被从node_modules中卸载。Handler 参数统一为 Crawling Context旧版 SDK 中用户提供的各个 handler 函数的参数是各自独立创建的对象。这导致同一个请求在不同函数中拿到的参数对象不是同一个引用跨函数追踪状态非常困难const handlePageFunction async (args1) { args1.hasOwnProperty(proxyInfo) // true } const handleFailedRequestFunction async (args2) { args2.hasOwnProperty(proxyInfo) // false } args1 args2 // falseSDK v1 引入了一个统一的单一对象Crawling Context。所有 handler 共享同一个上下文对象const handlePageFunction async (crawlingContext1) { crawlingContext1.hasOwnProperty(proxyInfo) // true } const handleFailedRequestFunction async (crawlingContext2) { crawlingContext2.hasOwnProperty(proxyInfo) // true } // 所有 context 都是同一个对象。 crawlingContext1 crawlingContext2 // trueCrawling Context 的id与跨上下文访问既然所有对象都是同一个SDK 就能追踪所有正在运行的 crawling context通过新增的id属性实现跨上下文访问let masterContextId; const handlePageFunction async ({ id, page, request, crawler }) { if (request.userData.masterPage) { masterContextId id; // 准备 master 页面。 } else { const masterContext crawler.crawlingContexts.get(masterContextId); const masterPage masterContext.page; const masterRequest masterContext.request; // 现在可以在另一个 handlePageFunction 中操作 master 页面的数据。 } }autoscaledPool移至crawlingContext.crawler下为避免对象臃肿并让关键对象更容易访问v1 在 handler 参数上暴露了crawler属性const handlePageFunction async ({ request, page, crawler }) { await crawler.requestQueue.addRequest({ url: https://example.com }); await crawler.autoscaledPool.pause(); }这也意味着puppeteerPool、autoscaledPool等旧版简写不再需要const handlePageFunction async (crawlingContext) { crawlingContext.autoscaledPool // 不再存在 crawlingContext.crawler.autoscaledPool // 这才是正确用法 }PuppeteerPool被BrowserPool取代BrowserPool在PuppeteerPool的基础上扩展出管理其他浏览器自动化库的能力。只有PuppeteerCrawler和PlaywrightCrawler使用它可以通过crawler对象访问const crawler new Apify.PlaywrightCrawler({ handlePageFunction: async ({ page, crawler }) { crawler.browserPool // ----- } }); crawler.browserPool // -----页面现在有 ID页面 ID 与crawlingContext.id相同这让你在 hooks 中可以访问完整的crawlingContext详见下文生命周期 hooks 小节const pageId browserPool.getPageId这一设计在当前仓库中仍然存在BrowserPool的getPageId(page)方法通过内部pageIdsWeakMap 返回页面 ID页面 ID 在浏览器启动前就已创建并伴随页面直到关闭见 browser-pool.ts。配置与生命周期 hooksBrowserPool最重要的新增能力是生命周期 hooks可通过两个 crawler 的browserPoolOptions访问。完整的browserPoolOptions列表定义在browser-pool中当前仓库对应 BrowserPoolOptionsconst crawler new Apify.PuppeteerCrawler({ browserPoolOptions: { retireBrowserAfterPageCount: 10, preLaunchHooks: [ async (pageId, launchContext) { const { request } crawler.crawlingContexts.get(pageId); if (request.userData.useHeadful true) { launchContext.launchOptions.headless false; } } ] } })从当前仓库源码可以补充BrowserPoolOptions的默认值见 browser-pool.ts 的 zod schema选项默认值说明maxOpenPagesPerBrowser20单个浏览器同时打开的最大页面数超过后启动新浏览器retireBrowserAfterPageCount100浏览器处理多少页后自动退役关闭operationTimeoutSecs15底层库异步操作启动浏览器/打开页面的超时秒数closeInactiveBrowserAfterSecs300非活动浏览器多久后被关闭以释放资源retireInactiveBrowserAfterSecs10非活动浏览器多久后被标记为退役useFingerprintstrue是否启用浏览器指纹v3 起默认开启hooks 共分六类preLaunchHooks、postLaunchHooks、prePageCreateHooks、postPageCreateHooks、prePageCloseHooks、postPageCloseHooks。从源码看hooks 按声明顺序依次await执行且 hook 的第一个参数是pageId页面创建前或page页面创建后用于追踪 hook 由哪次newPage()触发见 browser-pool.ts。BrowserController的引入BrowserController是browser-pool中负责浏览器管理的类为 Puppeteer 和 Playwright 提供统一的 API。它在后台自动工作但如果你需要正确地关闭浏览器应该通过它来做。它出现在 handler 参数中const handlePageFunction async ({ page, browserController }) { // 错误用法。会绕过 BrowserPool可能引发问题。 await page.browser().close(); // 正确用法。允许优雅关闭。 await browserController.close(); const cookies [/* some cookie objects */]; // 错误用法。只在 Puppeteer 中有效Playwright 中无效。 await page.setCookies(...cookies); // 正确用法。两种库都有效。 await browserController.setCookies(page, cookies); }当前仓库中BrowserController的抽象基类完整实现了这套统一 APIgetCookies(page)、setCookies(page, cookies)、close()以及kill()、newPage()等内部方法见 abstract-classes/browser-controller.ts。close()会优雅关闭浏览器并确保不残留进程同时通过PROCESS_KILL_TIMEOUT_MILLIS 5000的超时兜底强制终止卡住的进程见 browser-controller.ts。专门的PuppeteerController与PlaywrightController分别继承它实现底层差异。BrowserController还携带浏览器的重要信息例如启动它的上下文——这在 v1 之前很难获取const handlePageFunction async ({ browserController }) { // 浏览器使用的代理信息 browserController.launchContext.proxyInfo // 浏览器使用的会话 browserController.launchContext.session }对应的LaunchContext类在当前仓库中实现它持有launchOptions、proxyUrl、useIncognitoPages、userDataDir等信息并提供extend()方法安全地附加浏览器作用域的任意字段如 session ID见 packages/browser-pool/src/launch-context.ts。BrowserPool方法与PuppeteerPool的对照部分函数被移除与更早的弃用一致部分发生了变更// 旧 await puppeteerPool.recyclePage(page); // 新 await page.close();// 旧 await puppeteerPool.retire(page.browser()); // 新 browserPool.retireBrowserByPage(page);// 旧 await puppeteerPool.serveLiveViewSnapshot(); // 新 // BrowserPool 中不再有 LiveView其中retireBrowserByPage(page)在当前仓库中依然存在它通过getBrowserControllerByPage(page)找到页面所属的浏览器控制器并对其执行退役见 browser-pool.ts。退役不同于立即关闭——浏览器不再开新页面但会等待已打开的页面关闭后再优雅退出避免运行中的任务被打断。更新的PuppeteerCrawlerOptions为了让PuppeteerCrawler和PlaywrightCrawler保持一致v1 更新了选项体系。移除gotoFunction改用导航前后 hooks可配置的gotoFunction概念并不理想尤其是底层使用经过修改的gotoExtended——用户重写gotoFunction想要扩展默认行为时必须了解这些内部细节。v1 用preNavigationHooks和postNavigationHooks取代了它。下面这个例子展示了旧gotoFunction有多么繁琐const gotoFunction async ({ request, page }) { // 预处理 await makePageStealthy(page); // 必须记住怎么做 const response await gotoExtended(page, request, {/* 必须记住默认参数 */}); // 后处理 await page.evaluate(() { window.foo bar; }); // 不能忘记 return response; } const crawler new Apify.PuppeteerCrawler({ gotoFunction, // ... })使用preNavigationHooks和postNavigationHooks则简单得多。preNavigationHooks接收两个参数crawlingContext和gotoOptionspostNavigationHooks只接收crawlingContextconst preNavigationHooks [ async ({ page }) makePageStealthy(page) ]; const postNavigationHooks [ async ({ page }) page.evaluate(() { window.foo bar }) ] const crawler new Apify.PuppeteerCrawler({ preNavigationHooks, postNavigationHooks, // ... })这两个选项在当前仓库的PuppeteerCrawlerOptions中依然是标准配置见 packages/puppeteer-crawler/src/internals/puppeteer-crawler.ts。仓库还特别提醒在preNavigationHooks中使用injectJQuery()会导致结果不稳定应将其放在postNavigationHook或requestHandler中使用。launchPuppeteerOptionslaunchContext旧launchPuppeteerOptions一直令人困惑因为它把 Apify 自定义选项与 Puppeteer 的launchOptions混在了一起const launchPuppeteerOptions { useChrome: true, // Apify 选项 headless: false, // Puppeteer 选项 }新launchContext对象显式定义了launchOptions。launchPuppeteerOptions已被移除const crawler new Apify.PuppeteerCrawler({ launchContext: { useChrome: true, // Apify 选项 launchOptions: { headless: false // Puppeteer 选项 } } })LaunchContext同时也是browser-pool的类型两边的结构完全一致SDK 只是额外增加了一些选项。当前仓库中PuppeteerLaunchContext的结构与此一脉相承launchOptions对应 Puppeteer 的puppeteer.launch选项另外提供useChrome为 true 且未指定executablePath时启动完整版 Chrome 而非捆绑的 Chromium、proxyUrl、launcher支持puppeteer-extra等包装库、useIncognitoPages每页独立上下文、互不共享 cookie 与缓存等扩展选项见 packages/puppeteer-crawler/src/internals/puppeteer-launcher.ts。移除launchPuppeteerFunctionbrowser-pool引入了生命周期 hooks 的概念——在浏览器生命周期中特定事件发生时执行的函数const launchPuppeteerFunction async (launchPuppeteerOptions) { if (someVariable chrome) { launchPuppeteerOptions.useChrome true; } return Apify.launchPuppeteer(launchPuppeteerOptions); } const crawler new Apify.PuppeteerCrawler({ launchPuppeteerFunction, // ... })现在你可以用preLaunchHook实现同样的功能const maybeLaunchChrome (pageId, launchContext) { if (someVariable chrome) { launchContext.useChrome true; } } const crawler new Apify.PuppeteerCrawler({ browserPoolOptions: { preLaunchHooks: [maybeLaunchChrome] }, // ... })这种方式更好它在 Puppeteer 和 Playwright 间保持一致并且允许你用预定义的行为轻松组合浏览器const preLaunchHooks [ maybeLaunchChrome, useHeadfulIfNeeded, injectNewFingerprint, ]借助crawler.crawlingContexts这些 hook 函数还能访问触发启动的request所对应的crawlingContextconst preLaunchHooks [ async function maybeLaunchChrome(pageId, launchContext) { const { request } crawler.crawlingContexts.get(pageId); if (request.userData.useHeadful true) { launchContext.launchOptions.headless false; } } ]Launch 函数除了Apify.launchPuppeteer()v1 新增了Apify.launchPlaywright()。更新后的参数launch 选项对象也因同样原因新旧选项混用易混淆做了更新// 旧 await Apify.launchPuppeteer({ useChrome: true, headless: true, }) // 新 await Apify.launchPuppeteer({ useChrome: true, launchOptions: { headless: true, } })当前仓库中launchPuppeteer(launchContext, configuration)的实现依然遵循这一签名通过PuppeteerLauncher启动浏览器并会读取CRAWLEE_HEADLESS环境变量、将proxyUrl校验后写入--proxy-server启动参数等见 packages/puppeteer-crawler/src/internals/puppeteer-launcher.ts。自定义模块puppeteerModule统一为launcherApify.launchPuppeteer原本支持puppeteerModule选项。引入 Playwright 后名称被统一为launcher——因为playwright模块本身并不直接启动浏览器需要指定具体的浏览器类型chromium/firefox/webkitconst puppeteer require(puppeteer); const playwright require(playwright); await Apify.launchPuppeteer(); // 等价于 await Apify.launchPuppeteer({ launcher: puppeteer }) await Apify.launchPlaywright(); // 等价于 await Apify.launchPlaywright({ launcher: playwright.chromium })从当前仓库browser-pool的 README 可以看到这一能力的完整形态BrowserPool通过browserPlugins接收插件如new PlaywrightPlugin(playwright.chromium)支持在一个池中同时管理多个插件并按轮询round-robin方式分配页面也支持newPageWithEachPlugin()同时用所有插件各开一个页面用于多环境测试见 packages/browser-pool/README.md。这正是文档中未来可以支持更多库承诺的实现基础。迁移清单速查旧 APIv1 之前新 APIv1 起SDK 捆绑puppeteer自行安装puppeteer或playwrighthandler 各自独立的参数对象统一的 Crawling Context所有 handler 共享同一对象参数中的puppeteerPool/autoscaledPoolcrawler.browserPool/crawler.autoscaledPoolPuppeteerPoolBrowserPool经browserPoolOptions配置puppeteerPool.recyclePage(page)page.close()puppeteerPool.retire(browser)browserPool.retireBrowserByPage(page)puppeteerPool.serveLiveViewSnapshot()移除BrowserPool 无 LiveViewgotoFunctionpreNavigationHookspostNavigationHookslaunchPuppeteerOptionslaunchContext内含launchOptionslaunchPuppeteerFunctionbrowserPoolOptions.preLaunchHookspuppeteerModulelauncherPlaywright 需指定如playwright.chromium仅Apify.launchPuppeteer()另有Apify.launchPlaywright()小结Apify SDK v1 通过每年一次大版本破坏性变更的承诺换取长期稳定并通过BrowserPool让 Puppeteer 与 Playwright 站在同一套生命周期管理 API 之下。从本文可以看到所有迁移的核心思路是一致的把散落的、隐含的参数与行为收敛为显式的、统一的对象和 hooks——handler 参数收敛为 Crawling Context启动参数收敛为launchContext自定义行为收敛为各类 hooks。这一设计原则在如今 Crawlee 的browser-pool、puppeteer-crawler、playwright-crawler各包源码中依然清晰可见也是理解后续 v2、v3 系列演进的钥匙。【免费下载链接】crawleeCrawlee—A web scraping and browser automation library for Node.js to build reliable crawlers. In JavaScript and TypeScript. Extract data for AI, LLMs, RAG, or GPTs. Download HTML, PDF, JPG, PNG, and other files from websites. Works with Puppeteer, Playwright, Cheerio, JSDOM, and raw HTTP. Both headful and headless mode. With proxy rotation.项目地址: https://gitcode.com/GitHub_Trending/cr/crawlee创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考