ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Scrapy+Scrapyd+Gerapy爬虫调度框架搭建实战指南

Scrapy+Scrapyd+Gerapy爬虫调度框架搭建实战指南 “你们的线上爬虫任务是怎么调度的”2024年有次去大厂面试对方问了个特别基础又特别致命的问题。我当时愣了几秒因为说实话我们团队那会儿还靠着crontab加shell脚本过日子。本地写好爬虫打包上传到服务器进程挂掉了手动重启想看看某条爬虫今天跑了多少条数据得先SSH登录服务器再翻日志文件效率极低。那次面试之后我认真审视了爬虫调度这个环节最终把整套流程切换成了Scrapy Scrapyd Gerapy这套组合实测下来确实是目前开源生态里最稳、最易上手、资料也最全的调度框架方案。这篇文章不聊面试了就从一个实战者的角度把从零搭建这套框架的完整过程、核心细节、参数选择和踩坑记录全部写出来。不管你是刚接触爬虫方向的初级工程师还是正在改进团队爬虫基建的技术负责人只要你的业务里“爬虫数量超过三个、需要团队协作、有固定频次更新需求”这套方案都值得花半天时间完整过一遍。1. 整体设计三个工具各自负责什么为什么这么搭第一次听说这套组合的人最容易被三个名字搞晕Scrapy、Scrapyd、Gerapy它们到底有什么区别简单说这三者不是并列关系而是垂直的分工关系。Scrapy是爬虫引擎本身负责发起请求、解析响应、清洗数据、管道输出这是整套系统的心脏。Scrapyd是Scrapy官方维护的“部署与运行守护服务”装到服务器上之后你可以通过HTTP接口把写好的爬虫项目打包上传、远程启动、远程停止、查看运行状态。Gerapy则是跑在Scrapyd之上的可视化辅助平台用Web界面来管理主机、部署项目、配置定时任务、查看爬虫日志相当于给Scrapyd套了一层图形化外壳。一句话总结链路Gerapy通过界面下发指令给ScrapydScrapyd负责启动和守护Scrapy项目里的爬虫进程。为什么选这套组合而不是别的方式我在选型时认真对比过几个常见方案纯crontab shell脚本零成本但没有任何可见性和故障恢复能力进程挂了就是挂了只能等报警。Airflow / DolphinScheduler等大数据调度平台调度能力很强但引入成本和运维成本高对爬虫这种轻量级任务来说属于大炮打蚊子。Scrapyd单独用纯命令行和HTTP调用能用但对团队里非技术或半技术的同学不友好查看状态、部署项目都不够直观。Scrapyd Gerapy组合部署复杂度尚可接受既有Scrapyd的官方稳定性和生态支持又有可视化管理带来的团队协作效率提升。另外这套框架天然支持多台服务器扩展——Gerapy里可以注册多个主机每台主机对应一个Scrapyd服务爬虫任务可以分散下发到不同的机器上去跑这就为后续的分布式爬虫扩展留下了口子。架构上可以这样理解整个链路开发机写Scrapy代码 | | 打包上传 v GerapyWeb管理端 :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/xhtmlxml,application/xml;q0.9,*/*;q0.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(hostxxx, port3306, userroot, passwordxxx, databaseprice_monitor) def process_item(self, item, spider): # 字段校验 if not item.get(price): raise DropItem(fMissing 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.0Gerapy或者其他机器上的部署工具都没法远程连接。这里还有个很容易踩的坑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启动爬虫任务POSTprojectmyprojectspiderexample_spider/cancel.json取消任务POSTprojectmyprojectjobJobId/listprojects.json列出所有项目GET/listspiders.json列出项目下所有爬虫GETprojectmyproject/listjobs.json查看运行中/等待/完成的任务GETprojectmyproject实际调度一个爬虫的开销非常低一条curl命令就能完成curl http://服务器IP:6800/schedule.json -d projectmyproject -d spiderexample_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( urlhttps://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就相当于页面中的一个可交互元素定位和取值非常方便。如果遇到跨域iframeiframe的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部署一个测试爬虫跑通之后再迁移到服务器。这套流程在第一次配置时多花二十分钟但能帮你规避掉绝大多数“部署到服务器才发现的低级问题”。如果你们团队也正在为爬虫管理发愁或者你正在准备面试中跟爬虫基建相关的问题希望这篇文章能给你一个完整的参考路径。从头到尾搭一遍你会对这套框架的每个环节都有更深的理解。
返回列表