ARTICLE DETAIL

资讯详情

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

Scrapy+Scrapyd+Gerapy:爬虫工程化部署与管理实战

Scrapy+Scrapyd+Gerapy:爬虫工程化部署与管理实战 1. 为什么非要把这三兄弟凑到一起先说清楚一件事Scrapy 是爬虫框架Scrapyd 是部署工具Gerapy 是管理平台这三者各有分工但组合起来才是一条完整的爬虫工程化链路。我见过太多人只用了 Scrapy 就跑天下爬虫脚本一多问题就开始暴露了。本地跑一个爬虫开个终端敲scrapy crawl spider_name单机单任务没问题可一旦到了二三十个爬虫、五六台服务器要协调的规模手动操作就是一场灾难。你不可能每台机器登录上去敲命令也不可能在半夜爬起来重启一个挂了半天的任务。Scrapyd 解决的是远程部署 调度的问题——它本质是一个 HTTP 服务你把 Scrapy 工程打包上传到 Scrapyd 服务器通过 API 就能启动、停止、查询爬虫任务。这样一套机制下来爬虫就能脱离本地终端运行变成一台真正意义上的后台服务。但 Scrapyd 也只是解决了单个节点的问题。多个服务器、多个爬虫版本的统一管理依旧很痛苦。这个时候 Gerapy 就派上用场了。它本身是一个基于 Django 构建的 Web 管理界面可以绑定多台 Scrapyd 服务器基于 Scrapyd API 做集群管理——直接在浏览器里查看所有节点的运行状态、并发任务、调度日志还能在线编辑爬虫代码、打包部署到指定节点。说白了这套组合的终极目标就一句话让爬虫变成像部署 Web 服务一样标准化的流程。2. 从 0 搭建这套框架的实操细节2.1 环境准备与版本搭配版本搭配是第一个坑。网上很多教程直接pip install scrapyd gerapy一把梭结果 Gerapy 装了最新版反而连不上旧版 Scrapyd因为两者之间的 API 在不同版本里有差异。我自己在项目里稳定用下来的组合是这样组件推荐版本说明Python3.8 或 3.103.9、3.11 也行但个别依赖编译会有小问题Scrapy2.7.x2.8 以上也可以但 2.7 在稳定性和兼容性上最稳Scrapyd1.2.11.2.1 是最后一个稳定版本1.2.0 有个日志目录的 bugGerapy1.1.x1.1.0 之后管理界面才比较成熟scrapyd 1.2.1有个很坑的默认配置bind是127.0.0.1意味着只能本机访问远程节点的 Gerapy 根本连不上。你需要修改scrapyd.conf把bind改成0.0.0.0port保持6800不动。这个配置不改Gerapy 那边显示节点离线你就等着排查到怀疑人生。2.2 Scrapyd 部署与配置安装 Scrapyd 很简单pip install scrapyd1.2.1但直接启动scrapyd命令你大概率会遇到ImportError相关的问题最常见的是scrapyd依赖了旧版的Twisted而 Scrapy 2.x 又需要新版的Twisted。解决方法是装完后强制重装一遍pip install --upgrade twisted启动后Scrapyd 默认会在当前目录下创建eggs、logs、dbs三个目录分别存放打包后的工程文件、爬虫日志和任务数据库。然后可以访问http://localhost:6800测试是否启动成功能打开一个简陋的页面说明服务已经在跑了。还有一个很多人忽略的点生产环境建议用 systemd 来守护 Scrapyd 进程。爬虫服务挂了不可怕可怕的是挂了没人发现或者重启后日志丢失。我一般会在/etc/systemd/system/scrapyd.service写一个服务文件配置好工作目录和 Python 环境路径并设置Restartalways。这样即使进程异常退出也能自动拉起。2.3 项目代码层面的配合光装好 Scrapyd 还不够你的 Scrapy 工程要能被正常调度还需要在代码层面配合。首先确认scrapy.cfg里有这样一段配置[deploy] url http://127.0.0.1:6800/ project your_project_name这里的url指向 Scrapyd 服务地址。用scrapyd-deploy部署时Scrapyd 会读取这个配置把项目打包成一个 egg 文件上传。其次要注意Scrapyd 默认的并发配置很低。它的max_proc默认是 0也就是自动根据 CPU 核数开进程但单进程内max_proc_per_cpu默认也是 0同样自动分配。大多数情况下这些默认值够用但如果你有多台机器CPU 核数多会导致任务并发数一下飙太高。我一般会在scrapyd.conf里显式限制[scrapyd] max_proc 4 max_proc_per_cpu 2另外Scrapy 项目里的settings.py需要设置CONCURRENT_REQUESTS单进程并发请求数和DOWNLOAD_DELAY下载延迟。这些参数决定了爬虫的实际压力。初学者最容易犯错的地方是把并发数调得特别大疯狂请求对方服务器结果 IP 被封了整个任务白跑。针对不同网站这个参数是动态调整的静态页面可以放宽动态渲染页面建议调小并发。2.4 Gerapy 安装与节点绑定安装 Gerapypip install gerapy1.1.0 gerapy init cd gerapy gerapy migrate gerapy runserver 0.0.0.0:8000然后浏览器访问http://localhost:8000第一次会让你创建管理员账号。登录后进入界面第一件事就是添加 Scrapyd 节点点击节点管理选择创建节点主机名填写 Scrapyd 所在机器的 IP 地址端口填写6800测试连接直到显示连通状态有个细节Gerapy 所在机器和 Scrapyd 所在机器要是网络互通的。如果是云服务器需要在安全组里放开 6800 和 8000 端口否则会出现 Gerapy 能打开但节点一直离线的情况。3. 核心链路实操从部署到任务调度3.1 打包部署整个爬虫工程完成上述配置后你的爬虫工程就可以正式部署到 Scrapyd 了。部署分两种方式方式一传统命令行部署Scrapyd 客户端在 Scrapy 工程目录下运行pip install scrapyd-client scrapyd-deploy default -p your_project_name部署成功后会返回一个 JSON里面有project和spiders两个字段前者确认项目是否上传成功后者列出该项目下所有可用的爬虫。如果这一步返回空列表多半是 Scrapy 工程有语法错误或者 spider 文件里没有定义name属性。方式二Gerapy 在线部署Gerapy 提供了更方便的在线部署方式不需要本地安装scrapyd-client。操作流程是在 Gerapy 的项目管理里点击创建项目上传 Scrapy 工程的压缩包zip格式不要带外层文件夹Gerapy 自动解压项目并读取scrapy.cfg获取部署信息点击打包Gerapy 会自动编译整个工程点击部署选择目标节点Gerapy 把打包产物通过 Scrapyd API 推送到对应服务器部署完成后回到任务管理里就能看到这个项目下的所有爬虫列表了。使用 Gerapy 这种方式的好处是你不需要每台服务器都装一份源码代码集中管理在 Gerapy 这一侧线上代码版本一目了然。3.2 Scrapyd API 命令详解部署完毕后日常调度操作就是基于 Scrapyd 提供的 HTTP API。给你梳理一份最常用的 API 对照表这些在 Gerapy 里会有图形化按钮但命令行方式在排查问题时更直接功能请求方式接口地址参数启动任务POST/schedule.jsonproject,spider,version可选停止任务POST/cancel.jsonproject,job任务ID查询任务状态GET/listjobs.jsonproject查看所有项目GET/listprojects.json无查看项目爬虫GET/listspiders.jsonproject删除项目POST/delproject.jsonproject比如手动启动爬虫curl -X POST http://your-scrapyd-host:6800/schedule.json \ -d projectyour_project_name \ -d spideryour_spider_name返回结果里会带一个jobid字段这就是这个任务实例的唯一标识。后面如果你想停止它就要用这个jobid调用cancel.json。这个jobid很重要很多人在 Gerapy 界面上点停止按钮无效排查发现是任务不在运行状态其实是jobid没对上号——停止任务前需要确认任务当前状态是running还是finished。3.3 通过 Gerapy 完成日常调度Gerapy 的核心价值就在任务调度层面。它的任务管理页面会列出当前节点上所有运行中和已完成的任务每行任务都显示爬虫名称、启动时间、运行状态、耗时和日志视图入口。你可以在界面上直接对一个爬虫做以下操作运行点击启动按钮填写可选参数比如要爬取的 URL 列表立即调度停止强制终止任务适合规则异常时的紧急干预查看日志实时跟踪爬虫运行日志每个爬虫的日志文件都会完整保留按天切割定时启动Gerapy 本身没有内置定时功能但可以通过创建两个任务配合操作系统 Crontab 实现定时调度比如每天凌晨 2 点用 Crontab 触发一次 API 调用我个人的习惯是Gerapy 用来管理真正的定时还是交给 Crontab。原因是 Gerapy 是 Web 服务如果服务器重启它的调度配置不会自动恢复而 Crontab 是系统级的只要机器活着它就在。按以下方式做一个每天凌晨 3 点的定时任务0 3 * * * curl -X POST http://your-host:6800/schedule.json -d projectyour_project_name -d spideryour_spider_name不要在 Gerapy 里造重复的轮子让各系统各司其职。3.4 多节点集群管理当你有两三台以上的服务器跑爬虫时单机调度的概念就升级成了集群调度。Gerapy 支持在节点管理里把多台 Scrapyd 服务器全部添加进来。假设你有两台服务器 A 和 B都安装了 Scrapyd并且代码是一致的那么当你在 Gerapy 的任务管理里选择某个项目部署时可以同时选中 A、B 两个节点推送。部署完成后两个节点上都有这份爬虫代码。任务分配上Gerapy 没有那么智能的负载均衡它不会自动帮你把任务平均分配。你需要自己决定哪个爬虫跑在哪台机器上。我的做法是相同网站的爬虫固定跑在同一个节点这样能最大化利用本地 DNS 缓存和连接池。如果同一个网站的任务在多个节点上同时启动对目标服务器来说相当于多倍的并发压力很容易触发反爬机制。所以多节点的意义在于隔离和容错而不是单纯增加并发。比如一个爬虫要爬工商数据一个要爬电商商品就分开放在不同的节点上避免一个任务的失败拖累另一个任务。4. 常见问题与排查技巧实录4.1 任务部署成功但无法运行这是最常见的问题之一scrapyd-deploy返回正常/listspiders.json也能看到爬虫名字但启动任务时返回错误或者任务一直在pending状态不执行。排查步骤我按顺序说查看 Scrapyd 的日志通常在你的工作目录的logs文件夹下找对应项目名的错误日志确认磁盘空间是否足够Scrapyd 打包的文件和日志都很吃磁盘满了会直接导致任务阻塞看 Scrapy 工程是否依赖了第三方包如果pip依赖没有安装在 Scrapyd 所在的服务器上爬虫一运行就ModuleNotFoundError检查 Scrapyd 的内存和 CPU 限制max_proc设置过低时会排队等待尤其注意第 3 点。Scrapyd 部署的只是你的工程源码不是整个 Python 环境工程里所有依赖的第三方库都需要在目标服务器上提前配好。我自己踩过一个大坑开发环境没问题部署到线上机器后爬虫一直报ring库导入报错排查半天发现是ring这个包在那个环境里没装。后来我在项目里维护了一个requirements.txt并且在新机器部署前先把依赖全部装掉问题数量立刻大幅下降。4.2 Gerapy 显示节点离线Gerapy 的节点状态检测是基于 Scrapyd 的/daemonstatus.json接口。如果界面显示离线通常有这几种可能现象原因解决办法节点离线本机 curl 正常防火墙或安全组未开放端口放行 6800 端口节点离线Scrapyd 启动报错端口被占用换端口或杀掉占用进程GERAPY 访问外网失败网络隔离检查跨网段路由与代理设置还有一个容易忽略的问题Scrapyd 配置里的bind如果还是127.0.0.1Gerapy 从其他机器访问时TCP 层根本连不通。这个在前面反复强调过一定要确认bind已经改成了0.0.0.0。4.3 日志文件不输出或内容为空Scrapyd 正常情况下日志是按项目分目录存放的如果日志文件创建了但里面一直是空的可以在scrapyd.conf找到log_level配置默认是INFO有时候为了减少日志量有人会改成ERROR这样会导致 DEBUG 信息不输出看起来像没有日志。另一个原因是Scrapy 的 LOG 配置没有和 Scrapyd 的日志系统对齐。在 Scrapy 工程的settings.py里如果设置了LOG_ENABLED False那 Scrapyd 自然收不到任何日志。要保证线上能看日志settings.py里的LOG_ENABLED必须为True并且不建议设置LOG_FILE让日志直接走标准输出这样 Scrapyd 才能捕获。4.4 线上爬虫正常但内存持续飙升这个问题经常出现在大量 item 堆积在内存中时没及时写入数据库或者某些中间件有内存泄漏。排查办法用ps -e -o pid,rss,cmd按内存排序查看各进程占用确认ITEM_PIPELINES里是否有批量收集数据的功能比如攒够 1000 条再批量写库这种方式在内存紧张时要慎用确认是否在半路抛异常导致进程假死Twisted的异常处理坑很多需要看日志确认内存峰值和 Crawler 进程数量有直接关系所以我前面强调用max_proc控制并发进程数这不仅仅是控制对目标网站的压力也是保护你自己的服务器。5. 进阶玩法Scrapy 结合 Playwright 应对动态请求5.1 为什么要在 Scrapy 链路里引入 PlaywrightScrapy 本身对静态页面的抓取非常高效走的是同步的 Twisted 模型配合中间件能做各种反爬绕过。但现在的网站尤其是那些后台管理系统、数据大屏、带有复杂交互的页面已经普遍采用前端渲染的方式——数据不是直接写在 HTML 里而是页面 JS 加载时才去后端接口拉取甚至整个页面是用iframe嵌套的。你直接请求 HTML返回的只是空骨架什么数据都拿不到。这时候有两个解决思路在 Scrapy 中模拟浏览器用 Selenium 或 Playwright 渲染页面后再抓取逆向分析页面中的 API 接口直接请求数据接口第二种思路效率最高但门槛也不低需要你能看懂前端的请求逻辑并且能处理好签名、加密参数。当你做逆向做不动的时候就用 Playwright 来兜底。Playwright 比 Selenium 的优势非常明显不需要额外安装 WebDriver自带浏览器内核API 设计更现代支持异步速度也更快。最重要的是它完美解决了iframe嵌套表单一类的深水区爬取问题。5.2 Scrapy Playwright 的环境配置要在 Scrapy 里用 Playwright需要先做一个桥梁。我们不需要把整个 Scrapy 换掉只需在中间件层做一些适配。安装依赖pip install playwright playwright install chromium然后在 Scrapy 工程里写一个自定义下载中间件核心代码思路如下# middlewares.py from playwright.sync_api import sync_playwright class PlaywrightMiddleware: def process_request(self, request, spider): if request.meta.get(render_js): # 只有标记了 render_js 的请求才走浏览器 with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(request.url, wait_untilnetworkidle) body page.content() browser.close() return HtmlResponse(urlrequest.url, bodybody, encodingutf-8, requestrequest) return None在settings.py中启用这个中间件DOWNLOADER_MIDDLEWARES { myproject.middlewares.PlaywrightMiddleware: 543, }然后在你的爬虫里这样使用# spider.py class JsSpider(scrapy.Spider): name js_spider def start_requests(self): yield scrapy.Request( urlhttps://example.com/dashboard, meta{render_js: True} ) def parse(self, response): # 此时返回的是渲染后的完整 HTML yield {title: response.xpath(//h1/text()).get()}这套方案能解决大多数动态渲染页面的问题但代价是速度慢了很多。一次浏览器渲染大概是直接请求的 3 到 5 倍时间。所以只用它处理必须渲染的请求而不是所有请求都走浏览器这是我前后做了几百个爬虫项目后的最重要心得。5.3 iframe 嵌套页面的处理思路iframe是动态页面里最让人头疼的一个场景——页面数据不在主文档里而是嵌套在子页面中。很多人第一反应是用 XPath 直接定位但发现根本拿不到 iframe 里的内容因为在 HTML 解析层面iframe的内容是一个独立的文档。处理 iframe 有两种方式方式一直接拼接 iframe 的 URL 进行请求先用 XPath 抓取 iframe 标签的src属性然后把 iframe 的 URL 当作新的请求发出去。这是最轻量的方式而且不要等 iframe 里的 JS 渲染有些 iframe 本身也是动态页面的那就再对 iframe 的 URL 套一层 Playwright 渲染。def parse(self, response): iframe_src response.xpath(//iframe/src).get() if iframe_src: yield scrapy.Request( urlresponse.urljoin(iframe_src), meta{render_js: True}, callbackself.parse_iframe ) def parse_iframe(self, response): # 这时候 response 里才是真正承载数据的文档 pass方式二在页面内切换 iframe 上下文这种方式更适合会用 Playwright API 的情况。在中间件渲染过程中用page.frame_locator定位到目标 iframe然后把 iframe 内部的内容提取出来。def process_request(self, request, spider): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(request.url, wait_untilnetworkidle) # 切换到 iframe 并提取内容 frame page.frame(nameiframe-name) content frame.content() browser.close() return HtmlResponse(urlrequest.url, bodycontent, encodingutf-8, requestrequest)两种方式适用场景不同第一种适合 iframe 的 src 是独立链接的情况效率高第二种适合 iframe 与主页面有大量交互逻辑的情况比如点击 iframe 里的按钮才显示数据。在实际项目中你会发现很多反爬机制就在这种细节里——某些 iframe 的src还被 JavaScript 动态设置直接用 XPath 根本拿不到这时就必须要用浏览器渲染。从 Scrapy 到 Scrapyd 再到 Gerapy这条工具链把爬虫的工程化问题解决得很彻底Scrapy 负责采集逻辑Scrapyd 负责远程运行Gerapy 负责可视化集群管理。再加上 Playwright 作为动态渲染补充方案基本上已经覆盖了目前市面上绝大多数网站的采集需求。如果你现在还在本地命令行里一条一条跑爬虫是时候把这套框架搭起来让机器替你做那些重复劳动了。
返回列表