Crawlee v3 升级完全指南:从 Apify SDK v2 迁移的破坏性变更与实战迁移手册

发布时间:2026/9/12 18:59:53
Crawlee v3 升级完全指南:从 Apify SDK v2 迁移的破坏性变更与实战迁移手册 Crawlee v3 升级完全指南从 Apify SDK v2 迁移的破坏性变更与实战迁移手册【免费下载链接】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/crawleeCrawlee v3 是 Apify SDK v2 的精神继承者它将原本捆绑在一起的爬虫工具与 Apify 平台辅助方法拆分为两个独立的 NPM 包并引入了包名、TS 类型、存储机制、会话 Cookie API、爬虫选项、上下文辅助方法context-aware helpers等一系列破坏性变更。本文以 upgrading_v3.md 为骨架结合当前仓库的源码实现与文档系统梳理从 Apify SDK v2 迁移到 Crawlee v3 的完整变更清单帮助你理解每个变更背后的设计动机并给出可直接复制的迁移代码。背景为什么会有 v3在apifyv3 之前apify包同时包含爬虫相关工具与 Apify 平台辅助方法。v3 将整个项目拆分为两个主要部分Crawlee新的 Web 爬取库以crawlee包发布在 NPM 上Apify SDKApify 平台辅助方法以apify包发布在 NPM 上。Crawlee 作为 Apify SDK 的精神继任者spiritual successor沿用了原有版本号体系因此 Crawlee 的第一个正式版本就是 v3。Crawlee monorepo从单包到多包架构crawlee包由若干更小的包组成这些包以crawlee命名空间独立发布包名职责crawlee/core所有爬虫实现的基础包含Request、RequestQueue、RequestList、Dataset等类crawlee/cheerio导出CheerioCrawlercrawlee/playwright导出PlaywrightCrawlercrawlee/puppeteer导出PuppeteerCrawlercrawlee/jsdom导出JSDOMCrawlercrawlee/basic导出BasicCrawlercrawlee/http导出HttpCrawler用于构建crawlee/jsdom与crawlee/cheeriocrawlee/browser导出BrowserCrawler用于构建crawlee/playwright与crawlee/puppeteercrawlee/memory-storageapify/storage-local的替代方案crawlee/browser-pool原browser-pool包crawlee/utils工具方法原Apify.utils下的一部分crawlee/types主要承载StorageClient等 TS 接口定义当前仓库正是这一 monorepo 结构的落地实现packages/目录下的core、basic-crawler、cheerio-crawler、http-crawler、jsdom-crawler、browser-crawler、playwright-crawler、puppeteer-crawler、browser-pool、utils、types等即对应上述各包。安装 Crawlee大多数 Crawlee 包之间是相互扩展和重导出的关系因此只需安装你实际要用的那个包即可。例如如果计划使用playwright安装crawlee/playwright就足够了——它已经包含crawlee/browser包的全部内容而crawlee/browser又包含crawlee/basiccrawlee/basic又包含crawlee/core。如果不在乎多引入一些额外代码可以直接使用crawlee元包meta-package它重导出了大部分crawlee/*包因此包含全部爬虫类npm install crawlee如果只需要 cheerio 支持可以只安装crawlee/cheerionpm install crawlee/cheerio使用playwright或puppeteer时仍需显式安装这些浏览器依赖——这样可以让用户自行控制使用哪个版本npm install crawlee playwright # 或 npm install crawlee/playwright playwright有时你可能想使用crawlee/utils中的工具方法也可以单独安装它。该包包含了一些之前在Apify.utils下的工具浏览器相关的工具也可以在爬虫包中找到如crawlee/playwright。完整的 TypeScript 支持Crawlee 和 Apify SDK 都是完整的 TypeScript 重写因此包内自带最新的类型定义。对于 TypeScript 爬虫官方推荐使用apify/tsconfig包中预定义的类型配置。不要忘记将module和target设置为ES2022或以上以便使用顶层awaittop level await。{ extends: apify/tsconfig, compilerOptions: { module: ES2022, target: ES2022, outDir: dist, lib: [DOM] }, include: [ ./src/**/* ] }apify/tsconfig配置启用了noImplicitAny。初始开发阶段你可能想禁用它因为如果代码中残留未使用的局部变量它会导致构建失败。Docker 多阶段构建对于Dockerfile推荐使用多阶段构建这样最终镜像中不会安装 TypeScript 等开发依赖# 使用多阶段构建因为构建 TS 源码需要 dev 依赖 FROM apify/actor-node:20 AS builder # 复制所有文件安装所有依赖包括 dev 依赖并构建项目 COPY . ./ RUN npm install --includedev \ npm run build # 创建最终镜像 FROM apify/actor-node:20 # 只复制必要文件 COPY --frombuilder /usr/src/app/package*.json ./ COPY --frombuilder /usr/src/app/README.md ./ COPY --frombuilder /usr/src/app/dist ./dist COPY --frombuilder /usr/src/app/apify.json ./apify.json COPY --frombuilder /usr/src/app/INPUT_SCHEMA.json ./INPUT_SCHEMA.json # 只安装生产依赖 RUN npm --quiet set progressfalse \ npm install --onlyprod --no-optional \ echo Installed NPM packages: \ (npm list --onlyprod --no-optional --all || true) \ echo Node.js version: \ node --version \ echo NPM version: \ npm --version # 运行编译后的代码 CMD npm run start:prod浏览器指纹Browser fingerprintsv2 中 Puppeteer 爬虫有一个魔法般的stealth选项通过若干技巧尽可能模拟真实用户。虽然它在一定程度上有效但 v3 决定用生成的浏览器指纹generated browser fingerprints取代它。如果不想要动态指纹可以在browserPoolOptions中通过useFingerprints禁用此行为const crawler new PlaywrightCrawler({ browserPoolOptions: { useFingerprints: false, }, });从当前仓库的实现看指纹缓存从按代理 URL 关联演进为按会话关联fingerprintOptions现在接受useFingerprintCache和fingerprintCacheSize取代了此前的useFingerprintPerProxyCache和fingerprintPerProxyCacheSize因为缓存的指纹不再与代理 URL 绑定而是与会话绑定见下文内部破坏性变更。这与 avoid_blocking_playwright.ts 等反封锁示例中指纹的实际用途一致。会话 Cookie 方法重命名v2 中要获取或添加本次请求将要使用的会话 Cookie需要调用session.getPuppeteerCookies()或session.setPuppeteerCookies()。由于该方法可以用于任何爬虫不只PuppeteerCrawlerv3 将它们重命名为session.getCookies()和session.setCookies()用法完全一致。从源码来看会话 Cookie 的存取已成为通用能力packages/browser-pool的BrowserController抽象类定义了getCookies(page)与setCookies(page, cookies)见 browser-controller.tsPlaywright、Puppeteer 乃至 Stagehand 控制器分别实现而浏览器爬虫在恢复会话状态时正是通过session.cookieJar.getCookies(request.url)来同步 Cookie见 browser-crawler.ts。内存存储Memory storage存储数据或中间状态例如RequestQueue持有的状态时v3 默认使用crawlee/memory-storage。它是apify/storage-local的替代品将状态保存在内存中apify/storage-local则使用 SQLite 数据库。虽然状态保存在内存中它也会转储到文件系统以便观察数据同时会尊重KeyValueStore中已有的数据例如INPUT.json文件。在 Apify 平台上运行爬虫时需要使用Actor.init或Actor.main它们会在 Apify 平台上自动将存储客户端切换为ApifyClient。仍然可以使用apify/storage-local先安装它再传给Actor.init或Actor.main的选项使用 Crawlee 需要apify/storage-localv2.1.0。import { Actor } from apify; import { ApifyStorageLocal } from apify/storage-local; const storage new ApifyStorageLocal(/* options like enableWalMode belong here */); await Actor.init({ storage });默认存储的清理Purgingv2 中本地多次运行之间的状态会被保留必须使用apify-cli的--purge参数手动清理。在 Crawlee 中自动清理成为默认行为调用Actor.init/main时会自动清理存储。可以通过Actor.init选项中的purge: false选择退出。重命名的爬虫选项与接口部分选项被重命名以更准确地反映其用途。旧参数名仍然被支持在运行时层面但 TS 类型层面不再提供旧选项新选项handleRequestFunctionrequestHandlerhandlePageFunctionrequestHandlerhandleRequestTimeoutSecsrequestHandlerTimeoutSecshandlePageTimeoutSecsrequestHandlerTimeoutSecsrequestTimeoutSecsnavigationTimeoutSecshandleFailedRequestFunctionfailedRequestHandler爬取上下文接口也按同样的约定重命名语义更加明确CheerioHandlePageInputs→CheerioCrawlingContextPlaywrightHandlePageFunction→PlaywrightCrawlingContextPuppeteerHandlePageFunction→PuppeteerCrawlingContext上下文感知的辅助方法Context aware helpers原本位于Apify.utils命名空间下的一部分工具现在被移入爬取上下文crawling context并且是上下文感知的一些参数会自动从上下文中填充例如当前的Request实例、当前的Page对象或绑定到爬虫的RequestQueue。链接入队enqueueLinks最受关注的辅助方法是enqueueLinks。如上面所说它是上下文感知的——不再需要传入requestQueue或page参数也不需要 cheerio 的$。除此之外它现在提供 3 种入队策略EnqueueStrategy.Allall匹配找到的任何 URLEnqueueStrategy.SameHostnamesame-hostname匹配与基准 URL 具有相同子域的任何 URL默认EnqueueStrategy.SameDomainsame-domain匹配与基准 URL 具有相同域名的任何 URL。例如对于基准 URLhttps://example.comhttps://wow.an.example.com和https://example.com都会被匹配。这意味着甚至可以不带任何参数调用enqueueLinks()。默认情况下它会遍历当前页面找到的所有链接只过滤那些指向相同子域的链接。在仓库中策略枚举定义于 url.ts其注释精确解释了 Domain / Hostname / Origin 的区别crawlee.dev是https://example.crawlee.dev/的 domainexample.crawlee.dev是 hostnamehttps://example.crawlee.dev是 origin。策略的实际过滤逻辑位于 enqueue_links.ts其中SameOrigin与SameHostname行为一致但会尊重 URL 的协议。此外还可以通过 glob 指定 URL 应匹配的模式const crawler new PlaywrightCrawler({ async requestHandler({ enqueueLinks }) { await enqueueLinks({ globs: [https://crawlee.dev/*/*], // 也可以使用 regexps 和 pseudoUrls 键 }); }, });隐式RequestQueue实例所有爬虫现在都可以通过crawler.getRequestQueue()方法自动获得RequestQueue实例。如果实例尚不存在它会为你创建。这意味着不再需要手动创建RequestQueue可以直接使用下面描述的crawler.addRequests()方法。仍然可以显式创建RequestQueuecrawler.getRequestQueue()方法会尊重通过爬虫选项传入的实例并返回它。在 basic-crawler.ts 中可以看到getRequestQueue()的实现它返回IRequestManager在内部会根据配置懒加载创建请求管理器。crawler.addRequests()批量添加请求现在可以批量添加多个请求。新增的addRequests方法会处理一切它先入队前 1000 个请求并立即 resolve同时在后台继续处理剩余部分同样是每批 1000 个这样就不会触发任何 API 速率限制。这意味着爬取几乎会立即开始最多几秒内这在以前只有组合使用RequestQueue和RequestList才能做到。// 在前 1000 个请求添加完成后立即 resolve const result await crawler.addRequests([/* 很多请求甚至可以是数百万个 */]); // 如果想等待所有请求都添加完成可以 await waitForAllRequestsToBeAdded promise await result.waitForAllRequestsToBeAdded;从源码实现basic-crawler.ts看addRequests还支持label/userData等选项应用到整批请求并可通过include/exclude模式与strategy默认EnqueueStrategy.All结合过滤 URL——这与enqueueLinks的过滤语义保持一致。更简洁的错误日志v2 中请求处理器内部抛出的错误会记录完整的错误对象。在 Crawlee 中只要我们知道请求会被重试就只把错误消息记录为警告。如果想像 v2 一样启用详细日志请使用CRAWLEE_VERBOSE_LOG环境变量。Request.label快捷方式给请求打标签以前是通过Request.userData对象实现的。在 Crawlee 中还可以使用Request.label快捷方式。它实现为一对get/set访问器读写Request.userData中的值。enqueueLinks的选项接口也支持该快捷方式。在 request.ts 中可以看到它的实现get label()直接返回this.userData.labelset label(value)将其写回userData——因此它与旧写法完全兼容。async requestHandler({ request, enqueueLinks }) { if (request.label ! DETAIL) { await enqueueLinks({ globs: [...], label: DETAIL, }); } }移除requestAsBrowserv1 中requestAsBrowser的底层实现被替换为对got-scraping的代理调用got-scraping是got的自定义扩展尽可能模拟真实浏览器。v3 中移除了requestAsBrowser鼓励直接使用got-scraping。为了方便迁移还新增了context.sendRequest()辅助方法它允许通过got-scraping处理上下文绑定的Request对象const crawler new BasicCrawler({ async requestHandler({ sendRequest, log }) { // 可以使用 options 参数覆盖 gotScraping 选项 const res await sendRequest({ responseType: json }); log.info(received body, res.body); }, });关于sendRequest()的详细用法可参考 Got Scraping 指南。被移除的选项useInsecureHttpParser已移除永久设置为true以更好地模拟浏览器行为。useHttp2已移除。Got Scraping 会自动进行协议协商且useHttp2已内置为true——如今 100% 的浏览器都支持 HTTP/2 请求Web 上也有越来越多的站点在使用它。被重命名的选项在requestAsBrowser方案中部分选项的命名不同。下面是重命名清单payload该选项表示要发送的请求体可以是string或Buffer。但现在已经没有payload选项了需要使用body如果要发送 JSON则用json// 之前 await Apify.utils.requestAsBrowser({ …, payload: Hello, world! }); await Apify.utils.requestAsBrowser({ …, payload: Buffer.from(c0ffe, hex) }); await Apify.utils.requestAsBrowser({ …, json: { hello: world } }); // 之后 await gotScraping({ …, body: Hello, world! }); await gotScraping({ …, body: Buffer.from(c0ffe, hex) }); await gotScraping({ …, json: { hello: world } });ignoreSslErrors重命名为https.rejectUnauthorized。默认设置为false方便使用。如果希望确保连接安全可以这样做// 之前 await Apify.utils.requestAsBrowser({ …, ignoreSslErrors: false }); // 之后 await gotScraping({ …, https: { rejectUnauthorized: true } });注意两者的语义是相反的因此取值也需要反转。header-generator选项useMobileVersion、languageCode和countryCode不再存在需要直接使用headerGeneratorOptions// 之前 await Apify.utils.requestAsBrowser({ …, useMobileVersion: true, languageCode: en, countryCode: US, }); // 之后 await gotScraping({ …, headerGeneratorOptions: { devices: [mobile], // 或 [desktop] locales: [en-US], }, });timeoutSecs设置超时请使用timeout.request现在是毫秒// 之前 await Apify.utils.requestAsBrowser({ …, timeoutSecs: 30, }); // 之后 await gotScraping({ …, timeout: { request: 30 * 1000, }, });throwOnHttpErrorsthrowOnHttpErrors→throwHttpErrors。该选项在遇到不成功的 HTTP 状态码例如404时抛出错误。默认值为false。decodeBodydecodeBody→decompress。该选项对响应体进行解压。默认为true——请不要更改它否则网站会出错除非你知道自己在做什么。abortFunction这个函数曾经在特定响应上返回true时让 promise 抛出错误但它并没有那么有用。更合适的做法是取消请求const promise gotScraping(…); promise.on(request, request { // 请注意这不是 Got 的 Request 实例而是 ClientRequest 实例。 // https://nodejs.org/api/http.html#class-httpclientrequest if (request.protocol ! https:) { // 不安全的请求中止。 promise.cancel(); // 如果设置了 isStream 为 true请改用 stream.destroy()。 } }); const response await promise;移除浏览器池插件混用v2 中可以创建一个混用 Puppeteer 和 Playwright 插件甚至是自建自定义插件的浏览器池。从 v3 起不再允许这样做创建此类浏览器池会抛出错误期望所有使用的插件都是同一类型。以示例说明这一变更禁止混用 Puppeteer 与 Playwright。但你仍然可以创建使用多个 Playwright 插件、各自使用不同启动器的池这一限制在仓库的测试中也有体现例如 no-hybrid-plugins.test.ts 专门覆盖了禁止混用插件类型的行为。在浏览器之外处理请求一个小而值得一提的特性是可以用浏览器爬虫在浏览器之外处理请求。为此可以组合使用Request.skipNavigation和context.sendRequest()。可参考 跳过特定请求的导航 示例了解具体实现方式。日志LoggingCrawlee 直接以命名导出的方式导出默认的log实例。爬取上下文中也提供了一个带作用域的log实例——它记录的消息会带有爬虫名称前缀应优先用于请求处理器内部的日志记录。const crawler new CheerioCrawler({ async requestHandler({ log, request }) { log.info(Opened ${request.loadedUrl}); }, });自动保存的爬虫状态每个爬虫实例现在都有useState()方法返回一个可用的状态对象。当persistState事件发生时它会自动保存。该值是缓存的因此可以随意多次调用此方法并获得完全相同的引用。也无需担心保存问题因为它是自动发生的。const crawler new CheerioCrawler({ async requestHandler({ crawler }) { const state await crawler.useState({ foo: [] as number[] }); // 直接改值无需关心保存 state.foo.push(123); }, });从源码看basic-crawler.tsuseState()通过KeyValueStore.getAutoSavedValue()实现默认状态键为CRAWLEE_STATE。值得注意的是如果多个未显式设置id的爬虫实例都调用useState()它们会共享同一个状态对象——源码会发出警告并建议为每个爬虫实例提供唯一的id选项例如new BasicCrawler({ id: my-crawler-1, ... })。Apify SDK 侧的变化Apify 平台辅助方法现在可以在 Apify SDKapifyNPM 包中找到。它导出的Actor类提供以下静态辅助方法ApifyClient快捷方式addWebhook()、call()、callTask()、metamorph()在 Apify 平台运行的辅助方法init()、exit()、fail()、main()、isAtHome()、createProxyConfiguration()存储支持getInput()、getValue()、openDataset()、openKeyValueStore()、openRequestQueue()、pushData()、setValue()事件支持on()、off()其他工具getEnv()、newClient()、reboot()Actor.main只是语法糖在开头调用Actor.init()在结尾调用Actor.exit()并将用户函数包裹在 try/catch 中。所有这些方法都是异步的应当await——在 Node 16 中可以使用顶层await。也就是说下面两段代码是等价的import { Actor } from apify; await Actor.init(); // your code await Actor.exit(Crawling finished!);import { Actor } from apify; await Actor.main(async () { // your code }, { statusMessage: Crawling finished! });Actor.init()会在 Apify 平台上运行时条件性地将 Crawlee 的存储实现切换为ApifyClient否则保持默认内存存储实现。它还会订阅 websocket 事件或在本地模拟它们。Actor.exit()负责拆除资源并调用process.exit()确保进程不会因某种原因无限挂起。事件EventsApify SDKv2导出Apify.events它是一个EventEmitter实例。在 Crawlee 中事件改由EventManager类管理。可以通过Actor.eventManagergetter 访问它或使用Actor.on和Actor.off快捷方式。-Apify.events.on(...); Actor.on(...);也可以通过Configuration.getEventManager()获取EventManager实例。除了已有的事件现在还有一个exit事件在调用Actor.exit()时触发Actor.main()结束时也会调用。这个事件允许你在调用Actor.exit时优雅地关闭任何资源。更小/内部的破坏性变更以下变更同样值得关注尤其是进行大规模迁移时Apify.call()现在只是运行ApifyClient.actor(actorId).call(input, options)的快捷方式同时会考虑 env vars 中的 tokenApify.callTask()现在只是运行ApifyClient.task(taskId).call(input, options)的快捷方式同时会考虑 env vars 中的 tokenApify.metamorph()现在只是运行ApifyClient.task(taskId).metamorph(input, options)的快捷方式同时会考虑 env vars 中的ACTOR_RUN_IDApify.waitForRunToFinish()已移除请改用ApifyClient.waitForFinish()Actor.main/init默认清理存储移除了purgeLocalStorage辅助方法清理逻辑移入存储类本身StorageClient接口现在有可选的purge方法清理通过Actor.init()自动发生可以在init/main方法的选项中通过purge: false选择退出QueueOperationInfo.request不再可用Request.handledAt现在是 ISO 格式的字符串日期Request.inProgress和Request.reclaimed现在是Set而不是 POJOpuppeteer utils 中的injectUnderscore已移除APIFY_MEMORY_MBYTES不再被考虑请改用CRAWLEE_AVAILABLE_MEMORY_RATIO部分AutoscaledPool选项不再可用cpuSnapshotIntervalSecs和memorySnapshotIntervalSecs被顶层配置systemInfoIntervalMillis取代maxUsedCpuRatio移至顶层配置ProxyConfiguration.newUrlFunction可以是异步的.newUrl()和.newProxyInfo()现在返回 promiseprepareRequestFunction和postResponseFunction选项已移除请改用导航钩子navigation hooksgotoFunction和gotoTimeoutSecs已移除移除了对旧/损坏请求队列其Request属性为 null的兼容性修复fingerprintsOptions重命名为fingerprintOptionsfingerprints→fingerprintfingerprintOptions现在接受useFingerprintCache和fingerprintCacheSize取代了不再可用的useFingerprintPerProxyCache和fingerprintPerProxyCacheSize。这是因为缓存的指纹不再与代理 URL 关联而是与会话关联。迁移检查清单综合以上全部变更从 Apify SDK v2 迁移到 Crawlee v3 时建议按以下顺序逐项核对替换导入将apify包中的爬虫相关导入改为crawlee或对应crawlee/*包平台相关代码保留在apify的Actor中更新爬虫选项将handleRequestFunction/handlePageFunction改为requestHandlerhandleFailedRequestFunction改为failedRequestHandler超时选项改为requestHandlerTimeoutSecs/navigationTimeoutSecs更新类型名将CheerioHandlePageInputs等改为对应的*CrawlingContext利用上下文辅助方法删除enqueueLinks调用中手动传入的requestQueue/page/$参数改用三种EnqueueStrategy过滤用crawler.getRequestQueue()取代手动创建队列批量入队用crawler.addRequests()代替手动RequestQueue.addRequest循环并按需await result.waitForAllRequestsToBeAdded替换requestAsBrowser改用got-scraping或上下文中的sendRequest()并按映射表迁移payload、ignoreSslErrors、timeoutSecs、throwOnHttpErrors、decodeBody、abortFunction及 header-generator 选项迁移存储与会话 API确认默认内存存储的行为、Actor.init/main的自动清理语义将session.getPuppeteerCookies()改为session.getCookies()启用 TypeScript使用apify/tsconfig并设置module/target为ES2022配合多阶段 Docker 构建。相关阅读Apify 平台部署指南了解Actor.init/Actor.main在平台上的完整用法Got Scraping 指南sendRequest()与got-scraping的深入实践跳过导航示例Request.skipNavigationsendRequest()组合实战会话管理指南session.getCookies()/setCookies()的完整使用场景升级到 v4了解后续版本的进一步演进。【免费下载链接】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),仅供参考