☰
Scrapy+Scrapyd+Gerapy爬虫调度框架搭建实战指南
2026/10/3 11:24:55 网站建设 项目流程

“你们的线上爬虫任务是怎么调度的?”

2024年有次去大厂面试,对方问了个特别基础又特别致命的问题。我当时愣了几秒,因为说实话,我们团队那会儿还靠着crontab加shell脚本过日子。本地写好爬虫,打包上传到服务器,进程挂掉了手动重启,想看看某条爬虫今天跑了多少条数据,得先SSH登录服务器,再翻日志文件,效率极低。

那次面试之后,我认真审视了爬虫调度这个环节,最终把整套流程切换成了Scrapy + Scrapyd + Gerapy这套组合,实测下来确实是目前开源生态里最稳、最易上手、资料也最全的调度框架方案。这篇文章不聊面试了,就从一个实战者的角度,把从零搭建这套框架的完整过程、核心细节、参数选择和踩坑记录全部写出来。

不管你是刚接触爬虫方向的初级工程师,还是正在改进团队爬虫基建的技术负责人,只要你的业务里“爬虫数量超过三个、需要团队协作、有固定频次更新需求”,这套方案都值得花半天时间完整过一遍。

1. 整体设计:三个工具各自负责什么,为什么这么搭

第一次听说这套组合的人,最容易被三个名字搞晕:Scrapy、Scrapyd、Gerapy,它们到底有什么区别?简单说,这三者不是并列关系,而是垂直的分工关系。

Scrapy是爬虫引擎本身,负责发起请求、解析响应、清洗数据、管道输出,这是整套系统的心脏。Scrapyd是Scrapy官方维护的“部署与运行守护服务”,装到服务器上之后,你可以通过HTTP接口把写好的爬虫项目打包上传、远程启动、远程停止、查看运行状态。Gerapy则是跑在Scrapyd之上的可视化辅助平台,用Web界面来管理主机、部署项目、配置定时任务、查看爬虫日志,相当于给Scrapyd套了一层图形化外壳。

一句话总结链路:Gerapy通过界面下发指令给Scrapyd,Scrapyd负责启动和守护Scrapy项目里的爬虫进程。

为什么选这套组合而不是别的方式?我在选型时认真对比过几个常见方案:

  • 纯crontab + shell脚本:零成本,但没有任何可见性和故障恢复能力,进程挂了就是挂了,只能等报警。
  • Airflow / DolphinScheduler等大数据调度平台:调度能力很强,但引入成本和运维成本高,对爬虫这种轻量级任务来说属于大炮打蚊子。
  • Scrapyd单独用:纯命令行和HTTP调用,能用,但对团队里非技术或半技术的同学不友好,查看状态、部署项目都不够直观。
  • Scrapyd + Gerapy组合:部署复杂度尚可接受,既有Scrapyd的官方稳定性和生态支持,又有可视化管理带来的团队协作效率提升。

另外,这套框架天然支持多台服务器扩展——Gerapy里可以注册多个主机,每台主机对应一个Scrapyd服务,爬虫任务可以分散下发到不同的机器上去跑,这就为后续的分布式爬虫扩展留下了口子。

架构上可以这样理解整个链路:

开发机(写Scrapy代码) | | 打包上传 v Gerapy(Web管理端 :8000) | | HTTP指令 v Scrapyd(部署运行服务 :6800) | | 启动进程 v Scrapy爬虫实例(目标网站抓取)

这套链路的好处是:开发、部署、调度、监控四个环节完全解耦,任何一环出问题都可以单独定位处理,不需要为了修一个调度任务去翻爬虫代码。

2. Scrapy篇:先把爬虫本身做扎实

Gerapy和Scrapyd只是“调度外壳”,真正干活的是Scrapy项目本身。我见过不少团队,架子搭得很漂亮,但爬虫代码一塌糊涂,调度平台再完善也救不回来。所以先把Scrapy这一层做扎实。

2.1 项目骨架和核心配置

如果你的项目还没有Scrapy环境,先安装框架并创建项目:

pip install scrapy scrapy startproject myproject cd myproject scrapy genspider example_spider example.com

整个项目的骨架会是这样的:

myproject/ ├── scrapy.cfg ├── myproject/ │ ├── __init__.py │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders/ │ └── example_spider.py

这套默认骨架里,items.py定义数据字段结构,pipelines.py负责数据清洗和入库,middlewares.py处理请求与响应的拦截逻辑,spiders/目录存放实际爬虫文件。

真正上线前,settings.py里这几个参数请务必盯住:

# 是否遵循robots协议,开发时建议False ROBOTSTXT_OBEY = False # 并发请求数,默认16,实际根据目标网站承压能力调整 CONCURRENT_REQUESTS = 8 # 同一域名下的延迟,单位秒 DOWNLOAD_DELAY = 1.5 # 禁止cookies,对多数场景可以提高效率 COOKIES_ENABLED = False # 默认请求头,部分网站会校验User-Agent DEFAULT_REQUEST_HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', } # 开启AutoThrottle限速插件(重要) AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 5.0 AUTOTHROTTLE_MAX_DELAY = 60.0

这里面的逻辑需要说清楚:DOWNLOAD_DELAY是硬延迟,每个请求之间固定间隔这么久;AUTOTHROTTLE是Scrapy内置的动态限速机制,它会根据服务器响应时间和并发状态自动调整延迟。两个同时开启时,AutoThrottle会接管延迟控制,刚开始可能延迟较高,几次请求后会逐步降低到合理水平。

我个人的习惯是并发请求数设置到8~16之间,延迟从1~2秒起步,然后观察目标网站的反应,再动态调整。如果你的目标网站没有严格的反爬措施,CONCURRENT_REQUESTS = 16和DOWNLOAD_DELAY = 0.5的组合就能达到不错的抓取速度。

2.2 中间件和管道的关键用法

爬虫写多了你会发现,真正体现工程能力的不是Spider里的解析代码,而是中间件和管道这两层。

中间件(Middleware)是请求和响应的“交通警察”。举个例子,我经常在process_request里做代理IP轮换和请求头随机化:

# middlewares.py import random class RandomUserAgentMiddleware: def __init__(self): self.user_agents = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15', 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15', ] @classmethod def from_crawler(cls, crawler): return cls() def process_request(self, request, spider): request.headers['User-Agent'] = random.choice(self.user_agents) return None

然后在settings.py里启用这个中间件:

DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.RandomUserAgentMiddleware': 543, }

注意中间件的数字编号,数字越小越靠近下载器,数字越大越靠近引擎。一般情况下500~600这个区间的中间件用来处理请求头、代理、重试逻辑是比较合理的。

管道(Pipeline)是数据出口。很多人把所有逻辑都塞进Spider,等数据量大起来就出了问题。正确的做法是:Spider只负责解析并产出Item,管道负责去重、清洗、入库。举个例子,一个典型的价格监控爬虫,管道里会做字段校验和入库:

# pipelines.py import pymysql class PricePipeline: def open_spider(self, spider): self.conn = pymysql.connect(host='xxx', port=3306, user='root', password='xxx', database='price_monitor') def process_item(self, item, spider): # 字段校验 if not item.get('price'): raise DropItem(f"Missing price in {item}") # 入库操作 cursor = self.conn.cursor() sql = "INSERT INTO products (title, price, url) VALUES (%s, %s, %s)" cursor.execute(sql, (item['title'], item['price'], item['url'])) self.conn.commit() cursor.close() return item def close_spider(self, spider): self.conn.close()

管道的优先级也是在settings.py里配置,数字越小的管道越先执行。这里有个重要经验:去重、校验类管道放在前面,入库类管道放在后面。因为先抛掉脏数据,后面入库的压力就小很多。

2.3 对接Scrapyd前必做的三件事

第一,确认scrapy.cfg文件是完整的。注意Scrapyd部署时读的是项目根目录的scrapy.cfg,不是项目内层的那个,很多新手把文件改错了导致部署失败。

第二,settings.py里不要写死本机路径。如果有读取配置文件的需求,使用os.path.dirname(__file__)这种相对路径方式,否则换到服务器上后你会被“文件找不到”折磨很久。

第三,把所有需要动态变化的参数(数据库连接串、Redis地址、代理池地址)都抽到环境变量或者单独配置文件中。不然每换一次环境,就得改一次代码再重新部署。

3. Scrapyd篇:给爬虫一个能远程操控的运行环境

Scrapy项目准备好了,接下来就是部署环节。Scrapyd可以理解成一个“爬虫运行容器”,装上它,你的服务器就变成了一个可以随时接收爬虫代码、启动爬虫任务的运行环境。

3.1 安装与基本配置

安装本身很简单:

pip install scrapyd scrapyd-client

安装完成后,默认配置下直接在命令行输入scrapyd就能启动服务,监听在127.0.0.1:6800。

如果不做任何配置就完事,后面等着踩坑。建议创建配置文件/etc/scrapyd/scrapyd.conf:

[scrapyd] eggs_dir = /var/lib/scrapyd/eggs logs_dir = /var/lib/scrapyd/logs items_dir = /var/log/scrapyd/items jobs_to_keep = 5 daemonize = yes max_proc = 4 max_proc_per_cpu = 4 [http] port = 6800 bind_address = 0.0.0.0

这里的几个关键参数我逐个说一下:

  • daemonize = yes:让Scrapyd后台运行,不会因为SSH会话关闭而被杀掉。
  • max_proc:最多同时运行几个爬虫进程,建议根据CPU核数设置。设置过小,任务队列会积压;设置过大,服务器负载会飙高。
  • jobs_to_keep:保留每个爬虫最近几次的运行记录,不要设置太大,否则日志文件会占满磁盘。
  • bind_address = 0.0.0.0:关键!默认只监听本机,如果不改成0.0.0.0,Gerapy或者其他机器上的部署工具都没法远程连接。

这里还有个很容易踩的坑:Scrapyd的Python环境和爬虫项目的依赖必须是同一套环境。很多人的服务器上同时有Python 3.8和Python 3.11,如果scrapyd装在一个环境,项目依赖装在另一个环境,部署后任务启动会直接报ModuleNotFoundError。

3.2 部署并调用API下发任务

Scrapyd官方提供了命令行部署工具scrapyd-deploy,配置在项目根目录的scrapy.cfg中:

[deploy:production] url = http://服务器IP:6800/ project = myproject

然后执行:

cd myproject scrapyd-deploy production -p myproject

执行成功后会返回类似这样的信息:

Deploying Scrapy project to http://服务器IP:6800/ ... Server response (200): {"status": "ok", "project": "myproject", "version": "1712880000", "spiders": 1}

spiders: 1表示已识别的爬虫数量,这个数字对不上就要检查爬虫文件里的name是否与其他冲突。

部署完成后,就可以通过HTTP API来管理任务了。常用的API我列在下面:

API用途示例
/schedule.json启动爬虫任务POSTproject=myproject&spider=example_spider
/cancel.json取消任务POSTproject=myproject&job=JobId
/listprojects.json列出所有项目GET
/listspiders.json列出项目下所有爬虫GETproject=myproject
/listjobs.json查看运行中/等待/完成的任务GETproject=myproject

实际调度一个爬虫的开销非常低,一条curl命令就能完成:

curl http://服务器IP:6800/schedule.json -d project=myproject -d spider=example_spider

返回结果里的jobid字段要保存好,后续取消任务、查看日志都靠它。

3.3 我踩过的Scrapyd坑

第一,日志不输出或者输出不全。Scrapyd默认会按logs/项目名/爬虫名/JobId.log的路径保存日志,如果看不到日志,检查logs_dir目录是否存在,以及运行Scrapyd的用户是否有写权限。我把logs_dir指定到一个专用目录,并保证当前用户对它有写权限后就正常了。

第二,部署后修改不生效。这个坑极其隐蔽——Scrapyd部署时会为项目生成一个带版本号的egg包,如果新旧版本号相同,Scrapyd会认为没有变化而拒绝更新。所以部署脚本里要用时间戳动态生成版本号:

scrapyd-deploy production -p myproject --version $(date +%Y%m%d%H%M%S)

第三,spider not found报错。通常是因为部署egg包时project名字和实际Scrapy项目名不一致,或者多个Scrapy项目重名导致覆盖。解决办法是保持scrapy.cfg里的project名与爬虫项目名完全一致,部署时也用同一个名字。

第四,任务启动后立即退出。排查方法很简单,先看日志文件尾部,再手动在服务器上进入项目目录执行scrapy crawl example_spider,能跑起来就说明是环境问题,多半是依赖缺失或者路径不对。

4. Gerapy篇:把调度变成可视化操作

Scrapyd解决的是“能不能远程部署和启动”的问题,但它依旧不够直观。GitHub上每次改代码都要命令行部署,每次看日志都要SSH到服务器,团队里任何一人想操作爬虫都得先背几条命令——这就是Gerapy的用武之地。

4.1 安装初始化与主机管理

Gerapy是由国内开发者开发的开源项目,使用Django框架构建,安装和初始化流程非常简单:

pip install gerapy gerapy init cd gerapy gerapy migrate gerapy createsuperuser gerapy runserver 0.0.0.0:8000

几条命令下来,Gerapy管理端就运行在服务器的8000端口了。第一次打开Web页面,用你创建的超级用户登录,第一步是要把Scrapyd主机添加进去——在“主机管理”页面填入服务器IP、Scrapyd端口6800,点击确认。

填写主机地址时有一个容易忽略的细节:Gerapy检测主机状态用的是HTTP请求,如果服务器有防火墙或者安全组策略,一定要放行6800端口的入站请求。我在阿里云服务器上就吃过这个亏,Gerapy一直显示主机“未连接”。

4.2 部署项目到主机

主机配置好后,要把本地Scrapy项目交给Gerapy管理。在Gerapy项目目录下执行:

gerapy parse --file /path/to/myproject

这个命令会解析Scrapy项目并生成Gerapy需要的项目配置文件。然后刷新Gerapy界面,“项目管理”里就能看到该项目。选中项目,点击部署,选择目标主机和版本号,确认后Gerapy就会把项目打包成egg包上传到Scrapyd服务器。

这里有个重要的操作顺序:先解析项目,后部署项目。我同事第一次用的时候直接跳过了gerapy parse这步,结果界面上项目列表是空的,花了不少时间才找到原因。

部署完成后,“任务管理”页面会自动列出这个项目下所有爬虫(对应Scrapy里的spiders)。点击运行,在弹窗里可以设置爬虫参数,比如代表抓取分页数的max_pages这类自定义参数,确认后任务就会下发到Scrapyd并开始执行。

4.3 定时任务与运行监控

Gerapy最实用的功能之一就是定时调度——它内置了Cron表达式解析,不用再去服务器上配置系统级crontab。

在Gerapy的“定时任务”页面,新建一条定时任务,选择项目、爬虫,填写Cron表达式,比如每天凌晨2点抓取一次就填:

0 2 * * *

Gerapy会在到点后自动调用Scrapyd的调度API启动爬虫。相比系统crontab,这个方案的好处是:配置界面化、任务变动可审计、依托Scrapyd的进程守护能力——爬虫异常退出后可以配置自动重启,而不像裸crontab那样进程死了就彻底没了。

运行时监控方面,Gerapy的“任务管理-运行中”页面可以看到当前所有正在运行的爬虫任务,点击每个任务的JobId,能查看实时日志。日志是滚动加载的,方便定位报错。

如果你的日志量特别大,建议定期清理Gerapy数据库中的历史任务记录,否则时间久了查询会变慢。

4.4 Gerapy的短板和应对

Gerapy默认功能在某些场景下确实不够用,这里说两个我在实际使用中遇到的短板和应对方案:

第一,Gerapy没有内置数据监控看板,比如抓取量曲线、成功率趋势这些指标,它是没有的。我的做法是让Pipeline统计数据并写入数据库,再用Grafana做可视化报表,这样可以在一个统一的看板上看到各爬虫的数据抓取情况。

第二,Gerapy的报警功能偏弱,只在任务运行失败时会有简单提示。我的做法是写一个定时巡检脚本,定期检查Scrapyd返回的任务状态,如果连续几次检查发现某爬虫长时间无数据产出,就用企业微信机器人发一条告警消息。

5. 动态页面与iframe场景:scrapy + playwright的破局思路

单纯使用Scrapy,碰到JavaScript动态渲染的页面往往会抓不到数据;如果页面里还有嵌套的iframe,传统的requests方案基本无解。2024年全网都在聊scrapy-playwright,这套集成方案确实值得单独拿出来讲。

5.1 什么情况需要上Playwright

先判断一下你的目标页面属于哪种类型:

  • 页面源码里直接包含目标数据 → 普通Scrapy请求就能搞定。
  • 数据通过异步接口加载,返回JSON后被前端JS渲染 → 直接找XHR接口,没必要渲染页面。
  • 数据是JS动态生成的(比如点击“加载更多”后才渲染DOM),且找不到合适的接口 → 这时候才需要Playwright。
  • 页面内容在嵌套iframe里,且iframe的src是JS生成的 → 这是Playwright的强项。

我遇到过最典型的场景:目标页面是一个数据可视化大屏,核心数据嵌在一个动态创建的iframe中。普通Scrapy请求拿到主页面源码后,iframe的src字段为空,是页面加载完成后由JS拼接生成的,这时候没有任何静态页面可以请求——必须渲染。

5.2 集成步骤与核心代码

scrapy-playwright的使用步骤,先说安装:

pip install scrapy-playwright playwright install chromium

第一条命令安装集成组件,第二条命令下载Chromium内核。注意服务端部署时也需要执行这条命令,很多人在服务器上看不到页面不是在调试代码问题,而是压根忘了装浏览器。

接下来在settings.py里追加配置:

DOWNLOAD_HANDLERS = { "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler", "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler", } TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

在Spider里,只需要在需要动态渲染的Request上增加meta={"playwright": True}:

import scrapy class DataScreenSpider(scrapy.Spider): name = "data_screen" def start_requests(self): yield scrapy.Request( url="https://example.com/screen", meta={"playwright": True} ) async def parse(self, response): # 使用response的playwright上下文处理iframe page = response.meta["playwright_page"] # 等待iframe加载完成 await page.wait_for_selector("iframe") # 定位iframe中的元素 frame = page.frame_locator("#data-frame") value = await frame.locator(".value-text").inner_text() yield { "value": value }

这里的关键在page.frame_locator方法——它专门用来处理iframe内部元素的选择。如果是传统的requests方案,你根本拿不到iframe内部的内容;而用Playwright渲染后,iframe就相当于页面中的一个可交互元素,定位和取值非常方便。

如果遇到跨域iframe(iframe的src指向另一个站点),Playwright同样可以处理,因为Chromium会同时加载主页面和iframe内部页面。

5.3 资源开销与并发控制

不得不提醒一句:Playwright渲染的代价很高。

一次普通的Scrapy请求只需要十几毫秒的响应时间;一次Playwright渲染需要加载完整浏览器内核、执行全部JS、渲染整个页面,耗时通常几秒钟,内存占用最高可达300-500MB。所以在引入Playwright后,务必降低并发数:

# 专门给Playwright爬虫用较低并发 CONCURRENT_REQUESTS = 2 DOWNLOAD_DELAY = 3.0

如果项目里同时有静态爬虫和动态爬虫,我建议把它们拆成两个项目或者两个爬虫,用不同的Settings配置。否则静态爬虫被动态爬虫拖累,性能会大打折扣。

另外要多说一句:不要把所有页面都交给Playwright。我的做法是先对目标主页发一个普通的Scrapy请求,如果发现页面是静态的,就走常规解析流程;发现需要渲染,再在同一个Spider里动态切换策略。这样可以大量节省服务器资源。

6. 常见问题排查与相关思考

6.1 一套能直接照抄的排障速查表

整套框架跑起来之后,日常维护中最容易碰到的问题基本固定。我把这些问题整理成了一张速查表,方便各位直接对照处理:

问题现象可能原因排查顺序
Gerapy主机显示“未连接”防火墙、Scrapyd未启动、端口绑定错误先检查Scrapyd进程,再测端口连通性
部署项目成功但无爬虫列表Gerapy未执行parse命令检查“项目管理”里是否有已完成解析的项目
任务启动后立刻失败依赖缺失、Python环境不一致查看日志文件,手动执行scrapy crawl命令
部署后更改不生效版本号相同被Scrapyd忽略用时间戳作为版本号重新部署
爬虫运行但数据量异常少被目标网站限流、解析规则失效看日志中响应状态码,检查response内容
Playwright页面加载超时网络问题、浏览器未安装、并发过高检查playwright install是否执行,降低CONCURRENT_REQUESTS

除了这张表,还有一个非常实用的排查技巧:所有的调度问题,第一步永远是看日志。Scrapyd保存了每个Job的完整日志,Gerapy日志页也能直接看到具体报错,绝大多数问题(模块缺失、解析异常、网络错误)在日志里都有明确提示。

6.2 从面试和团队基建角度重新看这套技术栈

文章开头提到面试场景,其实面试官问爬虫调度框架,核心想听的不是说“我会用scrapyd”,而是你有没有认真想过这几个问题:

为什么要Scrapyd而不是直接用supervisor守护进程?因为Scrapyd提供的是对外API和统一管理能力,可以接入其他系统做联动,比如Gerapy界面、定时任务、CI/CD。

Gerapy解决了什么问题?Scrapyd有API但无界面,人肉敲命令容易出错、审计缺失,Gerapy把这些流程封装成可视化操作。

这套组合的局限在哪?Scrapyd是单机多进程模型,单台服务器的资源上限就是它的上限,真的到了每天千万级请求量的规模,需要再往上加分布式——而Scrapy生态里解决分布式的标准方案是scrapy-redis,它把Request队列从内存迁移到Redis中,让多台机器可以消费同一个任务队列。

也就是说,Scrapy + Scrapyd + Gerapy更像是“单体应用架构”阶段的爬虫基建;再往上演进,方向是任务队列外部化、调度中心与执行节点分离、数据上报链路独立。理解这条演进路径,比单纯会用工具重要得多。

结尾想说的几句大实话

从crontab手动时代切换到这个三件套体系,最直观的感受是:一个成熟的技术栈,省下的不是写代码的时间,而是排查问题的精力。之前线上爬虫挂了,我们得先查机器、再看进程、再翻日志,运气好十分钟找到原因,运气不好耗一下午;现在所有日志集中在Gerapy里,点开页面就能看到报错的上下文,定位问题的时间从小时级降到了分钟级。

最后再分享一个我个人的小习惯:无论是Scrapyd还是Gerapy,都建议在正式上线前把整个流程在本地完整走一遍——本地装一套Scrapyd,再用Gerapy部署一个测试爬虫,跑通之后再迁移到服务器。这套流程在第一次配置时多花二十分钟,但能帮你规避掉绝大多数“部署到服务器才发现的低级问题”。

如果你们团队也正在为爬虫管理发愁,或者你正在准备面试中跟爬虫基建相关的问题,希望这篇文章能给你一个完整的参考路径。从头到尾搭一遍,你会对这套框架的每个环节都有更深的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询