真正有效的做法是分层控制+动态响应:需设concurrent_requests_per_domain为第一道闸,配合autothrottle_enabled自动调优,并禁用重定向与冗余重试。

直接调低 CONCURRENT_REQUESTS 不能解决“压垮服务器”的问题,真正有效的做法是分层控制 + 动态响应,而不是靠单一参数硬调。
为什么只改 CONCURRENT_REQUESTS 是错的
这个参数只控制“引擎能同时处理多少请求”,但不区分域名、不感知响应状态、也不管 IP 归属。比如你设成 8,但目标网站有 20 个子域名(a.example.com、b.example.com…),Scrapy 默认会把它们当不同域名,每个都发 8 个并发——实际对后端可能全是同一台服务器,瞬间就超载。
常见错误现象包括:
- 目标返回大量
503 Service Unavailable或连接被重置 - 同一 IP 的
CONCURRENT_REQUESTS_PER_IP实际未生效,因为 DNS 解析或 CDN 导致多个域名指向相同后端 IP - 日志里频繁出现
Twisted timeout或Connection refused,但CONCURRENT_REQUESTS看起来并不高
CONCURRENT_REQUESTS_PER_DOMAIN 才是防压垮的第一道闸
它强制限制对每个域名的并发请求数,比全局参数更贴近真实服务边界。必须设,且通常要比 CONCURRENT_REQUESTS 小得多。
- 反爬强的站点(如电商、地图类):设为
1或2,配合DOWNLOAD_DELAY = 2 - 中等压力站点(企业官网、博客):设为
4~8,启用RANDOMIZE_DOWNLOAD_DELAY = True - 内部测试环境或无反爬站点:可设为
16,但仍建议不超过CONCURRENT_REQUESTS的 1/2
注意:CONCURRENT_REQUESTS_PER_DOMAIN 和 CONCURRENT_REQUESTS_PER_IP 冲突时,后者优先。如果多个域名共用一个 IP(比如用 Cloudflare),必须启用 CONCURRENT_REQUESTS_PER_IP 并设为更低值(如 2)。
动态调整不是实时改变量,而是靠中间件响应反馈
Scrapy 没有运行时 API 让你随时修改 CONCURRENT_REQUESTS。所谓“动态”,是指根据服务器反馈自动降级并发策略,核心靠下载中间件 + 状态码拦截:
- 监听
503、429、500响应,在process_response中触发临时限流:比如给当前域名加self.crawler.engine.slot.scheduler.delay = 5 - 用
AUTOTHROTTLE_ENABLED = True替代手动调参——它会基于响应延迟和失败率,自动拉长DOWNLOAD_DELAY,比硬调并发更平滑 - 不要在
spider.parse()里调time.sleep(),这会阻塞整个 Twisted 循环;所有延迟必须走 Scrapy 调度器机制
示例片段(中间件节选):
def process_response(self, request, response, spider):
if response.status in [503, 429]:
spider.logger.warning(f"Throttling {request.url} due to {response.status}")
# 触发该域名下一次请求延后 3 秒
spider.crawler.engine.slot.scheduler.enqueue_request(
scrapy.Request(url="dummy://", dont_filter=True, priority=1000)
)
# 实际应结合 domain-specific delay logic
return response
容易被忽略的底层影响点
并发数调得再小心,如果没关掉这些,照样可能打崩目标服务器:
-
REDIRECT_ENABLED = False:默认开启重定向,302 跳转会立刻发新请求,相当于隐式放大并发 -
RETRY_ENABLED = True:失败重试默认 2 次,503一来就是三连击,务必设RETRY_TIMES = 0或仅重试5xx中的特定码 - 没配
DOWNLOAD_TIMEOUT:慢响应长期占着并发 slot,建议设为15,避免拖慢整体队列 - 用了
scrapy-redis却没调CONCURRENT_REQUESTS_PER_DOMAIN:分布式下各节点独立计数,极易叠加超发











