
Crawlee 版本迭代深度解析从 v0.5 新特性看 Crawlee 请求管理与服务架构演进【免费下载链接】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/blog/2025/01-10/index.md为骨架结合当前仓库中 Crawlee JS 的源码实现系统拆解 Crawlee 的版本迭代思路新的包结构如何改善开发体验html_to_text、use_state等上下文助手如何简化数据提取stop()方法与请求加载器Request Loaders如何重塑爬虫生命周期与请求管理以及 Service Locator 如何让 Crawlee 适应不同运行环境。读完本文你将掌握这些特性的使用方式、适用场景以及它们背后的源码级设计原理。升级与快速开始Crawlee 每个里程碑版本都会带来新功能与改进v0.5 是当时有史以来最大的一次发布移植了更多来自 Crawlee JS 的功能、引入了 Python 独有的新特性、整合了包结构并修复了一批 bug。升级方式非常直接直接从 PyPI 安装最新版本即可pip install --upgrade crawlee从旧版本升级的用户需要关注官方的升级指南以确保平滑过渡。本文不重复完整的变更日志而是聚焦几大核心变化包结构重组、与 Crawlee JS 的功能对齐、Python 专属新特性以及底层服务架构。新的包结构按职责归类的子包v0.5 引入了整合后的包结构目标有三个让开发体验更顺畅、让开发者更快找到需要的爬虫类、让 IDE 在导入时给出更好的代码提示。爬虫类统一收拢到crawlers子包所有爬虫类以及它们对应的 Crawling Context 类都被归入统一的crawlers子包。导入语句的变化如下- from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler, BeautifulSoupCrawlingContext from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext如上图所示当输入from crawlee.crawlers import时IDE 会列出该子包中所有可导入的爬虫类与上下文类——BasicCrawler、BeautifulSoupCrawler、HttpCrawler等一目了然。这种集中归类方式让有哪些爬虫可用变得可见IDE 的自动补全也更精准。从源码结构看这一设计与 JS 端packages/下按 crawler 类型分包的思路一脉相承例如 packages/basic-crawler、packages/cheerio-crawler、packages/playwright-crawler 各自独立成包但 Python 端通过单一子包进一步降低了记忆成本。存储客户端收拢到storage_clients子包存储相关的客户端类也被统一移动到storage_clients子包- from crawlee.memory_storage_client import MemoryStorageClient from crawlee.storage_clients import MemoryStorageClient这种整合让每个类都有了清晰的归属IDE 在查找正确的爬虫或存储客户端时也能提供更好的自动补全。持续对齐 Crawlee JS上下文助手Crawlee 团队持续推动与 JS 库Crawlee JS的功能对齐。v0.5 移植了更多功能其中两个典型的上下文助手是html_to_text与use_state。html_to_text一键剥离 HTML 标签html_to_text上下文助手用于从 HTML 页面中提取纯文本它会自动移除所有标签只返回原始文本内容。它同时存在于ParselCrawlingContext与BeautifulSoupCrawlingContext中。import asyncio from crawlee.crawlers import ParselCrawler, ParselCrawlingContext async def main() - None: crawler ParselCrawler() crawler.router.default_handler async def handler(context: ParselCrawlingContext) - None: context.log.info(Crawling: %s, context.request.url) text context.html_to_text() # Continue with the processing... await crawler.run([https://crawlee.dev]) if __name__ __main__: asyncio.run(main())在这个例子中我们使用ParselCrawler抓取网页然后调用context.html_to_text()提取干净的文本用于后续处理。这类上下文助手的设计在 JS 端同样存在——它们被定义在 Crawling Context 上作为请求处理器内的便捷能力。例如 packages/core/src/crawlers/crawler_commons.ts 中定义的RestrictedCrawlingContext就集中声明了pushData、addRequests、useState、getKeyValueStore等一组上下文方法说明把高频能力挂在 context 上是 Crawlee 跨语言统一的接口哲学。use_state跨运行持久化的状态管理use_state上下文助手让创建和管理持久化状态变得简单所有状态值都会自动持久化支持在多次爬虫运行、重启和失败之间保持数据本质上是对KeyValueStore的便捷抽象。import asyncio from crawlee import Request from crawlee.configuration import Configuration from crawlee.crawlers import ParselCrawler, ParselCrawlingContext async def main() - None: # Create a crawler with purge_on_start disabled to retain state across runs. crawler ParselCrawler( configurationConfiguration(purge_on_startFalse), ) crawler.router.default_handler async def handler(context: ParselCrawlingContext) - None: context.log.info(fCrawling {context.request.url}) # Retrieve or initialize the state with a default value. state await context.use_state(state, default_value{runs: 0}) # Increment the run count. state[runs] 1 # Create a request with always_enqueue enabled to bypass deduplication and ensure it is processed. request Request.from_url(https://crawlee.dev/, always_enqueueTrue) # Run the crawler with the start request. await crawler.run([request]) # Fetch the persisted state from the key-value store. kvs await crawler.get_key_value_store() state await kvs.get_auto_saved_value(state) crawler.log.info(fFinal state after run: {state}) if __name__ __main__: asyncio.run(main())这段示例中有三个值得注意的细节purge_on_startFalse默认情况下 Crawlee 在每次运行启动时会清理旧存储若要跨运行保留状态必须关闭它always_enqueueTrueRequest.from_url创建请求时默认会去重设置always_enqueue可绕过去重确保同一 URL 在每次运行都会被处理——这正是演示状态跨运行累加所需的行为读取持久化状态运行结束后通过crawler.get_key_value_store()拿到 key-value store再get_auto_saved_value(state)读回最终状态。注意use_state是实验性功能其行为与接口可能在后续版本中演化。在 JS 端useState的底层实现位于 packages/core/src/storages/utils.ts它打开或创建一个KeyValueStore然后调用kvStore.getAutoSavedValueState(name || CRAWLEE_GLOBAL_STATE, defaultValue)——默认存储名是CRAWLEE_GLOBAL_STATE并且支持通过keyValueStoreName指定自定义 store、通过configuration注入配置。也就是说自动持久化的可变共享状态在两端都是同一套机制状态本质上就是 key-value store 中的一个自动保存值。JS 文档还特别强调了一点见 packages/core/src/crawlers/crawler_commons.tsuseState()刻意不是事务性的。在 JS 端如果需要在存储提交成功后再更新计数应使用afterStorageCommit回调避免item 回滚但计数已自增的不一致。全新特性Python 优先的能力除了移植 JS 功能v0.5 还引入了 Python 优先Python-first的新特性这些能力预计会在未来几个月进入 Crawlee JS。Crawler 的stop方法条件满足即优雅停机BasicCrawler以及所有继承自它的爬虫现在拥有stop方法当特定条件满足时例如找到了你想要的数据可以轻松中止爬取。import asyncio from crawlee.crawlers import ParselCrawler, ParselCrawlingContext async def main() - None: crawler ParselCrawler() crawler.router.default_handler async def handler(context: ParselCrawlingContext) - None: context.log.info(Crawling: %s, context.request.url) # Extract and enqueue links from the page. await context.enqueue_links() title context.selector.css(title::text).get() # Condition when you want to stop the crawler, e.g. you # have found what you were looking for. if Crawlee for Python in title: context.log.info(Condition met, stopping the crawler.) await crawler.stop() await crawler.run([https://crawlee.dev]) if __name__ __main__: asyncio.run(main())这个例子展示了先广撒网再精准收割的典型用法处理器内先enqueue_links()扩展爬取范围同时检查页面标题一旦命中目标即调用crawler.stop()。从 JS 端源码packages/basic-crawler/src/internals/basic-crawler.ts可以看到优雅停止的具体语义一旦stop()被调用任务循环的isTaskReadyFunction会返回false不再领取新请求但同时日志明确提示 Ongoing requests will be allowed to complete只有当所有进行中的请求都处理完毕后isFinishedFunction才返回true爬虫才真正关闭。也就是说stop()与立即中断不同它会等待正在处理的请求完成保证已领取任务的完整性这也是与timeoutSecs超时强制结束等机制的核心区别。Request loaders可插拔的请求来源v0.5 引入了三个新类RequestLoader接口、RequestManager接口与RequestManagerTandem。它们管理 Crawlee 如何访问和存储请求既可以把其他组件服务作为请求来源也可以选择与RequestQueue组合使用既能接入任意请求源又能把外部数据源与 Crawlee 标准的RequestQueue结合。官方为这套特性提供了完整的指南文档docs/guides/request_loaders.mdx。下面给出一个 Python 端的组合示例import asyncio from crawlee.crawlers import ParselCrawler, ParselCrawlingContext from crawlee.request_loaders import RequestList, RequestManagerTandem from crawlee.storages import RequestQueue async def main() - None: rl RequestList( [ https://crawlee.dev, https://apify.com, # Long list of URLs... ], ) rq await RequestQueue.open() # Combine them into a single request source. tandem RequestManagerTandem(rl, rq) crawler ParselCrawler(request_managertandem) crawler.router.default_handler async def handler(context: ParselCrawlingContext) - None: context.log.info(fCrawling {context.request.url}) # ... await crawler.run() if __name__ __main__: asyncio.run(main())此例将RequestList与RequestQueue组合成一个 tandem。实际上任何实现RequestLoader接口的类都可以替代RequestList以满足特定需求——例如从外部 API、数据库或文件读取请求。请求加载器体系一览在 JS 端这套抽象由两个接口和若干实现构成详见 docs/guides/request_loaders.mdx 中的类图IRequestLoaderpackages/core/src/storages/request_loader.ts爬取中只读请求流的基接口。提供getTotalCount()、getPendingCount()、getHandledCount()、fetchNextRequest()、markRequestAsHandled()、checkReadiness()以及可选的toTandem()与persistState()。它刻意不允许新增请求。IRequestManagerpackages/core/src/storages/request_manager.ts在只读接口之上扩展写能力——addRequest()、addRequestsBatched()、reclaimRequest()失败重试、purge()以及爬虫回传的recordPacingSignal()。RequestList管理静态 URL 列表的轻量实现单个运行周期内创建初始化后不能增删请求可容纳上百万 URL 且开销远低于逐个入队。SitemapRequestLoader按 Sitemaps 协议从 XML 与纯文本 sitemap 读取 URL 的专用加载器支持过滤解析在后台进行爬取可以在 sitemap 完全解析前启动。注意它不支持包含链接的 HTML 页面——那应该由普通爬虫配合enqueueLinks处理。RequestManagerTandempackages/core/src/storages/request_manager_tandem.ts把只读加载器与可写管理器组合在一起。ThrottlingRequestManager包装可写管理器按域名对请求限速详见下文。关键约定请求生命周期契约IRequestLoader接口文档packages/core/src/storages/request_loader.ts强调了一个易被忽略的生命周期契约每个通过fetchNextRequest()取出的请求都被视为进行中直到传给markRequestAsHandled()。加载器无法回收请求只有管理器能reclaim重试因此不标记 handled持久化的加载器在重启后会重新下发该请求导致重复爬取不标记 handledcheckReadiness()永远不会报告finished爬虫永不结束不标记 handledgetHandledCount()/getPendingCount()统计会失真。Tandem 的工作机制RequestManagerTandem的核心逻辑在 fetchNextRequest 中每次取请求时先检查只读加载器的checkReadiness()若加载器仍有待处理请求则通过transferNextRequestToQueue()把请求以{ forefront: true }转存到可写管理器随后统一从管理器侧取请求。这样所有请求都经过队列去重与重试行为保持一致同一 URL 不会被多次爬取。管理器还支持以工厂函数形式延迟打开requestManager可以是() IRequestManager并在首次使用时才解析。限速管理器应对 429 的调度层方案ThrottlingRequestManager在调度层处理限速。有些站点对突发流量返回 HTTP 429Too Many Requests而非直接封禁默认行为下 429 会被视为会话被封禁导致换新会话立即重试不断消耗代理却没有真正放慢速度。限速管理器则把 429 交给调度层处理import { CheerioCrawler, ThrottlingRequestManager } from crawlee; const crawler new CheerioCrawler({ requestManager: new ThrottlingRequestManager({ domains: [api.example.com], // optional, these are the defaults baseDelaySecs: 2, maxDelaySecs: 60, maxDomainStallSecs: 900, }), requestHandler: async ({ request }) { // ... }, });其行为要点配置项定义见 packages/core/src/storages/throttling_request_manager.ts列出要限速的域名请求会被路由进各自的队列收到 429 时遵循Retry-After头否则从baseDelaySecs起指数退避直到maxDelaySecs期间该域名请求被暂扣其他域名照常满速被限速的请求不消耗maxRequestRetries其会话也保持不动限速与会话无关若某域名持续限速超过maxDomainStallSecs默认 900 秒仍不放行任何请求爬取会以PersistentRateLimitError关闭——此时继续等待没有意义其请求会故意留在队列里禁用purgeOnStart重跑即可在限速解除后续爬域名匹配是精确且大小写不敏感的不支持通配符需要逐个列出子域名或设置throttleBy: registrableDomain把站点及其所有子域名归到同一组时钟下domains: all则给爬取中遇到的每个域名分配时钟与独立队列最多maxThrottledDomains默认 100超过会抛出异常如果爬取的域名远超此数建议改用maxRequestsPerMinute已发现域名列表保存在默认 key-value store 的persistStateKey下重启后会重新打开对应队列。每个被限速的域名运行两个时钟都走完才会派发请求Backoff响应式、临时性由 429 触发并随停止限速而衰减与Crawl delay主动式、恒定值两次派发间的最小间隔来自 robots.txt 的Crawl-delay由minCrawlDelaySecs托底。爬虫自身的sameDomainDelaySecs选项正是建立在domains: all 上述配置之上——若你显式传入自己的管理器则由它接管这个底线。请求管理器的 Pacing 信号IRequestManager新增的recordPacingSignal()packages/core/src/storages/request_manager.ts承载站点声明了以什么节奏接受请求的信息。信号分为三种reasonrateLimited来源拒绝请求因为我们太快如 429/503可选携带waitMsminInterval来源声明了请求的最小间隔如 robots.txt 的Crawl-delayminIntervalEverywhere爬取所有者声明的、适用于所有域名的全局底线如sameDomainDelaySecs这是唯一不带url的变体。信号还携带scopehostname/registrableDomain或任意字符串说明覆盖范围管理器可以把信号应用到更宽的 scope但绝不允许收窄否则会漏掉部分 URL 不限速。该方法是必选的——不进行限速的管理器如普通队列返回false并让爬虫告警信号被丢弃包装型管理器如 tandem则直接转发。Service locator面向运行时环境的服务注入ServiceLocator主要用于管理 Crawlee 依赖的内部服务具体包括Configuration、StorageClient和EventManager。通过替换这些组件可以让 Crawlee 适配不同的运行环境。方式一显式使用 service locatorimport asyncio from crawlee import service_locator from crawlee.configuration import Configuration from crawlee.crawlers import ParselCrawler, ParselCrawlingContext from crawlee.events import LocalEventManager from crawlee.storage_clients import MemoryStorageClient async def main() - None: service_locator.set_configuration(Configuration()) service_locator.set_storage_client(MemoryStorageClient()) service_locator.set_event_manager(LocalEventManager()) crawler ParselCrawler() # ... if __name__ __main__: asyncio.run(main())方式二直接把服务传给爬虫实例由其在内部完成设置import asyncio from crawlee.configuration import Configuration from crawlee.crawlers import ParselCrawler, ParselCrawlingContext from crawlee.events import LocalEventManager from crawlee.storage_clients import MemoryStorageClient async def main() - None: crawler ParselCrawler( configurationConfiguration(), storage_clientMemoryStorageClient(), event_managerLocalEventManager(), ) # ... if __name__ __main__: asyncio.run(main())在 JS 端ServiceLocator的实现位于 packages/core/src/service_locator.ts它提供getConfiguration()/setConfiguration()、getEventManager()/setEventManager()、getStorageBackend()/setStorageBackend()、getLogger()/setLogger()等成对方法。关键语义是若未显式设置则创建默认实例例如getStorageBackend()在persistStorage开启时返回FileSystemStorageBackend否则返回MemoryStorageBackend且替换已检索过的服务会抛出ServiceConflictError避免运行时组件不一致。Python 端的set_*API 与 JS 端的set*API 完全同构这再次印证了两端在架构设计上的对齐。结语Crawlee v0.5 代表了 Crawlee 一次重要的架构演进crawlers/storage_clients子包让类归属更清晰html_to_text、use_state等上下文助手降低了数据提取与状态管理的门槛stop()提供了条件式优雅停机Request loaders 体系RequestList、SitemapRequestLoader、RequestManagerTandem、ThrottlingRequestManager让请求来源可插拔、请求节奏可控Service Locator 则让运行时环境可定制。这些特性在 JS 端均有对应实现可循——从 packages/core/src/storages 下的请求管理相关源码到 packages/core/src/service_locator.ts再到 packages/basic-crawler/src/internals/basic-crawler.ts 的停止逻辑都能看到跨语言一致的设计哲学。若想深入了解请求加载器官方指南 docs/guides/request_loaders.mdx 及其配套示例request_loaders_rl_basic.ts、request_loaders_sitemap_basic.ts、request_loaders_rl_tandem_helper.ts 等是最好的起点。【免费下载链接】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),仅供参考