Crawlee v3 升级指南:从 Apify SDK v2 迁移到新一代 Node.js 爬虫框架

Crawlee v3 升级指南:从 Apify SDK v2 迁移到新一代 Node.js 爬虫框架 Crawlee v3 升级指南从 Apify SDK v2 迁移到新一代 Node.js 爬虫框架【免费下载链接】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本文是面向已在使用 Apify SDK v2即apify包的开发者编写的升级指南系统梳理 Crawleev3相对 Apify SDKv2的主要破坏性变更breaking changes。读完本文你将掌握Crawlee monorepo 的包结构与正确的安装方式、requestHandler/enqueueLinks/crawler.addRequests()/Request.label/crawler.useState()等新 API 的用法、从requestAsBrowser迁移到got-scraping/context.sendRequest()的参数映射表以及 Apify 平台相关助手迁移到 Apify SDKActor后的调用方式。Crawlee 是 Apify SDK 的精神继承者因此团队决定延续版本号将 Crawlee 直接发布为 v3。本文档基于仓库中的 upgrading_v3.md 整理并结合 packages 下的源码实现进行深度佐证。Crawlee 与 Apify SDK v2 的拆分在apify包的 v3 之前apify包同时包含爬虫相关工具和 Apify 平台相关的辅助方法。从 v3 开始整个项目被拆分为两个主要部分Crawlee——全新的网页爬取库以crawlee包发布到 NPMApify SDK——Apify 平台的辅助方法以apify包发布到 NPM。这解释了升级时最常见的困惑之前Apify.utils.*、Apify.events等 API 全部消失它们分别被迁移到了 Crawlee 或新的 Apify SDK 中。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-storage内存存储实现是apify/storage-local的替代方案crawlee/browser-pool浏览器池即原独立的browser-pool包crawlee/utils工具方法集合crawlee/types持有 TS 接口主要关于StorageClient安装 Crawlee大多数crawlee/*包是互相扩展并重新导出的因此只需安装你实际要用的那个包即可。例如如果你打算使用playwright安装crawlee/playwright就够了——它已经包含crawlee/browser包的全部内容而crawlee/browser又包含crawlee/basiccrawlee/basic又包含crawlee/core的全部内容。如果不在意多引入一些代码可以直接使用crawlee元包它重新导出了大多数crawlee/*包因此包含所有爬虫类。这一设计在源码中有直接体现packages/crawlee/src/index.ts 中crawlee元包通过export * from crawlee/core、crawlee/basic、crawlee/browser、crawlee/http、crawlee/jsdom、crawlee/linkedom、crawlee/cheerio、crawlee/puppeteer、crawlee/playwright等一次性聚合导出并额外提供utils命名空间含log、social、sleep、downloadListOfUrls、parseOpenGraph、extractMicrodata等。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包中预定义的 TypeScript 配置。不要忘记将module和target设置为ES2022或以上以便使用顶层 awaittop level await。apify/tsconfig配置默认启用了noImplicitAny在初始开发阶段你可能想禁用它因为如果代码中残留了未使用的局部变量它会导致构建失败。{ extends: apify/tsconfig, compilerOptions: { module: ES2022, target: ES2022, outDir: dist, lib: [DOM] }, include: [ ./src/**/* ] }Docker 构建对于Dockerfile官方推荐使用多阶段构建multi-stage build这样最终镜像中就不需要安装 TypeScript 等开发依赖# 使用多阶段构建因为我们需要 dev deps 来构建 TS 源码 FROM apify/actor-node:20 AS builder # 复制所有文件安装所有依赖包括 dev deps并构建项目 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第一阶段builder负责npm install --includedev npm run build产出dist第二阶段只复制package*.json、README.md、dist以及部署必需的apify.json/INPUT_SCHEMA.json再执行npm install --onlyprod从而显著缩小镜像体积。浏览器指纹Browser fingerprintsv2 中 Puppeteer 爬虫有一个神奇的stealth选项它启用了一系列技巧来尽可能模仿真实用户。虽然它在某种程度上有效但 Crawlee 决定用生成的浏览器指纹browser fingerprints来替代它。如果不想使用动态指纹可以通过browserPoolOptions中的useFingerprints禁用它const crawler new PlaywrightCrawler({ browserPoolOptions: { useFingerprints: false, }, });指纹缓存也经过了重构fingerprintsOptions重命名为fingerprintOptionsfingerprints→fingerprint并且fingerprintOptions现在接受useFingerprintCache和fingerprintCacheSize替代原来的useFingerprintPerProxyCache与fingerprintPerProxyCacheSize这两个选项已不可用。这是因为缓存的指纹不再与代理 URL 绑定而是与会话session绑定。Session cookie 方法重命名在 v2 中如果想获取或设置用于请求的会话 cookie必须调用session.getPuppeteerCookies()或session.setPuppeteerCookies()。由于这些方法可用于所有爬虫不只是PuppeteerCrawler因此它们被重命名为session.getCookies()和session.setCookies()。除此之外用法完全相同。在底层实现中会话的 cookie 由tough-cookie的CookieJar管理见 session.ts。该文件中的getCookieString(url)返回可直接用于 Cookie 头的key1value1; key2value2格式字符串而setCookie(rawCookie, url)将单个 cookie 字符串写入特定 URL 对应的 jar 中写入失败时仅记录 warning不会抛出异常。内存存储Memory storage当存储数据或中间状态例如RequestQueue持有的状态时Crawlee 现在默认使用crawlee/memory-storage。它是apify/storage-local的替代方案将状态保存在内存中而不是apify/storage-local使用的 SQLite 数据库。虽然状态保存在内存中但它也会将状态转储dump到文件系统因此我们可以观察它并且它尊重 KeyValueStore 中已有的数据例如INPUT.json文件。当想在 Apify 平台上运行爬虫时需要使用Actor.init或Actor.main它们会在 Apify 平台上自动将存储客户端切换到ApifyClient。我们仍然可以使用apify/storage-local只需先安装它然后将其传给Actor.init或Actor.main的选项apify/storage-localv2.1.0 是 Crawlee 所要求的版本import { Actor } from apify; import { ApifyStorageLocal } from apify/storage-local; const storage new ApifyStorageLocal(/* 像 enableWalMode 这样的选项放在这里 */); await Actor.init({ storage });默认存储的清理Purging of the default storage在 v2 中本地运行之间的状态会被保留我们必须使用apify-cli的--purge参数来清理。在 Crawlee 中这现在是默认行为调用Actor.init/main时会自动清理存储。可以通过在Actor.init选项中设置purge: false来退出该行为。从源码实现看清理逻辑被下沉到了存储类本身。例如 file-system-storage.ts 中实现了purge()方法第 309 行起它会遍历各存储默认default存储执行purgeExceptInput()保留INPUT文件非默认存储执行完整purge()同时StorageClient接口现在提供了可选的purge方法。purgeLocalStorage辅助函数已被移除清理能力直接归属存储类。重命名的爬虫选项与接口部分选项被重命名以更好地反映其作用。所有旧参数名仍然受支持但仅限运行时不再提供 TS 层面的类型支持旧名称v2新名称v3handleRequestFunctionrequestHandlerhandlePageFunctionrequestHandlerhandleRequestTimeoutSecsrequestHandlerTimeoutSecshandlePageTimeoutSecsrequestHandlerTimeoutSecsrequestTimeoutSecsnavigationTimeoutSecshandleFailedRequestFunctionfailedRequestHandler爬取上下文接口也按相同约定重新命名含义更清晰旧名称v2新名称v3CheerioHandlePageInputsCheerioCrawlingContextPlaywrightHandlePageFunctionPlaywrightCrawlingContextPuppeteerHandlePageFunctionPuppeteerCrawlingContext上下文感知的辅助函数Context aware helpers之前Apify.utils命名空间下的一些工具现在被移到了爬取上下文crawling context中并且是上下文感知的context aware。这意味着部分参数会自动从上下文中填充比如当前的Request实例、当前的Page对象或绑定到爬虫的RequestQueue。链接入队Enqueuing links一个获得较多关注的常用辅助函数是enqueueLinks。如上所述它是上下文感知的——我们不再需要传入requestQueue或page参数也不需要 cheerio 的$。此外它现在提供3 种入队策略EnqueueStrategy.Allall匹配找到的任何 URLEnqueueStrategy.SameHostnamesame-hostname匹配与基准 URL 具有相同子域hostname的任何 URL默认策略EnqueueStrategy.SameDomainsame-domain匹配与基准 URL 具有相同域名的任何 URL。例如对于基准 URLhttps://example.comhttps://wow.an.example.com和https://example.com都会被匹配。这意味着我们甚至可以不带任何参数调用enqueueLinks()。默认情况下它会遍历当前页面上找到的所有链接并只过滤出那些指向同一子域的链接。在当前的仓库源码中这组策略定义于 url.ts 的EnqueueStrategy枚举all/same-hostname/same-domain/same-origin并提供了matchesEnqueueStrategy()辅助函数用于判断目标 URL 是否符合指定策略。enqueueLinks的选项接口include/exclude/strategy/label/transformRequestFunction等则定义于 enqueue_links.ts其文档注释明确说明include模式与strategy之间是AND 逻辑——URL 必须至少匹配一个 include 模式且满足策略才会被入队transformRequestFunction具有最高优先级可以覆盖全局的label选项。此外我们还可以通过globs指定 URL 应该匹配的模式const crawler new PlaywrightCrawler({ async requestHandler({ enqueueLinks }) { await enqueueLinks({ globs: [https://crawlee.dev/*/*], // 这里也可以使用 regexps 和 pseudoUrls 键 }); }, });在底层实现中glob / 正则模式的编译是有缓存的见 shared.tsenqueueLinksPatternCache保证同一模式只编译一次缓存上限 1000 项超出后淘汰最早项以保持高频enqueueLinks()调用的性能。glob 匹配默认不区分大小写new Minimatch(glob, { nocase: true })如果需要区分大小写应使用RegExp。隐式的RequestQueue实例所有爬虫现在都可以通过crawler.getRequestQueue()方法自动获得RequestQueue实例。如果它尚不存在该方法会为你创建实例。这意味着我们不再需要手动创建RequestQueue实例可以直接使用下面描述的crawler.addRequests()方法。我们仍然可以显式创建RequestQueuecrawler.getRequestQueue()方法会尊重这一点并返回通过爬虫选项提供的实例。crawler.addRequests()现在可以批量添加多个请求。新添加的addRequests方法会帮我们处理一切它先入队前1000个请求并 resolve然后在后台继续处理其余的请求同样以 1000 个为一小批这样就不会触发任何 API 限流。这意味着爬取几乎可以立即开始最多几秒内这在以前只有结合RequestQueue和RequestList才能实现。// 会在前 1000 个请求入队后立即 resolve const result await crawler.addRequests([/* 大量请求甚至可以是数百万个 */]); // 如果想等待所有请求都添加完成可以 await waitForAllRequestsToBeAdded 这个 promise await result.waitForAllRequestsToBeAdded;从源码实现看该方法位于 basic-crawler.tsaddRequests方法约第 2097 行起它支持通过batchSize选项配置批大小、waitBetweenBatchesMillis配置批次间的休眠时间并返回带有waitForAllRequestsToBeAdded的响应对象同时它也是隐式RequestQueue上addRequestsBatched()的别名且支持用include/exclude模式与strategy过滤请求与enqueueLinks相同的 AND 逻辑由于没有当前页面可锚定strategy默认为all。更精简的错误日志Less verbose error logging在 v2 中请求处理器内部抛出的错误会导致记录完整的错误对象。在 Crawlee 中只要我们知道该请求将被重试就只把错误消息作为 warning 记录。如果想恢复 v2 那样冗长的日志请使用CRAWLEE_VERBOSE_LOG环境变量。对应实现同样在 basic-crawler.ts当请求将要被重试时第 2870 行附近只有在设置了CRAWLEE_VERBOSE_LOG时才打印完整堆栈否则仅打印错误消息getMessageFromError第 2937 行附近process.env.CRAWLEE_VERBOSE_LOG ? error.stack : error.message。Request.label快捷方式给请求打标签label在 v2 中是通过Request.userData对象实现的。在 Crawlee 中我们还可以使用Request.label快捷方式。它实现为一对get/set内部读写Request.userData中的值。这个快捷方式的支持也添加到了enqueueLinks的选项接口中。async requestHandler({ request, enqueueLinks }) { if (request.label ! DETAIL) { await enqueueLinks({ globs: [...], label: DETAIL, }); } }对应实现见 request.ts第 364-372 行get label()返回this.userData.labelset label(value)写入this.userData.label——正如文档注释所写它是request.userData.label的快捷方式。移除requestAsBrowser在 v1 中requestAsBrowser的底层实现被替换为对got-scraping的简单代理——这是我们对got的自定义扩展尽可能模仿真实浏览器。在 v3 中我们移除了requestAsBrowser鼓励直接使用got-scraping。为了便于迁移我们还添加了context.sendRequest()辅助函数它允许将上下文绑定的Request对象通过got-scraping处理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 会自动进行协议协商且 HTTP/2 已被设置为true——如今 100% 的浏览器都支持 HTTP/2 请求而且越来越多的网站也在使用它重命名的选项在requestAsBrowser方案中部分选项的命名不同。以下是重命名选项的列表payload该选项表示要发送的请求体可以是string或Buffer。不过现在已经没有payload选项了你需要改用body或者如果想发送 JSON用json。示例如下// Before: await Apify.utils.requestAsBrowser({ …, payload: Hello, world! }); await Apify.utils.requestAsBrowser({ …, payload: Buffer.from(c0ffe, hex) }); await Apify.utils.requestAsBrowser({ …, json: { hello: world } }); // After: await gotScraping({ …, body: Hello, world! }); await gotScraping({ …, body: Buffer.from(c0ffe, hex) }); await gotScraping({ …, json: { hello: world } });ignoreSslErrors它被重命名为https.rejectUnauthorized。默认情况下为方便起见设为false。不过如果你想确保连接是安全的可以这样做// Before: await Apify.utils.requestAsBrowser({ …, ignoreSslErrors: false }); // After: await gotScraping({ …, https: { rejectUnauthorized: true } });请注意两者的语义是相反的因此我们需要同时反转取值。header-generator选项useMobileVersion、languageCode和countryCode不再存在。你需要直接使用headerGeneratorOptions// Before: await Apify.utils.requestAsBrowser({ …, useMobileVersion: true, languageCode: en, countryCode: US, }); // After: await gotScraping({ …, headerGeneratorOptions: { devices: [mobile], // 或 [desktop] locales: [en-US], }, });timeoutSecs要设置超时请使用timeout.request现在单位是毫秒// Before: await Apify.utils.requestAsBrowser({ …, timeoutSecs: 30, }); // After: await gotScraping({ …, timeout: { request: 30 * 1000, }, });throwOnHttpErrorsthrowOnHttpErrors→throwHttpErrors。该选项在 HTTP 状态码不成功时例如404抛出异常。默认值为false。decodeBodydecodeBody→decompress。该选项用于解压响应体。默认值为true——请不要修改它否则网站会出错除非你知道自己在做什么。abortFunction该函数曾用于在特定响应上使 promise 抛出异常当它返回true时。但它并不是很有用。你更可能想要的是取消请求可以这样做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;移除浏览器池插件混用Removal of browser pool plugin mixing在 v2 中你可以拥有一个混合使用 Puppeteer 和 Playwright 插件的浏览器池甚至是你自己构建的自定义插件。从本版本开始这不再被允许创建这样的浏览器池会抛出错误预期所有使用的插件都是同一类型。:::info 感到困惑举个例子这个变更禁止一个池混合使用 Puppeteer 和 Playwright。但你仍然可以创建使用多个 Playwright 插件、每个插件使用不同 launch 器的池:::在浏览器之外处理请求还有一个值得一提的小特性能够在浏览器之外处理浏览器爬虫的请求。为此我们可以组合使用Request.skipNavigation和context.sendRequest()。可以查看 跳过某些请求的导航示例 来了解如何实现该示例展示了这样一个场景爬取时遇到某些只想保存的资源如通过 CDN 分发的图片时组合Request#skipNavigation与sendRequest直接从 CDN 抓取图片并存入 key-value store而无需动用完整浏览器能力。日志LoggingCrawlee 直接以命名导出的方式导出默认的log实例。我们在爬取上下文中还提供了一个作用域scopedlog实例——它会以爬虫名称作为前缀记录日志在请求处理器内部应优先使用它const crawler new CheerioCrawler({ async requestHandler({ log, request }) { log.info(Opened ${request.loadedUrl}); }, });自动保存的爬虫状态Auto-saved crawler state每个爬虫实例现在都有useState()方法返回一个可以使用的状态对象。当persistState事件发生时它会自动保存。该值是缓存的所以你可以放心地多次调用此方法并获得完全相同的引用。也无需担心保存值的问题因为它会自动发生。const crawler new CheerioCrawler({ async requestHandler({ crawler }) { const state await crawler.useState({ foo: [] as number[] }); // 直接修改值即可无需关心保存 state.foo.push(123); }, });从源码实现看basic-crawler.ts约第 2013 行起useState()基于 KeyValueStore 的getAutoSavedValueState()实现如果爬虫实例有显式id状态键为CRAWLEE_STATE_KEY_id否则使用匿名共享键当多个爬虫实例在无显式id的情况下调用useState()时会发出 warning提示它们将共享同一个状态对象这通常不是预期的并建议为每个爬虫实例提供唯一的id选项。Apify SDKApify 平台辅助方法现在可以在 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 中。所有这些方法都是 async 的应该被 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()会处理资源拆除tear down并调用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)的快捷方式同时会考虑环境变量中的 tokenApify.callTask()现在是运行ApifyClient.task(taskId).call(input, options)的快捷方式同时会考虑环境变量中的 tokenApify.metamorph()现在是运行ApifyClient.task(taskId).metamorph(input, options)的快捷方式同时会考虑环境变量中的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可以是 async 的。.newUrl()和.newProxyInfo()现在返回 promiseprepareRequestFunction和postResponseFunction选项已移除改用导航钩子navigation hooksgotoFunction和gotoTimeoutSecs已移除移除了对带有 nullRequest属性的旧/损坏请求队列的兼容性修复fingerprintsOptions重命名为fingerprintOptionsfingerprints→fingerprintfingerprintOptions现在接受useFingerprintCache和fingerprintCacheSize替代不再可用的useFingerprintPerProxyCache与fingerprintPerProxyCacheSize因为缓存的指纹不再与代理 URL 绑定而是与会话绑定。升级小结从 Apify SDK v2 迁移到 Crawlee v3最核心的心智变化可以归纳为三条主线包拆分爬取能力与平台能力分离crawlee及其crawlee/*子包负责爬取apifyActor负责 Apify 平台集成API 现代化requestHandler、上下文感知的enqueueLinks、隐式RequestQueuecrawler.addRequests()、Request.label、crawler.useState()等新 API 让代码更简洁HTTP 客户端重构requestAsBrowser退出历史舞台统一到got-scraping/context.sendRequest()相关选项payload→body、ignoreSslErrors→https.rejectUnauthorized、timeoutSecs→timeout.request等按文中映射表逐一调整即可平滑迁移。【免费下载链接】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),仅供参考