每天15分钟玩转Scrapy - 部署与运维
开场
这一篇讲爬虫跑起来之后的事。
前面十篇都是在本机命令行里跑,你能看到每一个字。上线之后这层透明就没了。服务器上进程在不在、跑到哪一步了、报了什么错,你得有别的手段去知道。这一篇就是补上这些手段。
内容分三块。第一块是怎么看见一个正在跑的爬虫,2.19 在这一块给了个新东西,我实测了一遍。第二块是怎么把爬虫从一台机器搬到一群机器上,我用 scrapyd 走了一遍完整链路,从打包到调度到取回数据。第三块是性能基线,scrapy bench 我这次真的跑通了,数字在下面。
顺手说一句环境的事。这几节里的命令我是在 Windows 上跑的,涉及路径和权限的地方跟 Linux 有差别,我遇到差异会单独标出来。说实话这一篇里最容易踩的坑全都跟环境有关,跟 Scrapy 本身反倒关系不大。
上线与运维
爬虫上线之后,最想知道的一件事是它现在在干什么。
这一章先解决「看见」,再解决「部署」,最后解决「量基线」。三件事的顺序不能反,因为你得先能看见,改完之后才知道自己改了什么。
1. RemoteControl 把进程打开一扇窗
2.19 新增了一个默认启用的扩展,把运行的爬虫进程变成一个可查询、可执行的 HTTP 服务。
它监听在 localhost 的随机端口上,要求 Bearer 令牌认证。端口和令牌写在一个 job 文件里,位置是系统状态目录下的 scrapy/job_files,Windows 上是 %LOCALAPPDATA%\scrapy\job_files。
我把目录改到项目里方便观察。
| |
文件内容是 JSON,权限 0600。
端口和令牌都拿到了,先查状态。
| |
不带令牌会拿到 401,这一条我也验了。
真正有意思的是 /execute,它让你在那个进程里跑 Python。作用域里有两个东西,crawler 是当前爬虫实例,stash 是一个跨调用持久存在的字典。
| |
exec1.json 里是这段代码。
| |
返回是这样的。
再调一次,换一段代码,验证 stash 是不是真的跨调用活着。
三件事都对上了。实时统计能读到,stash 跨调用持久,crawler.engine.running 反映出引擎当前状态。

⚠️ 有两件事必须提醒。第一,它能执行任意代码,等价于把解释器开在别人面前,虽然默认只监听 127.0.0.1 而且有令牌,但也别往公网地址上绑。第二,job 文件里就放着令牌,权限虽然设成了 0600,也绝对不要随手贴到聊天窗口或者 issue 里。
这个扩展官方说是给 Scrapy MCP server 用的,也就是说你以后可以让 AI 直接通过这扇窗看你的爬虫。
2. 部署的三种落点
爬虫跑在本机和生产环境是两件事。第三种部署方式这里只讲选择依据,不展开操作。
Scrapyd 是官方出的爬虫服务。它把项目打包上传、按需调度、在网页上看日志。适合一个团队共用几台机器跑很多爬虫的场景。配套的工具是 scrapyd-client,用它把项目打成 egg 再传上去。
⚠️ 用 Scrapyd 之前先改一件事,它的监听地址默认是 0.0.0.0,也就是全网可达。生产环境务必改成内网地址,前面再套一层带认证的反向代理。
容器是另一种落点。爬虫的依赖经常很啰嗦,浏览器、字体、系统库都算,打进镜像之后「在我机器上能跑」这句话才算作数。JOBDIR 记得挂到卷上,不然容器一重启进度就没了。
再就是托管平台。官方的 Scrapy Cloud 属于这一类,把代码推上去就能跑。省事的地方在于不用管机器,代价是调试不如自己机器上方便,尤其是要抓需要浏览器的站点。
3. 用 bench 给机器量个底
调优之前得先有一个基线,不然你不知道自己改的到底是快了还是慢了。
官方准备了一个命令。
| |
它的做法是起一个本机服务端,在一个固定地址上拉固定量的数据,默认跑 10 秒看能处理多少页面。
我这次把它跑通了,逐秒的输出是这样。
| |
跑完的统计。
| |
十秒跑了一千五百多个响应,十兆字节。注意那个速率曲线是往下走的,第一秒一万两千多,到了第十秒掉到七千二。这是正常的,累积的排队和本机资源占用会一点点吃掉速度,所以拿这个数做横向比较的时候,要保证跑的是同样的时长,不然比出来的不是同一件事。

⚠️ 第一次跑它的时候我拿到的是这个。
| |
重试三次全是 502,bench 直接失败退出。报错里没有任何一个字提到代理,那三个 502 看起来像是本机那个 8998 服务端出了问题。
真实原因是我这台机器配了系统级 HTTP 代理,bench 内部发的请求走了代理,而代理当然不知道 localhost:8998 是什么,于是回了一个 502。把代理相关的环境变量清掉,或者把 localhost 加进 NO_PROXY,它就正常了。
这个坑值得单独记住,因为它会伪装成别的错误。 任何连本机端口的实验,跑之前都先确认代理有没有被绕开。我在前面几篇里搭的那些本地服务端,也是这样才跑通的。这事儿我当初没往代理上想,一直在查那个服务端,绕了一圈才回头发现。
回到 bench 的定位。说到底它测的是框架在你机器上的吞吐上限,不是你那个具体蜘蛛的吞吐。蜘蛛的形状、目标站的响应速度、反爬策略都会大幅改变结果。有真实数据的时候以真实数据为准,bench 的用途是换机器、换配置之后的横向比较。 我自己的感受是,性能这件事上直觉的准确率低得让人意外,有个数字在手边会踏实很多。
用 scrapyd 把爬虫变成服务
上一节把三种落点的特点过了一遍,这一节挑 scrapyd 走一条完整的链路,从打包到调度到取回数据。它是官方出的爬虫服务,适合一个团队共用几台机器跑很多爬虫的场景。
装两个包,scrapyd 是服务端,scrapyd-client 是打包上传用的客户端。
| |
服务端要一份配置文件。默认端口是 6800,我换成 6801 避免跟别的东西撞上,监听地址明确写成 127.0.0.1。
| |
起起来之后,日志里能看到它在做什么。
| |
max_proc 是 4,下面四行是四个进程槽就位。这个数字决定了这台机器上同时能跑几个爬虫进程,它跟你蜘蛛内部的并发是两回事,一层是进程级的,一层是请求级的。
⚠️ bind_address 这一行别省。scrapyd 的默认值是 0.0.0.0,也就是全网可达,而它的 API 里有一个 schedule 接口可以让你启动任意已上传的爬虫。生产环境务必写成内网地址,前面再套一层带认证的反向代理。
客户端那一侧改 scrapy.cfg。
| |
然后一条命令上传。
| |
输出是这样的。
| |
版本号是打包那一刻的 Unix 时间戳,所以每次上传都是一个新版本,服务端会同时留着旧版本。回执里的 spiders 是它从这个包里认出来的蜘蛛数量。
上传之后服务端的工作目录长这样。
| |
eggs 里是你上传的包,dbs 里是作业记录,items 和 logs 按项目和蜘蛛分层。
接下来全走 HTTP 接口。四个查询接口先看一眼。

| |
让它跑一次。
| |
拿到 jobid 之后查状态。
任务跑完了,起止时间、日志地址、数据地址都在里面。这两个 URL 可以直接拼上服务端地址去下载,抓到的数据就在那个 .jl 文件里,一行一条 JSON。
⚠️ 这里有个数字值得注意。任务的墙钟耗时是三秒半,而任务日志里那一行是 'elapsed_time_seconds': 1.3554213000024902。差了大约两秒,那是进程启动和 egg 解包的开销。 也就是说 elapsed_time_seconds 量的是蜘蛛自己的运行时间,不含调度开销。你要是拿它估算「跑一千个任务要多久」,会明显低估。
三个东西一起构成了这套方案的可用性。eggs 目录让回滚变成「切一个版本号」,logs 目录让每个任务的输出都能事后翻,items 目录让数据不用你自己写管道去落盘。代价是它只负责调度,任务的依赖、环境、出口 IP 这些还是得你自己在机器上准备好。
关于容器和托管平台,我在这个环境里没有 Docker 可用,所以那两条路径只写选择依据,不给操作步骤。这里要如实说清楚,避免你把没验证过的东西当成验证过的。
容易栽的跟头
坑 1:以为 job 文件会自己清理干净。 它会清理,但只在正常退出的时候。我做完这一篇的实验之后去数了一下那个目录,里面有 80 个文件,日期从早上零点一路排到实验结束。
| |
这些文件是爬虫进程正常结束时自己删掉的漏网之鱼,进程被强杀、机器重启、调试时按了 Ctrl-C,都留下一个。它们本身很小,两百字节一个,麻烦在于里面存着对应进程的端口和令牌。放在那儿不占地方,但它是一份凭证。
定期清一下。目录位置是系统状态目录下的 scrapy/job_files,Windows 上是 %LOCALAPPDATA%\scrapy\job_files,Linux 上在 XDG 的状态目录里。要换位置就用 REMOTE_CONTROL_JOBS_DIR 指过去,我在实验里就是这么做的。
坑 2:把 REMOTE_CONTROL_ENABLED 设成真,以为它就在跑。 这个扩展依赖 asyncio 支持,不满足条件的时候会自动禁用,日志里只会留一行。它不会让爬虫启动失败,所以很容易被忽略。判断方法不是看配置,是看启动日志里有没有那一行监听端口的提示,或者直接去 job 文件目录看有没有新文件生成。
| |
有这一行才算真的起来了。
坑 3:连本机端口的实验被系统代理拦下去。 这个坑害我多花了一轮。我用 scrapy bench 的时候第一次拿到的是三次 502 重试然后放弃,报错里一个字都没提代理,看起来像是那个本机服务端坏了。
| |
原因是这台机器配了系统级 HTTP 代理,请求走了代理,代理不知道 localhost:8998 是什么就回了个 502。清掉代理环境变量,或者把 localhost 和 127.0.0.1 加进 NO_PROXY,立刻就正常了。任何连本机地址的实验,跑之前先确认这一条。 它后面还会以各种面貌出现,比如 curl 能通而程序不通,或者「本地接口莫名其妙返回 502」。
坑 4:拿 elapsed_time_seconds 估算批量任务的耗时。 这个值量的是蜘蛛自己的运行时间,进程启动和 egg 解包不算在里面。实测一个任务墙钟跑了三秒半,日志里的 elapsed_time_seconds 是 1.36 秒,差了大约两秒。任务规模小的时候这个比例很大,你要是按它去规划「一千个任务要多久」,算出来的数字会明显偏乐观。
坑 5:scrapyd 的 bind_address 用默认值。 它默认监听 0.0.0.0,全网可达。而它的 schedule 接口允许启动服务器上任意一个已上传的爬虫。这两件事凑在一起,等于给任何能访问到 6800 端口的人递了一把钥匙,他可以让你的爬虫跑起来。生产环境改成内网地址,外面套一层带认证的反向代理。
坑 6:job 文件里那个 0600 权限在 Windows 上不等于安全。 Scrapy 写这个文件的时候确实按 0o600 创建,那是 POSIX 的权限位。Windows 上走的是另一套访问控制模型,这些位是模拟出来的,实际的文件访问控制由 NTFS 的 ACL 决定。
| |
我在 Windows 上看到的就是上面这个样子,不是 0600。别把权限位当成唯一一道防线,真正的约束是那个文件里存着能在爬虫进程里执行任意代码的令牌,所以它不该出现在聊天窗口、工单、截图或者代码仓库里。
小结
这一篇的三块内容,其实在回答同一个问题,你怎么知道线上那个东西还好。
RemoteControl 给的是最细的一层。它让你在一个跑着的进程里执行代码,实时统计、当前状态、甚至临时改一点东西都能做到。2.19 把它做成默认开启,说明官方认为「能看见运行中的进程」是个基础需求。但它也是一扇需要认真对待的门,能执行代码这个能力本身就是风险。
scrapyd 给的是中间一层。打包、上传、调度、取数据,全走 HTTP 接口,一套流程下来每个任务的边界都清清楚楚。我实测那一条链路的时候,最有用的是它把「墙钟耗时」和「蜘蛛耗时」分开了,这两个数摆在一起你才知道调度开销占了多少。
bench 给的是最底下那一层,机器的能力上限。这个数字跟你的蜘蛛没有直接关系,但它是你换机器、换配置时唯一可比的基准。
这一篇里我最想让你注意的还是那个 502。它跟爬虫、跟 Scrapy、跟这台机器的性能都没有关系,纯粹是环境里有个代理在中间。排错的时候,先确认自己的实验环境是干净的,比钻研报错信息更省时间。 我在这个系列里搭了不少本地服务端做对照实验,每一次都要先过这一关。
到这里十一篇就写完了。从第一个爬虫到能上线的服务,中间那些我自己踩过的坑基本都放进来了。写的时候我尽量把「我测出来的」和「文档上说的」分开,凡是标了数字的地方都有对应的运行记录,你可以照着复现。
这篇里要是有哪里讲得不对,欢迎拍砖。