深入理解 Scrapy 架构:Engine、Scheduler、Downloader 与 Spiders 的完整数据流 深入理解 Scrapy 架构Engine、Scheduler、Downloader 与 Spiders 的完整数据流【免费下载链接】scrapyScrapy, a fast high-level web crawling scraping framework for Python.项目地址: https://gitcode.com/GitHub_Trending/sc/scrapy本文以 Scrapy 官方架构文档 architecture.rst 为主体完整讲解 Scrapy 的组件划分与数据流从 Spider 发起初始请求到响应解析、Item 落库的 9 步循环并结合仓库中 scrapy/core/engine.py、scrapy/core/scheduler.py、scrapy/core/downloader/init.py 等核心源码说明每个组件的真实实现位置、关键方法与默认配置帮助你在编写 Spider、中间件或自定义调度器时能准确理解每个数据包的流转路径与事件触发时机。架构总览Scrapy 采用执行引擎Engine为中心的组件化架构Engine 负责控制系统内所有组件间的数据流并在特定事件发生时触发信号。系统内的组件职责如下摘自官方文档并补充源码位置组件职责源码位置Engine控制各组件之间的数据流触发事件信号scrapy/core/engine.pyScheduler接收 Engine 发来的 Request入队后在 Engine 索取时再吐出scrapy/core/scheduler.pyDownloader抓取网页并把 Response 送回 Enginescrapy/core/downloader/init.pySpiders用户编写的自定义类解析响应、提取 Items 与新的 Requestsscrapy/spiders/Item Pipeline处理 Spider 提取出的 Items清洗、校验、持久化如入库scrapy/pipelines/Downloader Middlewares位于 Engine 与 Downloader 之间的钩子处理下行 Request 与上行 Responsescrapy/core/downloader/middleware.pySpider Middlewares位于 Engine 与 Spiders 之间的钩子处理 Spider 输入Response与输出Items、Requestsscrapy/core/spidermw.pyExtensions不参与数据流用于在特定事件发生时执行逻辑scrapy/extensions/图中红色箭头勾勒出的数据流即下面逐条展开的 9 步循环。数据流Data Flow官方文档将 Scrapy 的数据流归纳为以下 9 步由执行引擎驱动Engine 从 Spider 获取初始 Requests引擎打开 Spider 后消费其start()即 start_urls 等起始请求产出的 Request。Engine 将 Requests 交给 Scheduler 排队并向 Scheduler 索取下一个要爬的 Request。Scheduler 返回下一个 Request给 Engine。Engine 将 Request 发送给 Downloader途中依次经过Downloader Middlewares对应process_request钩子。页面下载完成后Downloader 生成携带该页面的 Response送回 Engine途中经过Downloader Middlewares对应process_response钩子。Engine 接收 Response 并转交 Spider 处理途中经过Spider Middlewares对应process_spider_input钩子。Spider 解析 Response产出被抓取的数据项Items与待跟进的新 Requests 返回 Engine途中经过Spider Middlewares对应process_spider_output钩子。Engine 将 Items 送交 Item Pipelines将新的 Requests 送回Scheduler并继续向 Scheduler 索取下一个 Request。上述过程从第 3 步开始循环重复直到 Scheduler 中不再有未处理的请求。源码印证Engine 如何驱动这个循环从 scrapy/core/engine.py 中的ExecutionEngine类可以看到上述流程的具体落点入队对应第 1、2 步ExecutionEngine.crawl()是所有新请求进入管线的入口它调用_schedule_request()——先发送request_scheduled信号再调用scheduler.enqueue_request(request)若 Scheduler 拒绝返回False例如被去重过滤器拦截则发送request_dropped信号。对应源码见 engine.py 的 crawl/_schedule_request。出队并下载对应第 3、4 步_start_scheduled_request()不断从self._slot.scheduler.next_request()取出请求取不到时发送scheduler_empty信号取到后调用_download()交给 Downloader并在结果处理中判断若 Downloader 中间件返回的是新的Request典型场景是重定向会再次crawl()注入管线。对应源码见 engine.py 的 _start_scheduled_request。背压控制needs_backoutneeds_backout()会在引擎停止运行、槽位正在关闭、Downloader 并发占满、Scraper 积压超限任一情况发生时返回TrueEngine 随即暂停向 Downloader 投递请求。这是保证高吞吐下内存稳定的关键机制见 engine.py 的 needs_backout。响应转交 Spider对应第 5、6 步_handle_downloader_output()在拿到 Response 后调用scraper.enqueue_scrape(result, request)由 Scraper 负责经过 Spider Middlewares 喂给 Spider。见 engine.py 的 _handle_downloader_output。空闲判定与结束对应第 8、9 步spider_is_idle()只有在Scraper 槽位空闲、Downloader 无在途请求、Scheduler 无待处理请求三者同时成立时才返回True此时触发spider_idle信号若没有DontCloseSpider处理器阻止则关闭 Spider本轮爬取结束。见 engine.py 的 spider_is_idle 与 _spider_idle。一个容易被忽略的细节_Slotengine.py L65-L98中维护着一个inprogress请求集合和heartbeat心跳调用间隔_SLOT_HEARTBEAT_INTERVAL 5.0秒。即使 Scheduler 报告仍有待处理请求却暂时取不出请求Engine 也会通过心跳定期重试防止爬虫卡死。组件深潜Scrapy Engine数据流的总调度者Engine 的职责在文档中的原话是控制系统内所有组件之间的数据流并在特定动作发生时触发事件。在实现上ExecutionEngine在__init__中装配三大协作对象engine.py L104-L154Downloader由设置项DOWNLOADER默认scrapy.core.downloader.Downloader见 default_settings.py L315加载Scheduler由设置项SCHEDULER加载且必须通过BaseScheduler接口检查否则抛出TypeErrorScraper负责响应 → Spider这一段内部封装了 Spider Middlewares 与 Item Pipelines。值得注意的是当前仓库版本的 Engine 以async def协程为主体如start_async()、stop_async()、open_spider_async()旧的 Deferred 风格方法start()、stop()已标记为弃用仅做兼容转发——这说明 Scrapy 的事件驱动底座正在从纯 Twisted 向 Twisted asyncio 双轨演进详见后文事件驱动网络一节。Scheduler请求的队列与顺序scrapy/core/scheduler.py 定义了最小调度器接口BaseSchedulerL52-L124任何自定义 Scheduler 必须实现三个抽象方法方法语义has_pending_requests()是否还有已入队的请求enqueue_request(request)接收 Engine 的请求返回False时 Engine 会发出request_dropped信号且不再重试默认实现中被去重过滤器拒绝的请求即返回Falsenext_request()返回下一个待处理请求返回None表示当前 reactor 周期内无可发送请求Engine 会继续调用直到has_pending_requests()为False元类BaseSchedulerMeta还通过__subclasscheck__在运行时检查这三个方法是否存在且可调用——这也解释了 Engine 初始化时对 Scheduler 的接口校验逻辑。默认实现Schedulerscheduler.py L127-L498将请求存入按Request.priority排序的优先级队列SCHEDULER_PRIORITY_QUEUE。几个直接影响爬取行为的实现事实去重enqueue_request()先调用DUPEFILTER_CLASS默认scrapy.dupefilters.RFPDupeFilter的request_seen()已被过滤且未设置dont_filter的请求直接返回False内存/磁盘双队列默认全部请求走内存队列启用JOBDIR断点续爬时同时创建磁盘队列且不可序列化的请求自动回落到内存队列见enqueue_request与_dqpush的ValueError分支scheduler.py L364-L436同一优先级下内存队列优先于磁盘队列抓取顺序默认内存队列是 LIFO 栈因此爬取呈深度优先DFO顺序文档明确给出改为 BFO广度优先的三个设置DEPTH_PRIORITY 1、SCHEDULER_DISK_QUEUE scrapy.squeues.PickleFifoDiskQueue、SCHEDULER_MEMORY_QUEUE scrapy.squeues.FifoMemoryQueue见 scheduler.py 文档字符串 L181-L194统计scheduler/enqueued、scheduler/enqueued/disk、scheduler/enqueued/memory、scheduler/dequeued等计数在入队/出队时逐次累加可用于观察调度行为JOBDIR 文件布局requests.queue/目录保存未下载请求active.json保存优先级队列状态并在作业停止时写出、恢复时读入_write_dqs_state/_read_dqs_state。源码文档字符串特别提示这些文件属于实现细节不要依赖其结构。Downloader并发槽位与下载处理器Downloaderscrapy/core/downloader/init.py L83 起初始化时读取的关键设置CONCURRENT_REQUESTS总并发、CONCURRENT_REQUESTS_PER_DOMAIN按域名并发、CONCURRENT_REQUESTS_PER_IP按 IP 并发DOWNLOAD_DELAY与RANDOMIZE_DOWNLOAD_DELAY每个域名槽位Slot数据类L44-L80按delay控制请求节奏开启随机化后实际延迟在0.5×delay ~ 1.5×delay之间均匀取随机值DOWNLOAD_SLOTS可为特定槽名如download_slot、domain单独配置并发与延迟实际的请求发送委托给DownloadHandlersscrapy/core/downloader/handlers/按 URL scheme 分发到 http11、http2、httpx、ftp、file、data 等具体处理器。Downloader.fetch()的调用链是请求先注册进active集合再交给DownloaderMiddlewareManager.download_async()走完process_request→ 下载 →process_response的完整中间件链最后按槽位节流发出。Engine 正是通过downloader.needs_backout()由这些槽位容量决定与downloader.active在途请求集合实现背压与空闲判断。Spiders 与 ScraperSpider 是用户代码解析响应、产出 Items 与新 Requests。Scrapy 侧真正承载喂给 Spider这一职责的是Scraperscrapy/core/scraper.py它在 L106-L127 中装配了SpiderMiddlewareManagerSPIDER_MIDDLEWARES 中间件链ItemPipelineManager由ITEM_PROCESSOR设置加载默认即 Item Pipelines 管理器CONCURRENT_ITEMS并发处理 Item 的并发度。Scraper.Slotscraper.py L62-L103为每个运行中的 Spider 维护一个响应队列queue、活跃请求集合active与活跃数据量active_size当active_size超过max_active_size默认 5,000,000 字节时needs_backout()返回TrueEngine 暂停投递新响应防止大页面拖垮内存。is_idle()则在队列、活跃请求、Item 处理三者均为空时返回True——这正是 Engine 判定爬虫空闲的输入之一。Item PipelineItem Pipeline 在 Items 被提取后对其进行处理典型任务是清洗、校验与持久化如写入数据库。框架内建实现位于 scrapy/pipelines/如FilesPipeline、ImagesPipeline、MediaPipeline用户管线通过设置项ITEM_PIPELINES注册由Scraper中的ItemPipelineManager统一驱动。Downloader Middlewares位于 Engine 与 Downloader 之间文档说明Downloader 中间件是位于 Engine 与 Downloader 之间的特定钩子处理从 Engine 流向 Downloader 的请求以及从 Downloader 流回 Engine 的响应。管理器DownloaderMiddlewareManagerscrapy/core/downloader/middleware.py把三类方法挂成两条方向相反的链process_request按优先级正序执行append任一中间件返回 Response/Request 则短路直接跳回响应链process_response与process_exception按优先级逆序执行appendleft形成洋葱结构middleware.py L43-L52, L96-L156。仓库内置的默认中间件及优先级见 default_settings.py 的 DOWNLOADER_MIDDLEWARES_BASEDOWNLOADER_MIDDLEWARES_BASE { # Engine side scrapy.downloadermiddlewares.offsite.OffsiteMiddleware: 50, scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware: 100, scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware: 300, scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware: 350, scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware: 400, scrapy.downloadermiddlewares.useragent.UserAgentMiddleware: 500, scrapy.downloadermiddlewares.retry.RetryMiddleware: 550, scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware: 580, scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware: 590, scrapy.downloadermiddlewares.redirect.RedirectMiddleware: 600, scrapy.downloadermiddlewares.cookies.CookiesMiddleware: 700, scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware: 750, scrapy.downloadermiddlewares.stats.DownloaderStats: 850, scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware: 900, # Downloader side }注释中的 Engine side / Downloader side 直观标出了中间件链两端用户中间件通过DOWNLOADER_MIDDLEWARES设置与上述默认项合并优先级数值越小越靠近 Engine。每个中间件类的具体实现位于 scrapy/downloadermiddlewares/。Spider Middlewares位于 Engine 与 Spiders 之间Spider 中间件处理 Spider 的输入Response与输出Items 和 Requests。管理器SpiderMiddlewareManagerscrapy/core/spidermw.py维护四条方法链process_start、process_spider_input、process_spider_output、process_spider_exception。其中异常路径值得一提若 Spider 回调抛出异常_process_spider_exception()会依次询问各中间件一旦某个中间件返回可迭代对象控制权立即交还process_spider_output链继续处理spidermw.py L116-L150这让中间件有机会吞掉错误请求并产出替代结果。默认 Spider 中间件见 default_settings.py 的 SPIDER_MIDDLEWARES_BASESPIDER_MIDDLEWARES_BASE { # Engine side scrapy.spidermiddlewares.start.StartSpiderMiddleware: 25, scrapy.spidermiddlewares.httperror.HttpErrorMiddleware: 50, scrapy.spidermiddlewares.referer.RefererMiddleware: 700, scrapy.spidermiddlewares.urllength.UrlLengthMiddleware: 800, scrapy.spidermiddlewares.depth.DepthMiddleware: 900, scrapy.spidermiddlewares.metacopy.MetaCopyDetectionMiddleware: 1000, # Spider side }实现代码位于 scrapy/spidermiddlewares/。ExtensionsExtensions 与上面组件不同它们在数据流中没有固定角色而是监听爬虫生命周期事件Spider 打开/关闭、错误计数、内存占用等并执行逻辑。默认启用的扩展见 default_settings.py 的 EXTENSIONS_BASE包括CoreStats、LogCount、TelnetConsole、MemoryUsage、CloseSpider、FeedExporter、LogStats、SpiderState、AutoThrottle、RemoteControl等实现位于 scrapy/extensions/。事件驱动的网络模型官方文档明确指出Scrapy 构建于Twisted——Python 流行的事件驱动网络框架——之上因此采用非阻塞异步代码实现并发。这意味着整个引擎内没有线程等待网络 I/OEngine、Downloader、Scheduler 的协作全部由事件循环reactor调度一个进程即可支撑大量并发连接。从当前仓库的源码结构可以进一步看到底座的演进轨迹ExecutionEngine的核心方法已全部提供async版本如open_spider_async()、close_spider_async()见 engine.py L547 起Deferred 风格旧 API 被标记为ScrapyDeprecationWarning同时scrapy/utils/asyncio.py提供了create_looping_call、maybe_deferred_to_future等桥接工具让循环调用、心跳等机制在 Twisted reactor 与 asyncio 事件循环下都能运行。换言之文档所描述的事件驱动 非阻塞结论在当前版本中依然成立只是事件循环的实现细节兼容了 asyncio 生态。学习时建议掌握 Deferred/协程的基本心智模型后再阅读 scrapy/utils/defer.py 中的桥接实现。小结把 9 步数据流映射到代码数据流步骤对应源码入口1. 获取初始请求scrapy/core/spidermw.py 的 process_start → engine.py 的 _start_request_processing2/3. 入队与出队engine.py 的 crawl/_schedule_request 与 _start_scheduled_request、scheduler.py 的 enqueue_request/next_request4/5. 经 Downloader 中间件下载middleware.py 的 download_async downloader/init.py 的 fetch6/7. 经 Spider 中间件解析spidermw.py 的 scrape_response_async、scraper.py8. Items 入管线、Requests 回调度scraper.py 中 ItemPipelineManager 的驱动 与 engine 的crawl()循环9. 循环直至 Scheduler 清空engine.py 的 spider_is_idle/_spider_idle 与scheduler_empty信号理解了这套Engine 居中调度、队列控速、中间件分层拦截、信号驱动事件的骨架后你在配置CONCURRENT_REQUESTS、选择抓取顺序DFO/BFO、编写自定义 Middleware 或 Scheduler 时都能准确判断改动会影响数据流中的哪一段。【免费下载链接】scrapyScrapy, a fast high-level web crawling scraping framework for Python.项目地址: https://gitcode.com/GitHub_Trending/sc/scrapy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考