PySpider+Celery实战:轻量级分布式爬虫从单机到集群的快速落地指南(附避坑实录+源码可直接复用)
前言:在中小规模数据爬取场景中,很多开发者都会遇到一个共性问题——单机爬虫面对海量URL时,要么卡顿超时、要么效率低下,即便优化请求并发,也会受限于单台机器的CPU、带宽资源。而大型分布式爬虫框架(如Scrapy Cluster)部署复杂、学习成本高,并不适合快速落地。
笔者近期在负责某行业数据爬取项目时,就遇到了这样的痛点:需要爬取30万+条页面数据,单机PySpider跑完全部任务需要48小时以上,且中途容易出现进程崩溃、任务丢失的问题。最终选择基于PySpider+Celery搭建轻量级分布式爬虫集群,仅用1台调度机+3台工作机,就将爬取效率提升3倍以上,全程无任务丢失,且部署成本极低。
本文将从真实项目场景出发,详细拆解从单机PySpider爬虫到Celery分布式集群的完整落地流程,包含架构设计、代码改造、集群部署、监控运维等核心环节,全程附实测源码、踩坑实录和优化技巧,力求做到“拿来就用、避坑即走”,适合有一定Python爬虫基础、想快速落地分布式爬虫的开发者阅读。
注:本文所有代码均经过生产环境实测,无冗余逻辑,替换Redis地址和目标URL即可直接复用;所有避坑点均为笔者实际部署中踩过的真实问题,针对性给出解决方案,拒绝空谈理论。
一、为什么选择PySpider+Celery?(拒绝为了分布式而分布式)
在动手搭建之前,先明确一个核心问题:市面上分布式爬虫方案很多(Scrapy+Celery、Scrapy-Redis、PySpider+RabbitMQ等),为什么最终选择PySpider+Celery这个组合?
结合笔者的项目场景(中小规模数据爬取、快速落地、低运维成本),这个组合的优势的非常明显,也是实际项目中验证过的“最优解”之一:
-
PySpider的优势:无需重复造轮子——PySpider本身集成了请求发送、页面解析、任务调度、WebUI管理等功能,其Fetch模块的请求重试、cookie持久化、重定向处理等能力非常成熟,比自己用requests封装请求工具更高效、更稳定。尤其适合对页面解析逻辑复杂、需要频繁调整爬取策略的场景。
-
Celery的优势:轻量级分布式调度——Celery是Python生态中最成熟的分布式任务队列之一,配置简单、易集成,无需搭建复杂的调度中心,仅需一个消息中间件(Redis/RabbitMQ)就能实现多节点任务分发和结果存储。相比Scrapy-Redis,Celery的灵活性更高,可与PySpider无缝衔接,无需对PySpider的核心逻辑做大规模修改。
-
整体优势:低改造成本、高性价比——从单机PySpider改造为分布式集群,核心是将PySpider的“请求+解析”逻辑封装为Celery任务,剥离PySpider自身的调度模块,改造工作量小,新手也能快速上手。且依赖的Redis轻量、易部署,相比RabbitMQ,运维成本更低,适合中小团队和个人开发者。
补充:本文不对比其他分布式方案的优劣,仅聚焦PySpider+Celery的落地实操,如果你需要爬取千万级以上数据、对集群稳定性要求极高,可考虑Scrapy+Celery+RabbitMQ的组合;如果是中小规模、快速落地的场景,本文方案完全足够。
二、核心架构解析(实战视角,拒绝抽象)
很多技术文章在讲架构时,喜欢放一堆复杂的流程图,却不说明每个组件在实际项目中的作用。本文结合笔者的集群部署场景(1台调度节点+3台Worker节点),拆解核心架构的每个组件、以及组件之间的交互逻辑,让你一眼看懂“数据是如何流转的”。
2.1 核心组件及作用(实测场景)
-
调度节点(1台,阿里云轻量应用服务器,2核4G):核心负责“任务提交”和“集群管理”,部署Celery的任务提交脚本、Flower监控工具,不执行具体的爬取任务。主要作用是生成待爬URL列表、将任务提交到Redis队列,同时通过Flower监控整个集群的运行状态。
-
Worker节点(3台,腾讯云学生机,1核2G):核心负责“执行爬取任务”,部署Celery Worker、PySpider核心依赖,同步调度节点的爬虫代码。主要作用是从Redis队列中获取任务、调用PySpider的请求和解析逻辑,完成数据爬取后,将结果存储到Redis(或MongoDB/MySQL)。
-
Redis(部署在调度节点,端口6379):双重角色——作为Celery的Broker(任务消息队列),存储调度节点提交的爬取任务;作为Backend(任务结果存储),存储Worker节点执行完成的爬取结果。同时用于URL去重(避免重复爬取),是整个集群的“数据中转站”。
-
PySpider(所有节点均部署):仅复用其核心的“Fetch请求模块”和“Response解析模块”,剥离自身的scheduler、fetcher、processor等调度相关组件,避免与Celery的调度逻辑冲突。简单说,就是用PySpider“干活”(发请求、解析数据),用Celery“分配活”(分发任务)。
-
Celery(所有节点均部署):分布式调度核心,在调度节点负责提交任务,在Worker节点负责启动Worker进程、获取任务、执行任务、返回结果,通过Redis实现多节点之间的通信。
2.2 数据流转逻辑(实测流程)
-
调度节点执行任务提交脚本,生成待爬URL列表,通过Celery的delay()方法将每个URL封装为一个任务,提交到Redis的Broker队列中;
-
所有Worker节点的Celery Worker进程实时监听Redis的Broker队列,一旦有新任务,就会主动获取任务(按配置的并发数执行);
-
Worker节点调用PySpider的Fetch模块,向目标URL发送请求,获取页面响应后,通过PySpider的Response模块解析数据;
-
解析完成后,Worker节点将爬取结果通过Celery存储到Redis的Backend中(或写入业务数据库),同时标记该URL已爬取(URL去重);
-
调度节点通过Flower监控所有Worker节点的状态、任务执行进度,通过自定义脚本查询Redis中的爬取结果,完成数据汇总。
三、环境准备(避坑重点,实测可行)
环境准备是分布式集群落地的基础,也是最容易踩坑的环节之一。本文将分“系统依赖”“Python依赖”“Redis配置”三个部分,详细说明每个步骤的操作方法和注意事项,覆盖Windows和Linux系统(实测CentOS、Ubuntu、Windows 10均可行)。
核心注意点:所有节点(调度节点+Worker节点)的环境必须保持一致,尤其是Python版本、PySpider版本、Celery版本,否则会出现任务分发失败、模块导入错误等问题。
3.1 系统依赖(全节点通用)
主要安装Redis(消息中间件+结果存储),不同系统的安装方法不同,实测步骤如下:
| 系统类型 | 安装步骤(实测可行) | 避坑注意事项 |
|---|---|---|
| Ubuntu(推荐) | 1. 执行命令:sudo apt update && sudo apt install redis-server -y2. 启动Redis:sudo systemctl start redis-server3. 设置开机自启:sudo systemctl enable redis-server4. 验证:redis-cli ping(返回PONG即为正常) | 1. 若出现“redis-server: command not found”,需更新apt源后重新安装;2. Ubuntu默认Redis端口6379,无需额外配置。 |
| CentOS | 1. 执行命令:yum install redis -y2. 启动Redis:systemctl start redis3. 设置开机自启:systemctl enable redis4. 验证:redis-cli ping | 1. CentOS默认没有Redis源,若安装失败,需先安装epel源:yum install epel-release -y;2. 启动后若无法远程访问,需关闭防火墙:systemctl stop firewalld。 |
| Windows 10 | 1. 下载Redis安装包:https://github.com/tporadowski/redis/releases(选择最新的zip包)2. 解压到任意目录(如D:Redis)3. 双击redis-server.exe启动Redis(默认端口6379)4. 打开新终端,执行redis-cli ping验证 | 1. Windows版本的Redis不支持后台运行,关闭窗口即停止;2. 若出现“无法启动”,需检查端口是否被占用(netstat -ano |
3.2 Python环境配置(全节点通用,重点避坑)
这是最容易踩坑的环节!PySpider对Python版本非常敏感,实测Python3.8及以上版本会出现ImportError(依赖包不兼容),最终确定使用Python3.7版本,所有节点统一。
步骤如下(以Linux为例,Windows类似):
- 安装Python3.7:
`# Ubuntu安装Python3.7
sudo apt install python3.7 python3.7-venv python3.7-dev -y
CentOS安装Python3.7
yum install python3.7 python3.7-venv python3.7-devel -y`
- 安装Python3.7:
`# Ubuntu安装Python3.7
sudo apt install python3.7 python3.7-venv python3.7-dev -y
CentOS安装Python3.7
yum install python3.7 python3.7-venv python3.7-devel -y`
- 创建虚拟环境(避免依赖冲突):
`# 创建虚拟环境目录
mkdir -p /home/spider/env
cd /home/spider/env
生成虚拟环境(Python3.7)
python3.7 -m venv pyspider-celery-env
激活虚拟环境(Linux/Mac)
source /home/spider/env/pyspider-celery-env/bin/activate
Windows激活虚拟环境
pyspider-celery-envScriptsctivate`激活后,终端前缀会出现(pyspider-celery-env),表示进入虚拟环境。
- 创建虚拟环境(避免依赖冲突):
`# 创建虚拟环境目录
mkdir -p /home/spider/env
cd /home/spider/env
生成虚拟环境(Python3.7)
python3.7 -m venv pyspider-celery-env
激活虚拟环境(Linux/Mac)
source /home/spider/env/pyspider-celery-env/bin/activate
Windows激活虚拟环境
pyspider-celery-envScriptsctivate`激活后,终端前缀会出现(pyspider-celery-env),表示进入虚拟环境。
-
安装核心依赖(版本固定,避免冲突):
实测以下版本组合最稳定,直接执行pip命令安装,所有节点必须完全一致:pip install pyspider==0.3.10 celery[redis]==5.2.7 redis==4.5.5 requests==2.31.0 flower==2.0.1避坑点1:不要使用pip install pyspider(默认安装最新版本,与Python3.7不兼容);避坑点2:celery版本不要超过5.3.0,否则与Redis4.x版本不兼容,会出现任务提交失败;避坑点3:flower用于监控集群,版本固定为2.0.1,避免与Celery版本冲突。 -
安装核心依赖(版本固定,避免冲突):
实测以下版本组合最稳定,直接执行pip命令安装,所有节点必须完全一致:pip install pyspider==0.3.10 celery[redis]==5.2.7 redis==4.5.5 requests==2.31.0 flower==2.0.1避坑点1:不要使用pip install pyspider(默认安装最新版本,与Python3.7不兼容);避坑点2:celery版本不要超过5.3.0,否则与Redis4.x版本不兼容,会出现任务提交失败;避坑点3:flower用于监控集群,版本固定为2.0.1,避免与Celery版本冲突。
3.3 Redis远程访问配置(集群核心,必做)
分布式集群中,所有Worker节点需要访问调度节点上的Redis,因此必须修改Redis配置,允许远程访问(默认Redis只允许本地访问)。
步骤如下(以Linux为例):
- 编辑Redis配置文件:
`# Ubuntu配置文件路径
sudo vim /etc/redis/redis.conf
CentOS配置文件路径
sudo vim /etc/redis.conf
Windows配置文件路径(解压目录下)
编辑redis.windows.conf`
- 编辑Redis配置文件:
`# Ubuntu配置文件路径
sudo vim /etc/redis/redis.conf
CentOS配置文件路径
sudo vim /etc/redis.conf
Windows配置文件路径(解压目录下)
编辑redis.windows.conf`
- 修改3个关键配置(核心):
`# 1. 注释bind限制,允许所有IP访问(原配置:bind 127.0.0.1 ::1)
bind 0.0.0.0
2. 关闭保护模式(原配置:protected-mode yes)
protected-mode no
3. 生产环境建议添加密码(可选,避免Redis被恶意访问)
requirepass your_strong_password # 替换为你的密码,如123456(建议复杂一点)`
- 修改3个关键配置(核心):
`# 1. 注释bind限制,允许所有IP访问(原配置:bind 127.0.0.1 ::1)
bind 0.0.0.0
2. 关闭保护模式(原配置:protected-mode yes)
protected-mode no
3. 生产环境建议添加密码(可选,避免Redis被恶意访问)
requirepass your_strong_password # 替换为你的密码,如123456(建议复杂一点)`
- 重启Redis,使配置生效:
`# Linux重启Redis
sudo systemctl restart redis-server
Windows重启Redis:关闭redis-server.exe窗口,重新双击启动`
- 重启Redis,使配置生效:
`# Linux重启Redis
sudo systemctl restart redis-server
Windows重启Redis:关闭redis-server.exe窗口,重新双击启动`
- 验证远程访问(必做):
在任意一台Worker节点上,执行以下命令,验证是否能连接到调度节点的Redis:`# 若Redis设置了密码,执行:redis-cli -h 调度节点IP -p 6379 -a 你的密码 ping
若未设置密码,执行:redis-cli -h 调度节点IP -p 6379 ping
示例(设置了密码)
redis-cli -h 192.168.1.100 -p 6379 -a 123456 ping`返回PONG,说明Redis远程访问配置成功;若失败,检查调度节点防火墙是否开放6379端口、Redis配置是否修改正确。
- 验证远程访问(必做):
在任意一台Worker节点上,执行以下命令,验证是否能连接到调度节点的Redis:`# 若Redis设置了密码,执行:redis-cli -h 调度节点IP -p 6379 -a 你的密码 ping
若未设置密码,执行:redis-cli -h 调度节点IP -p 6379 ping
示例(设置了密码)
redis-cli -h 192.168.1.100 -p 6379 -a 123456 ping`返回PONG,说明Redis远程访问配置成功;若失败,检查调度节点防火墙是否开放6379端口、Redis配置是否修改正确。
四、单机PySpider爬虫实现(基础铺垫,先跑通再改造)
在进行分布式改造之前,先实现一个可运行的单机PySpider爬虫,验证核心的请求和解析逻辑是否正常。这一步的目的是“先跑通基础”,避免后续分布式改造时,出现问题无法定位(分不清是单机逻辑问题,还是分布式配置问题)。
本文以爬取http://httpbin.org/get(测试站点,返回请求信息,方便验证)为例,实现单机爬虫,实际项目中替换为目标站点即可。
4.1 编写单机爬虫代码(实测可运行)
创建文件single_spider.py,代码如下,每部分都加了详细注释,结合实际业务场景设计:
from pyspider.libs.base_handler import *
class SingleDataSpider(BaseHandler):
"""
单机版PySpider爬虫(基础版)
功能:爬取测试站点的请求信息,验证请求和解析逻辑
实际场景可替换为:目标站点的页面爬取、数据解析
"""
# 爬虫全局配置(贴合实际业务,避免被反爬)
crawl_config = {
# 请求头:模拟浏览器,避免被目标站点拦截
'headers': {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9'
},
'timeout': 15, # 请求超时时间(避免长时间阻塞)
'allow_redirects': True, # 允许重定向
'retries': 3 # 请求失败重试次数
}
# 爬虫入口函数:生成待爬取URL列表
# @every(minutes=24*60) # 实际场景可设置定时执行(每天执行一次)
def on_start(self):
"""
入口函数:可根据实际业务生成待爬URL
示例:批量生成测试URL,模拟多页面爬取
"""
# 模拟待爬URL列表(实际场景可从数据库、文件中读取)
target_urls = [
'http://httpbin.org/get?page=1',
'http://httpbin.org/get?page=2',
'http://httpbin.org/get?page=3',
'http://httpbin.org/get?page=4',
'http://httpbin.org/get?page=5'
]
# 循环提交爬取任务,指定解析函数为parse_detail
for url in target_urls:
self.crawl(url, callback=self.parse_detail)
# 页面解析函数:提取目标数据(核心业务逻辑)
@config(age=10*60) # 页面缓存时间(10分钟,避免重复爬取相同页面)
def parse_detail(self, response):
"""
解析函数:根据目标站点的页面结构,提取核心数据
:param response: PySpider的Response对象,包含页面响应信息
:return: 解析后的字典数据(实际场景可写入数据库)
"""
try:
# 解析页面数据(测试站点返回JSON,实际场景可使用xpath/css解析)
# 异常捕获:避免单个页面解析失败,导致整个爬虫崩溃
if response.json:
data = response.json
else:
data = response.text
# 提取核心数据(贴合实际业务,可自由修改)
result = {
'url': response.url, # 爬取的URL
'status_code': response.status_code, # 请求状态码
'request_headers': data.get('headers', {}), # 请求头信息(测试站点返回)
'爬取时间': response.headers.get('Date', ''), # 爬取时间
'page_num': data.get('args', {}).get('page', '') # 页码(测试站点返回)
}
# 打印爬取结果(实际场景可写入MySQL/MongoDB)
print(f"单机爬虫成功:{response.url},结果:{result}")
return result
except Exception as e:
# 单个页面解析失败,打印错误信息,不影响整个爬虫运行
print(f"单机爬虫解析失败:{response.url},错误信息:{str(e)}")
return None
4.2 运行单机爬虫(实测步骤)
PySpider运行需要启动4个进程(scheduler、fetcher、processor、webui),需打开4个终端(激活虚拟环境后执行),步骤如下:
# 终端1:启动PySpider调度器(核心,负责任务调度)
pyspider scheduler
# 终端2:启动PySpider抓取器(负责发送HTTP请求)
pyspider fetcher
# 终端3:启动PySpider处理器(负责解析页面数据)
pyspider processor
# 终端4:启动PySpider WebUI(可视化管理,可选,方便操作)
pyspider webui
启动成功后,访问http://localhost:5000,即可看到PySpider的WebUI界面:
-
找到SingleDataSpider(我们编写的爬虫类),点击右侧的“run”按钮;
-
在弹出的窗口中,点击“start”,即可启动爬虫;
-
查看终端3(processor)的输出,即可看到爬取结果,若所有URL都能正常爬取、解析,说明单机逻辑无问题。
避坑点:若启动时出现“ImportError: No module named ‘pyspider.libs.base_handler’”,说明虚拟环境未激活,或PySpider版本安装错误,重新激活虚拟环境、安装指定版本即可。
五、分布式改造(核心环节,实测改造思路)
单机爬虫跑通后,开始进行分布式改造。核心改造思路是:剥离PySpider的调度、抓取、处理进程,仅复用其Fetch请求模块和Response解析模块,将爬取逻辑封装为Celery任务,由Celery负责分布式任务分发和调度。
改造过程中,笔者遇到了很多问题(如任务分发失败、PySpider与Celery衔接异常、结果存储混乱等),下文会结合改造步骤,同步给出踩坑实录和解决方案。
5.1 改造核心思考(避免盲目改造)
在动手改造之前,先明确3个核心问题,避免做无用功:
-
为什么要剥离PySpider的调度模块?—— PySpider的调度模块是单机的,无法实现多节点任务分发,若不剥离,会与Celery的调度逻辑冲突,导致任务重复执行或无法执行。
-
PySpider与Celery如何衔接?—— 将PySpider的Fetch模块(发送请求)和Response模块(解析页面)封装到Celery任务中,Worker节点执行任务时,调用这两个模块完成爬取和解析。
-
任务和结果如何管理?—— 任务由调度节点提交到Redis的Broker队列,Worker节点从队列中获取任务;爬取结果由Worker节点存储到Redis的Backend中,调度节点可随时查询和汇总。
5.2 编写分布式核心代码(实测可复用)
创建分布式核心文件distributed_spider.py,该文件需要同步到所有节点(调度节点+Worker节点),代码如下,结合改造思路和避坑点编写:
from celery import Celery
from pyspider.core.fetch import Fetch # 复用PySpider的请求模块
from pyspider.core.response import Response # 复用PySpider的解析模块
import redis
import time
# -------------------------- Celery核心配置(分布式调度核心)--------------------------
# 初始化Celery实例(关键:所有节点必须使用相同的配置)
# 注意:若Redis设置了密码,broker和backend需添加密码,格式:redis://:密码@IP:6379/库名
app = Celery(
'distributed_spider', # 任务队列名称(自定义,所有节点保持一致)
broker='redis://:123456@192.168.1.100:6379/0', # 任务队列(Redis第0库)
backend='redis://:123456@192.168.1.100:6379/1', # 结果存储(Redis第1库)
include=['distributed_spider'] # 包含的任务模块(当前文件)
)
# Celery优化配置(适配爬虫场景,实测最优配置)
app.conf.update(
timezone='Asia/Shanghai', # 时区(避免时间错乱)
worker_concurrency=2, # 每个Worker的并发数(按CPU核心数调整,1核2G建议设2)
task_retry_max=3, # 任务失败最多重试3次(解决网络波动导致的失败)
task_retry_backoff=2, # 重试间隔(2秒,避免频繁重试给目标站点施压)
worker_prefetch_multiplier=1, # 每次取1个任务(避免Worker抢任务不均)
task_serializer='json', # 任务序列化方式(JSON格式,兼容所有节点)
result_serializer='json', # 结果序列化方式
accept_content=['json'], # 接收的内容格式
worker_redirect_stdouts_level='info', # Worker日志级别
task_acks_late=True, # 任务执行完成后再确认(避免Worker崩溃导致任务丢失)
)
# -------------------------- Redis配置(URL去重+辅助存储)--------------------------
# 初始化Redis连接(用于URL去重,避免重复爬取)
redis_client = redis.Redis(
host='192.168.1.100', # 调度节点Redis IP
port=6379,
password='123456', # Redis密码(与上面一致)
db=2, # URL去重使用Redis第2库,与任务、结果分离
decode_responses=True # 自动解码,返回字符串(避免bytes类型)
)
# URL去重关键:使用Redis的Set结构(天然去重,查询高效)
CRAWLED_URL_SET = 'crawled_urls' # 存储已爬URL的Set名称
# -------------------------- 爬虫核心逻辑(复用PySpider能力)--------------------------
# 初始化PySpider的Fetch模块(请求发送核心)
fetch = Fetch()
# 请求全局配置(与单机版一致,贴合实际业务,避免反爬)
request_config = {
'headers': {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9'
},
'timeout': 15,
'allow_redirects': True,
'retries': 3
}
# -------------------------- Celery分布式任务(核心改造)--------------------------
@app.task(bind=True, name='crawl_url_task') # bind=True:获取任务上下文;name:任务名称(自定义)
def crawl_url_task(self, url):
"""
Celery分布式爬取任务:封装PySpider的请求和解析逻辑
:param self: Celery任务上下文(用于重试、获取节点信息等)
:param url: 待爬取的URL
:return: 解析后的结果(存储到Redis Backend)
"""
# 第一步:URL去重(避免重复爬取,核心优化)
if redis_client.sismember(CRAWLED_URL_SET, url):
print(f"节点 {self.request.hostname}:URL {url} 已爬取,跳过")
return {"status": "success", "msg": "URL已爬取", "url": url}
try:
# 第二步:用PySpider的Fetch模块发送请求(复用成熟能力)
# 这里是衔接的核心:Fetch模块替代requests,支持重试、cookie等特性
resp = fetch.fetch(url, **request_config)
# 第三步:封装为PySpider的Response对象,方便解析(与单机版逻辑一致)
py_resp = Response(url, resp)
# 第四步:解析页面数据(复用单机版的解析逻辑,减少改造工作量)
if py_resp.json:
data = py_resp.json
else:
data = py_resp.text
result = {
'worker_node': self.request.hostname, # 标记执行任务的Worker节点(便于排查问题)
'url': py_resp.url,
'status_code': py_resp.status_code,
'request_headers': data.get('headers', {}),
'crawl_time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), # 格式化爬取时间
'page_num': data.get('args', {}).get('page', ''),
'status': 'success'
}
# 第五步:标记URL为已爬取(存入Redis Set)
redis_client.sadd(CRAWLED_URL_SET, url)
# 打印日志(便于调试,实际生产可关闭)
print(f"节点 {self.request.hostname} 爬取成功:{url},结果:{result}")
return result
except Exception as e:
# 任务失败,自动重试(最多3次)
print(f"节点 {self.request.hostname} 爬取失败:{url},错误:{str(e)},第{self.request.retries+1}次重试")
# 重试条件:未达到最大重试次数
if self.request.retries < app.conf.task_retry_max:
raise self.retry(exc=e, countdown=app.conf.task_retry_backoff)
else:
# 达到最大重试次数,返回失败结果
print(f"节点 {self.request.hostname} 爬取失败:{url},已达到最大重试次数")
return {"status": "failed", "msg": str(e), "url": url}
# -------------------------- 批量提交任务(调度节点专用)--------------------------
def batch_submit_tasks(url_list):
"""
批量提交爬取任务到Celery队列(仅在调度节点执行)
:param url_list: 待爬取的URL列表(实际场景可从数据库、文件读取)
:return: 提交的任务ID列表(用于后续查询结果)
"""
if not url_list:
print("待爬URL列表为空,无需提交任务")
return []
task_ids = []
print(f"开始提交任务,共{len(url_list)}个URL")
for url in url_list:
# 提交任务前,先检查URL是否已爬取(双重去重,避免无效任务)
if not redis_client.sismember(CRAWLED_URL_SET, url):
# delay():异步提交任务,立即返回任务ID,不阻塞后续提交
task = crawl_url_task.delay(url)
task_ids.append(task.id)
print(f"任务提交成功:任务ID {task.id},目标URL {url}")
else:
print(f"URL {url} 已爬取,跳过提交")
print(f"任务提交完成,共提交{len(task_ids)}个有效任务")
return task_ids
# -------------------------- 测试入口(仅用于调试)--------------------------
if __name__ == '__main__':
# 模拟待爬URL列表(实际场景可替换为业务URL,如从数据库读取)
target_urls = [
'http://httpbin.org/get?page=1',
'http://httpbin.org/get?page=2',
'http://httpbin.org/get?page=3',
'http://httpbin.org/get?page=4',
'http://httpbin.org/get?page=5',
'http://httpbin.org/get?page=6',
'http://httpbin.org/get?page=7',
'http://httpbin.org/get?page=8',
'http://httpbin.org/get?page=9',
'http://httpbin.org/get?page=10'
]
# 批量提交任务(仅在调度节点执行)
batch_submit_tasks(target_urls)
5.3 改造关键说明(避坑重点,实测总结)
这部分是分布式改造的核心,也是笔者踩坑最多的地方,结合实际改造经历,说明几个关键要点:
-
Celery与Redis的衔接:
避坑点:若Redis设置了密码,broker和backend的格式必须是“redis://:密码@IP:6379/库名”,注意密码前的冒号不能少,否则会出现“认证失败”。笔者一开始遗漏了冒号,导致任务提交失败,排查了1小时才找到问题。优化点:将任务队列(Broker)、结果存储(Backend)、URL去重(db=2)分别使用Redis的不同数据库,避免数据混乱,后续排查问题更方便。 -
PySpider与Celery的衔接:
核心是复用PySpider的Fetch和Response模块,无需自己封装请求工具。笔者一开始尝试用requests替代Fetch模块,发现需要重新处理cookie持久化、请求重试等逻辑,工作量大且不稳定,最终选择复用PySpider的成熟模块,效率提升很多。避坑点:Fetch模块的初始化必须在Celery任务之外,若在任务内部初始化,会导致每个任务都创建一个Fetch实例,占用大量资源,最终导致Worker崩溃。 -
URL去重设计:
分布式爬虫最容易出现的问题就是重复爬取,笔者一开始未做去重,导致多个Worker节点爬取同一个URL,不仅浪费资源,还可能被目标站点反爬。最终选择Redis的Set结构做去重,查询和插入效率都很高,且支持多节点共享去重数据。优化点:提交任务前和执行任务前都做一次去重(双重去重),避免无效任务提交到队列,提升集群效率。 -
任务重试机制:
结合爬虫场景,设置task_retry_max=3、task_retry_backoff=2,解决网络波动、目标站点临时不可用导致的任务失败。同时通过self.retry()实现自动重试,无需手动干预,提升集群稳定性。避坑点:必须在except块中判断重试次数,避免无限重试,否则会导致Redis队列中积累大量失败任务,占用资源。
六、集群部署(实测步骤,从单机到多节点)
分布式核心代码编写完成后,开始进行集群部署。部署流程分为3步:同步代码到所有节点 → 启动Worker集群 → 调度节点提交任务。本文以“1台调度节点+3台Worker节点”为例,详细说明部署步骤和避坑点。
前置条件:所有节点的环境已配置完成(Python3.7、核心依赖、Redis远程访问正常),分布式核心文件distributed_spider.py已同步到所有节点(建议放在同一目录,如/home/spider/code/)。
6.1 代码同步(所有节点)
将distributed_spider.py文件同步到所有节点的同一目录,推荐使用scp命令(Linux)或手动复制(Windows):
# 示例:从调度节点(192.168.1.100)同步到Worker节点(192.168.1.101)
# 在调度节点执行(需知道Worker节点的用户名和密码)
scp /home/spider/code/distributed_spider.py root@192.168.1.101:/home/spider/code/
# 输入Worker节点密码,即可完成同步
# 其他Worker节点执行相同命令,替换IP即可
避坑点:所有节点的代码必须完全一致,尤其是Celery的配置(Redis IP、密码、库名),若有差异,会导致任务分发失败或结果存储异常。笔者一开始因其中一台Worker节点的Redis密码写错,导致该节点无法获取任务,排查了很久才发现。
6.2 启动Worker集群(所有Worker节点执行)
Worker节点的核心作用是执行爬取任务,需要启动Celery Worker进程,后台运行(Linux),步骤如下:
-
激活虚拟环境:
source /home/spider/env/pyspider-celery-env/bin/activate -
激活虚拟环境:
source /home/spider/env/pyspider-celery-env/bin/activate -
切换到代码目录:
cd /home/spider/code/ -
切换到代码目录:
cd /home/spider/code/ -
启动Celery Worker(后台运行,实测命令):
# 后台运行Worker,日志输出到celery_worker.log,便于后续排查问题 nohup celery -A distributed_spider worker --loglevel=info -c 2 > celery_worker.log 2>&1&
命令说明:-
-A distributed_spider:指定Celery的应用模块(当前代码文件);
-
worker:启动Worker进程;
-
–loglevel=info:日志级别为info,打印任务执行情况;
-
-c 2:并发数,与之前Celery配置中的worker_concurrency一致;
-
nohup … &:后台运行,关闭终端不影响Worker进程;
-
celery_worker.log 2>&1:将日志输出到celery_worker.log文件。
-
-
启动Celery Worker(后台运行,实测命令):
# 后台运行Worker,日志输出到celery_worker.log,便于后续排查问题 nohup celery -A distributed_spider worker --loglevel=info -c 2 > celery_worker.log 2>&1&
命令说明: -
验证Worker是否启动成功:
`# 查看Worker进程
ps aux | grep celery
查看日志(关键,确认是否连接到Redis)
tail -f celery_worker.log`若日志中出现“Connected to redis://😗***@192.168.1.100:6379/0”,说明Worker已成功连接到Redis,启动正常;若出现“Connection refused”,检查Redis IP、端口、密码是否正确,防火墙是否开放。
- 验证Worker是否启动成功:
`# 查看Worker进程
ps aux | grep celery
查看日志(关键,确认是否连接到Redis)
tail -f celery_worker.log`若日志中出现“Connected to redis://😗***@192.168.1.100:6379/0”,说明Worker已成功连接到Redis,启动正常;若出现“Connection refused”,检查Redis IP、端口、密码是否正确,防火墙是否开放。
Windows Worker节点启动方法(无需后台运行):
# 激活虚拟环境
pyspider-celery-envScriptsactivate
# 切换到代码目录
cd D:spidercode
# 启动Worker
celery -A distributed_spider worker --loglevel=info -c 2
避坑点:Windows节点关闭终端后,Worker进程会停止,若需要长期运行,可使用“任务计划程序”设置开机自启。
6.3 提交任务(调度节点执行)
所有Worker节点启动正常后,在调度节点提交任务,步骤如下:
-
激活虚拟环境,切换到代码目录:
source /home/spider/env/pyspider-celery-env/bin/activate cd /home/spider/code/ -
激活虚拟环境,切换到代码目录:
source /home/spider/env/pyspider-celery-env/bin/activate cd /home/spider/code/ -
执行代码,提交任务:
python distributed_spider.py提交成功后,终端会输出任务提交信息,显示每个URL的任务ID;同时,所有Worker节点的celery_worker.log日志中,会显示“获取任务→执行任务→返回结果”的流程。 -
执行代码,提交任务:
python distributed_spider.py提交成功后,终端会输出任务提交信息,显示每个URL的任务ID;同时,所有Worker节点的celery_worker.log日志中,会显示“获取任务→执行任务→返回结果”的流程。 -
查看任务执行情况:
可通过以下两种方式查看:-
查看Worker节点日志:tail -f celery_worker.log,实时查看任务执行结果;
-
查看Redis中的任务和结果:通过redis-cli连接Redis,查看第0库(任务队列)和第1库(结果存储)。
-
-
查看任务执行情况:
可通过以下两种方式查看:
实测效果:提交10个URL任务,3台Worker节点并行执行,仅用10秒左右就完成了所有爬取任务,而单机爬虫需要40秒左右,效率提升3倍以上;且中途关闭其中一台Worker节点,其他节点会自动接管未完成的任务,无任务丢失。
七、监控与运维(实战必备,避免集群失控)
集群部署完成后,必须做好监控和运维,否则出现节点崩溃、任务失败等问题,无法及时发现和排查。本文推荐使用Flower(Celery官方监控工具),轻量、易部署,可实时监控集群状态、任务执行情况。
7.1 Flower监控部署(调度节点执行)
Flower已在环境准备阶段安装完成,直接启动即可:
# 激活虚拟环境
source /home/spider/env/pyspider-celery-env/bin/activate
# 启动Flower(默认端口5555,可指定端口)
# 若需要设置密码(避免他人访问),添加--basic-auth=用户名:密码
nohup celery -A distributed_spider flower --port=5555 --basic-auth=admin:123456 > flower.log 2>&1
启动成功后,访问http://调度节点IP:5555启动成功后,访问http://调度节点IP:5555,输入设置的用户名(admin)和密码(123456),即可进入Flower监控界面,核心功能实操如下(实测重点):
-
集群节点状态监控(核心功能):
进入界面后,左侧「Workers」菜单可查看所有Worker节点的状态,包括节点IP、hostname、在线状态、并发数、已执行任务数、失败任务数。实操重点:若某台Worker节点显示「Offline」,需登录该节点查看celery_worker.log日志,大概率是Redis连接失败、进程崩溃或代码不一致;若节点显示「Online」但无任务执行,检查该节点并发数配置或任务队列是否有任务。避坑点:Flower监控存在1-2秒延迟,若刚启动Worker节点,无需立即刷新,等待3秒左右再查看,避免误判节点离线。 -
任务执行详情监控:
左侧「Tasks」菜单可查看所有任务的执行情况,支持按任务状态(成功、失败、等待、执行中)筛选,点击单个任务ID,可查看任务详情:执行节点、提交时间、开始时间、结束时间、任务参数(URL)、执行结果/错误信息。实操重点:若有任务显示「Failed」,点击任务ID查看「Exception」字段,可快速定位失败原因(如URL无法访问、解析报错、Redis认证失败),无需逐台节点查看日志,大幅提升排查效率。优化技巧:可通过顶部「Filter」筛选失败任务,批量导出失败URL,后续可重新提交爬取,减少数据遗漏。 -
集群负载监控:
左侧「Monitor」菜单可查看集群的实时负载,包括任务执行速率、节点CPU/内存占用(需节点安装psutil依赖:pip install psutil)、任务队列长度。实操重点:若某台节点CPU占用持续超过80%,需降低该节点的并发数(修改Celery配置worker_concurrency或启动Worker时指定-c参数);若任务队列长度持续增加,说明Worker节点数量不足,可新增Worker节点分担压力。 -
Flower运维注意事项:
1. 生产环境建议给Flower设置密码(–basic-auth参数),避免暴露集群信息;2. Flower默认端口5555,若服务器防火墙未开放,需执行命令开放端口(Linux:sudo ufw allow 5555);3. 后台运行Flower后,若需停止,执行命令:ps aux | grep flower,找到进程ID后,kill -9 进程ID。
7.2 任务结果查询(实战必备,精准获取爬取数据)
任务执行完成后,结果会存储在Redis的Backend(本文配置的是第1库),可通过自定义脚本批量查询或单个查询,以下是实测可用的query_result.py脚本,支持单个任务ID查询和批量查询所有结果,直接复用即可。
7.2.1 编写结果查询脚本(调度节点执行)
在调度节点的代码目录(/home/spider/code/)创建query_result.py文件,代码如下,包含详细注释:
from distributed_spider import app # 导入Celery实例(与分布式核心代码一致)
from celery.result import AsyncResult
import redis
import json
# 初始化Redis连接(与distributed_spider.py中的配置完全一致)
redis_client = redis.Redis(
host='192.168.1.100',
port=6379,
password='123456',
db=1, # 对应Celery的Backend库(第1库)
decode_responses=True
)
def get_single_task_result(task_id):
"""
查询单个任务的结果(适合排查单个URL爬取失败问题)
:param task_id: 任务ID(提交任务时终端打印的ID)
:return: 格式化后的任务结果
"""
try:
result = AsyncResult(task_id, app=app)
# 解析任务状态和结果
if result.successful():
task_result = {
"任务ID": task_id,
"执行状态": "成功",
"执行节点": result.result.get("worker_node", ""),
"目标URL": result.result.get("url", ""),
"爬取时间": result.result.get("crawl_time", ""),
"爬取结果": result.result # 完整结果(可根据需求提取字段)
}
elif result.failed():
task_result = {
"任务ID": task_id,
"执行状态": "失败",
"错误信息": str(result.result), # 失败原因
"重试次数": result.request.retries
}
else:
task_result = {
"任务ID": task_id,
"执行状态": "执行中/等待中",
"当前重试次数": result.request.retries
}
return task_result
except Exception as e:
return {"任务ID": task_id, "执行状态": "查询失败", "错误信息": str(e)}
def get_all_tasks_result(save_to_file=False):
"""
批量查询所有任务的结果(适合汇总所有爬取数据)
:param save_to_file: 是否将结果保存到本地文件(默认False,建议大量数据时设为True)
:return: 任务结果列表、成功数、失败数
"""
# 从Redis中获取所有任务ID(Celery默认的任务ID存储key格式:celery-task-meta-*)
task_keys = redis_client.keys("celery-task-meta-*")
if not task_keys:
print("暂无任务结果可查询")
return [], 0, 0
all_results = []
success_count = 0
fail_count = 0
print(f"开始查询所有任务结果,共{len(task_keys)}个任务")
for key in task_keys:
# 提取任务ID(截取key的后缀:celery-task-meta-xxxx → xxxx)
task_id = key.split("-")[-1]
result = get_single_task_result(task_id)
all_results.append(result)
if result["执行状态"] == "成功":
success_count += 1
elif result["执行状态"] == "失败":
fail_count += 1
print(f"查询完成:成功{success_count}个,失败{fail_count}个,执行中/等待中{len(all_results)-success_count-fail_count}个")
# 保存结果到本地文件(便于后续数据处理)
if save_to_file:
with open("task_results.json", "w", encoding="utf-8") as f:
json.dump(all_results, f, ensure_ascii=False, indent=4)
print(f"任务结果已保存到当前目录的task_results.json文件中")
return all_results, success_count, fail_count
# 测试入口(可直接执行脚本,根据需求修改)
if __name__ == '__main__':
# 1. 单个任务ID查询(替换为实际的任务ID,提交任务时终端会打印)
single_task_id = "a1b2c3d4-xxxx-xxxx-xxxx-1234567890ab"
single_result = get_single_task_result(single_task_id)
print("单个任务查询结果:")
print(json.dumps(single_result, ensure_ascii=False, indent=4))
# 2. 批量查询所有任务结果,并保存到文件
# all_results, success_num, fail_num = get_all_tasks_result(save_to_file=True)
# 打印前5个任务结果(避免输出过多)
# print("前5个任务结果:")
# for res in all_results[:5]:
# print(json.dumps(res, ensure_ascii=False, indent=2))
7.2.2 脚本使用步骤(实测可运行)
-
确保query_result.py与distributed_spider.py在同一目录,且Redis配置一致;
-
激活虚拟环境,执行脚本:
source /home/spider/env/pyspider-celery-env/bin/activate cd /home/spider/code/ python query_result.py -
根据需求修改测试入口:
- 单个任务查询:替换single_task_id为实际的任务ID(提交任务时终端打印的ID);- 批量查询:注释单个查询代码,取消注释批量查询代码,save_to_file=True可将结果保存为JSON文件,便于后续导入数据库或数据分析。
避坑点:若查询时出现“KeyError: ‘worker_node’”,说明该任务执行失败(未返回worker_node字段),可通过get_single_task_result查询该任务的失败原因,针对性解决后重新提交。
7.3 集群日常运维技巧(实测总结)
分布式集群部署后,日常运维重点是“保稳定、快排查、防丢失”,结合笔者项目运维经验,总结3个核心技巧:
-
任务失败重试机制优化:
除了Celery自带的重试,可在调度节点添加定时脚本,每天查询失败任务,自动重新提交,避免手动干预:可基于query_result.py修改,筛选出失败任务的URL,调用batch_submit_tasks函数重新提交。 -
日志管理:
Worker节点和Flower的日志会持续增大,需设置日志切割(Linux可使用logrotate),避免占用过多磁盘空间;同时,将关键日志(失败任务、节点离线)输出到指定文件,便于快速检索。 -
节点容错:
若Worker节点故障(如服务器宕机),重启节点后,Celery Worker会自动重新连接Redis,获取未执行的任务,无需手动提交;生产环境建议给调度节点和Redis节点配置开机自启,避免服务器重启后集群无法正常运行。
八、实战避坑(新手必看,实测踩坑实录)
前文各章节虽零散提到一些避坑点,但结合笔者实际部署和运维经历,整理了10个新手最容易踩的坑,每个坑都对应真实场景和可直接解决的方案,覆盖环境配置、代码改造、集群部署、运维全流程,帮你少走弯路、快速排错。
-
**坑1:Python版本与PySpider版本不兼容(最常见)**现象:安装PySpider后,启动单机爬虫或Worker节点时,出现ImportError: cannot import name ‘xxx’ from ‘pyspider.libs’。原因:PySpider 0.3.10版本(实测最稳定)仅支持Python3.7,Python3.8及以上版本会出现依赖包不兼容问题。解决方案:所有节点统一安装Python3.7,不要使用系统默认的Python版本;创建虚拟环境时,指定使用Python3.7(python3.7 -m venv 虚拟环境名称)。
-
坑2:Redis远程访问失败现象:Worker节点启动后,日志显示ConnectionRefusedError: [Errno 111] Connection refused,无法连接到Redis。原因:Redis未修改bind配置、未关闭保护模式,或调度节点防火墙未开放6379端口;部分场景是Redis密码写错。解决方案:严格按照3.3节配置Redis,bind设为0.0.0.0,protected-mode设为no;开放6379端口(Linux:sudo ufw allow 6379);核对所有节点代码中的Redis密码,确保一致。
-
坑3:任务分发不均,部分Worker节点无任务现象:提交任务后,部分Worker节点持续有任务执行,部分节点显示Online但无任务,任务队列长度持续增加。原因:Celery默认的worker_prefetch_multiplier=4(每次获取4个任务),若某台Worker节点配置高、并发高,会抢完大部分任务,导致其他节点无任务可执行。解决方案:在Celery配置中设置worker_prefetch_multiplier=1,让每个Worker节点每次只获取1个任务,执行完成后再获取下一个,实现任务均匀分发。
-
坑4:Fetch模块初始化位置错误,导致Worker崩溃现象:Worker节点启动后,执行几个任务就崩溃,日志显示MemoryError或Too many open files。原因:将Fetch模块的初始化(fetch = Fetch())放在了Celery任务内部,导致每个任务都创建一个Fetch实例,占用大量内存和文件句柄。解决方案:将fetch = Fetch()放在Celery任务外部,全局仅初始化一次,所有任务共用一个Fetch实例(参考5.2节的分布式核心代码)。
-
坑5:URL去重失效,出现重复爬取现象:多个Worker节点爬取同一个URL,导致资源浪费,甚至被目标站点反爬。原因:Redis连接时未指定正确的db(URL去重使用第2库),或代码中CRAWLED_URL_SET变量名称不一致;部分场景是提交任务前未做双重去重。解决方案:核对redis_client的db配置,确保URL去重使用第2库;所有节点代码中的CRAWLED_URL_SET变量保持一致;严格执行“提交任务前+执行任务前”双重去重(参考5.2节代码)。
-
坑6:任务提交后,Worker节点无法获取任务现象:调度节点提交任务成功,Redis的Broker队列(第0库)有任务,但Worker节点日志无任务执行记录。原因:Celery的broker配置错误(如Redis密码遗漏冒号、IP错误);或Worker节点的代码与调度节点不一致;或任务名称不匹配(@app.task(name=‘crawl_url_task’)需保持一致)。解决方案:逐行核对所有节点代码中的Celery配置(broker、backend);确保所有节点的distributed_spider.py完全一致;核对任务名称,确保提交的任务名称与Worker节点的任务名称一致。
-
坑7:Flower无法访问现象:启动Flower后,访问http://调度节点IP:5555无法打开页面,显示“无法访问此网站”。原因:调度节点防火墙未开放5555端口;或Flower启动时未指定正确的Celery应用模块;或端口被其他进程占用。解决方案:开放5555端口;启动Flower时,确保-A参数指定的是distributed_spider(celery -A distributed_spider flower);检查端口占用(netstat -ano | findstr 5555),杀死占用进程后重新启动Flower。
-
坑8:任务执行完成后,结果查询不到现象:Worker节点日志显示任务执行成功,但通过query_result.py查询不到结果,或查询显示“执行中”。原因:Celery的backend配置错误(如Redis库错误、密码错误);或任务执行时间过长,Celery默认的结果过期时间已到(默认1天)。解决方案:核对backend配置,确保与Redis的第1库一致;若任务执行时间长,在Celery配置中添加result_expires=3600247(设置结果保存7天)。
-
坑9:Windows Worker节点启动后,关闭终端即停止现象:Windows节点启动Worker后,关闭命令行终端,Worker进程立即停止,无法继续执行任务。原因:Windows系统不支持nohup命令,直接启动Worker进程会绑定终端,终端关闭则进程停止。解决方案:使用Windows“任务计划程序”,创建定时任务,设置开机自启,启动命令为虚拟环境的activate命令+Worker启动命令;或使用第三方工具(如PM2 for Windows)后台运行Worker进程。
-
坑10:集群运行一段时间后,Worker节点陆续离线现象:集群初期运行正常,几天后部分Worker节点显示Offline,日志无明显错误。原因:服务器内存不足(Worker节点并发过高,占用过多内存);或Redis连接超时,未配置重连机制。解决方案:降低Worker节点的并发数(1核2G服务器建议设为2);在Redis配置中添加timeout=300(连接超时时间5分钟),Celery配置中添加broker_connection_retry_on_startup=True(启动时自动重连Redis)。
九、总结(实战落地核心要点)
本文从真实项目痛点出发,完整拆解了从单机PySpider爬虫到Celery分布式集群的落地流程,全程围绕“轻量级、快速落地、实战可用”的核心,避免空谈理论,所有代码、步骤、避坑点均经过生产环境实测,适合中小规模数据爬取场景、有一定Python爬虫基础的开发者。
梳理核心要点,帮你快速抓住落地关键,避免走弯路:
-
核心流程闭环:先跑通单机PySpider(验证请求+解析逻辑)→ 封装Celery任务(剥离PySpider调度模块)→ 配置Redis(消息队列+结果存储+URL去重)→ 启动Worker集群 → 提交任务 → 监控运维,一步都不能少,先保证单机可用,再进行分布式改造。
-
关键配置重点:所有节点环境(Python、依赖版本)必须一致;Redis远程访问配置是集群通信的核心;Celery的并发数、重试机制、任务分发配置,直接影响集群效率和稳定性;URL双重去重是分布式爬虫的必备优化。
-
避坑核心原则:遇到问题先查日志(Worker日志、Flower日志、Redis日志),大部分问题可通过日志定位;优先保证“简单可用”,不要盲目追求复杂配置(如初期无需配置集群容错,先实现任务分发和结果查询);新手建议从1台调度机+1台Worker机开始测试,再逐步增加Worker节点。
-
场景适配建议:本文方案适合30万-100万条数据的爬取场景,1台调度机+2-3台Worker机即可满足需求;若需爬取千万级以上数据,可优化两点:一是将Redis替换为RabbitMQ(提升任务分发效率),二是添加代理IP池(避免反爬),同时增加Worker节点数量。
最后,笔者想说:分布式爬虫的核心不是“多节点”,而是“高效、稳定、可运维”,本文方案的优势在于低改造成本、低运维成本,新手可直接复用代码,1-2天即可完成从单机到集群的落地。实际项目中,可根据目标站点的反爬策略、数据量大小,灵活调整代码和集群配置,祝大家都能快速落地分布式爬虫,高效获取目标数据。









