当前位置: 首页 > news >正文

踩过100+坑后,我终于搞懂了Redis+Scrapy分布式爬虫的核心原理


做爬虫这行快8年了,从最开始的单线程requests,到后来的多线程threading,再到现在的Scrapy框架,我算是把爬虫的各种坑都踩了个遍。

前阵子接了个需求,要爬取某电商平台1000万+商品数据,要求7天内完成。一开始我用单机Scrapy跑,速度慢得像蜗牛,一天才爬20万条,照这速度得跑50天。客户那边催得紧,我只能硬着头皮上分布式。

结果这一搞,又踩了无数坑。Redis连接超时、去重失效、任务重复执行、数据丢失、节点负载不均… 整整折腾了一周,才把整个系统调通。今天就把这些血泪经验分享出来,让大家少走点弯路。

为什么要做分布式爬虫?

很多人觉得单机爬虫够用了,没必要搞分布式。这话没错,但仅限于小数据量场景。当你需要爬取百万级甚至千万级数据时,单机的瓶颈就非常明显了。

我给大家算笔账:假设一个请求从发送到接收响应需要1秒,单机Scrapy开100个并发,一天能爬多少条?

100个并发 × 3600秒 × 24小时 = 8,640,000条

看起来不少,但实际情况远没有这么理想。网络延迟、反爬限制、页面解析时间、数据库写入速度… 这些都会严重影响实际爬取速度。我之前那个电商项目,单机实际速度只有理论值的1/40,一天才20万条。

而且单机爬虫还有个致命问题:单点故障。一旦机器宕机,所有任务都得从头开始。我就遇到过一次,爬了3天的数据,因为服务器断电全没了,当时想死的心都有。

分布式爬虫就完美解决了这些问题:

  • 水平扩展:加机器就能线性提升爬取速度
  • 高可用:一个节点挂了,其他节点继续工作
  • 负载均衡:任务自动分配到各个节点
  • 断点续爬:任务状态持久化到Redis,随时可以暂停和恢复

Redis+Scrapy分布式架构详解

Scrapy本身是不支持分布式的,它的调度器和去重器都是基于内存的。要实现分布式,我们需要把这两个核心组件替换成基于Redis的实现。

这是我最终落地的分布式爬虫架构图:

种子URL

Redis队列

爬虫节点1

爬虫节点2

爬虫节点N

数据管道

MySQL/PostgreSQL

MongoDB

Elasticsearch

Redis去重集合

监控面板

整个架构非常清晰,核心就是Redis作为中间件,负责存储任务队列和去重集合。所有爬虫节点都从同一个Redis队列中获取任务,爬取完成后将数据写入统一的数据存储。

核心组件说明

  1. Redis任务队列:存储待爬取的URL,使用List数据结构,实现先进先出的队列。
  2. Redis去重集合:存储已经爬取过的URL指纹,使用Set数据结构,保证O(1)时间复杂度的去重。
  3. 爬虫节点:运行Scrapy的服务器,可以是物理机、虚拟机或容器,数量可以根据需求动态调整。
  4. 数据管道:统一处理爬取到的数据,进行清洗、验证和存储。
  5. 监控面板:实时监控Redis队列长度、去重数量、各节点爬取速度等指标。

从零开始搭建分布式爬虫

环境准备

首先,你需要安装以下软件:

  • Python 3.8+
  • Scrapy 2.5+
  • Redis 6.0+
  • scrapy-redis 0.7.2+

安装命令很简单:

pipinstallscrapy scrapy-redis redis

这里要特别注意版本问题。我之前踩过一个大坑,scrapy-redis 0.6.x版本和Scrapy 2.5+不兼容,会出现各种奇怪的错误。一定要用0.7.2及以上版本。

第一步:创建Scrapy项目

scrapy startproject distributed_spidercddistributed_spider scrapy genspider example example.com

第二步:修改settings.py配置

这是最关键的一步,很多人分布式爬虫跑不起来,都是因为配置错了。

# 启用Redis调度器SCHEDULER="scrapy_redis.scheduler.Scheduler"# 启用Redis去重器DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"# 允许暂停和恢复爬取SCHEDULER_PERSIST=True# Redis连接配置REDIS_URL='redis://:your_password@127.0.0.1:6379/0'# 每个请求的优先级SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.PriorityQueue'# 最大空闲时间,防止爬虫退出SCHEDULER_IDLE_BEFORE_CLOSE=10# 并发请求数CONCURRENT_REQUESTS=100# 下载延迟DOWNLOAD_DELAY=0.5# 禁用cookiesCOOKIES_ENABLED=False# 管道配置ITEM_PIPELINES={'scrapy_redis.pipelines.RedisPipeline':300,'distributed_spider.pipelines.MySQLPipeline':400,}

这里有几个配置我要重点说一下:

  • SCHEDULER_PERSIST = True:这个一定要开,否则爬虫停止后,Redis中的任务队列和去重集合会被清空。我第一次没开,爬了一半重启爬虫,结果所有任务都没了。
  • SCHEDULER_QUEUE_CLASS:默认是PriorityQueue,根据请求的优先级排序。如果你不需要优先级,可以用FifoQueue,性能更好。
  • SCHEDULER_IDLE_BEFORE_CLOSE:当Redis队列为空时,爬虫会等待多少秒后退出。建议设置一个比较大的值,防止因为网络延迟导致队列暂时为空而退出。

第三步:修改Spider代码

原来的Spider需要继承自RedisSpider,而不是原来的scrapy.Spider

importscrapyfromscrapy_redis.spidersimportRedisSpiderclassExampleSpider(RedisSpider):name='example'allowed_domains=['example.com']# 注释掉start_urls,改为从Redis获取# start_urls = ['http://example.com/']# Redis键,用于存储种子URLredis_key='example:start_urls'defparse(self,response):# 解析页面内容title=response.xpath('//title/text()').get()yield{'title':title,'url':response.url,}# 提取下一页链接next_page=response.xpath('//a[@class="next"]/@href').get()ifnext_page:yieldresponse.follow(next_page,self.parse)

就是这么简单!原来的解析逻辑完全不用改,只需要把父类换成RedisSpider,然后注释掉start_urls,添加redis_key即可。

第四步:启动Redis服务

redis-server /etc/redis/redis.conf

一定要给Redis设置密码,并且只允许本地访问,否则你的Redis会被黑客攻击。我就见过有人把Redis暴露在公网上,结果被人挖矿了。

第五步:启动爬虫节点

在每台服务器上都执行:

scrapy crawl example

这时候你会看到爬虫启动了,但什么都没做,一直在等待任务。

第六步:推送种子URL到Redis

打开另一个终端,连接Redis:

redis-cli-ayour_password

然后推送种子URL:

lpush example:start_urls"http://example.com/"

这时候你会看到所有爬虫节点都开始工作了!

踩过的那些坑和解决方案

坑1:Redis连接超时

这是最常见的问题,尤其是在高并发场景下。

错误信息

redis.exceptions.ConnectionError: Error 110 connecting to redis:6379. Connection timed out.

解决方案

  1. 增加Redis连接池大小:
REDIS_PARAMS={'socket_timeout':30,'socket_connect_timeout':30,'retry_on_timeout':True,'max_connections':1000,}
  1. 优化Redis配置:
# redis.conf maxclients 10000 tcp-keepalive 300 timeout 0
  1. 使用Redis集群:如果单台Redis性能不够,可以考虑使用Redis集群。

坑2:去重失效,任务重复执行

这个问题非常隐蔽,很多人都遇到过,但不知道为什么。

原因:scrapy-redis默认使用请求的URL、method、body和headers来生成指纹。但如果你的请求中有随机参数或者时间戳,每次生成的指纹都不一样,就会导致去重失效。

解决方案:自定义去重规则,只使用URL的核心部分生成指纹。

fromscrapy_redis.dupefilterimportRFPDupeFilterimporthashlibclassCustomDupeFilter(RFPDupeFilter):defrequest_fingerprint(self,request):# 只使用URL的scheme、netloc和path生成指纹url_parts=request.url.split('?')[0]fp=hashlib.sha1(url_parts.encode('utf-8')).hexdigest()returnfp

然后在settings.py中配置:

DUPEFILTER_CLASS='distributed_spider.dupefilters.CustomDupeFilter'

坑3:数据丢失

当爬虫节点突然宕机时,正在处理的任务会丢失。

原因:scrapy-redis默认使用的是List结构,当一个节点从List中pop出一个任务后,这个任务就从Redis中删除了。如果节点在处理这个任务的过程中宕机,这个任务就永远丢失了。

解决方案:使用SortedSet实现可靠队列。

scrapy-redis提供了一个SortedSetQueue,它会把正在处理的任务放到一个临时集合中,只有当任务处理完成后才会从临时集合中删除。如果节点宕机,临时集合中的任务会被其他节点重新处理。

SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SortedSetQueue'

不过这个队列的性能比List差一些,你需要在可靠性和性能之间做权衡。

坑4:节点负载不均

有时候会出现一个节点非常忙,其他节点很闲的情况。

原因:scrapy-redis默认使用的是抢占式任务分配方式,哪个节点处理得快,哪个节点就会抢到更多的任务。如果某个节点的性能特别好,它就会抢到大部分任务。

解决方案

  1. 限制每个节点的并发数:
CONCURRENT_REQUESTS=50
  1. 使用轮询式任务分配:
    你可以自己实现一个调度器,采用轮询的方式把任务分配给各个节点。不过这个比较复杂,一般情况下限制并发数就够了。

坑5:Redis内存不足

当爬取的数据量非常大时,Redis的内存会很快被占满。

解决方案

  1. 定期清理Redis中的数据:
# 只保留最近7天的去重数据DUPEFILTER_EXPIRE=60*60*24*7
  1. 使用Redis的RDB和AOF持久化:
# redis.conf save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec
  1. 把数据尽快从Redis转移到数据库:
    不要让数据在Redis中停留太久,爬取完成后立即写入MySQL或MongoDB。

性能优化技巧

1. 优化Scrapy配置

# 增加并发数CONCURRENT_REQUESTS=200# 减少下载延迟DOWNLOAD_DELAY=0.1# 禁用重定向REDIRECT_ENABLED=False# 禁用重试RETRY_ENABLED=False# 增加DNS缓存DNSCACHE_ENABLED=TrueDNSCACHE_SIZE=10000

2. 使用异步数据库驱动

不要使用同步的数据库驱动,否则数据库写入会成为瓶颈。

  • MySQL:使用aiomysql
  • PostgreSQL:使用asyncpg
  • MongoDB:使用motor

3. 开启HTTP连接池

DOWNLOADER_HTTPCLIENTFACTORY='scrapy.core.downloader.webclient.ScrapyHTTPClientFactory'DOWNLOADER_CLIENTCONTEXTFACTORY='scrapy.core.downloader.contextfactory.ScrapyClientContextFactory'

4. 使用代理池

分布式爬虫很容易被封IP,一定要使用代理池。我推荐使用scrapy-proxies或者自己搭建一个代理池。

DOWNLOADER_MIDDLEWARES={'scrapy_proxies.RandomProxy':100,'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware':110,}PROXY_LIST='/path/to/proxy/list.txt'PROXY_MODE=0

监控与运维

一个好的分布式爬虫系统,必须要有完善的监控和运维机制。

我自己写了一个简单的监控脚本,可以实时查看Redis队列长度、去重数量、爬取速度等指标:

importredisimporttime r=redis.Redis(host='127.0.0.1',port=6379,password='your_password',db=0)defmonitor():whileTrue:queue_len=r.llen('example:requests')dupe_count=r.scard('example:dupefilter')item_count=r.llen('example:items')print(f"队列长度:{queue_len}")print(f"去重数量:{dupe_count}")print(f"已爬取数量:{item_count}")print("-"*50)time.sleep(5)if__name__=='__main__':monitor()

如果需要更专业的监控,可以使用Prometheus+Grafana。

总结

Redis+Scrapy是目前最流行的分布式爬虫解决方案,它简单易用,性能强大,能够满足绝大多数爬虫需求。

但要真正用好它,你需要深入理解它的原理,并且踩过足够多的坑。我这篇文章里提到的问题,都是我在实际项目中遇到过的,每一个解决方案都是用无数个通宵换来的。

最后给大家一个建议:不要一开始就追求完美的分布式架构。先从单机开始,当单机性能不够时,再逐步升级到分布式。很多时候,优化单机爬虫的性能,比盲目上分布式更有效。

http://www.jsqmd.com/news/878240/

相关文章:

  • 【技术专题】Reloaded-II依赖循环与无限下载问题的系统性解决方案
  • Windows热键冲突终极解决方案:Hotkey Detective精准定位占用程序
  • MacType 2025:终极Windows字体渲染优化指南,告别模糊文字困扰!
  • 初次使用 Taotoken 的 API Key 管理与访问控制功能体验
  • Postman便携版终极指南:免安装API开发神器快速上手
  • Hermes Agent工具接入Taotoken作为自定义模型源详细步骤
  • 3大止损策略拯救你的交易:backtrader实战指南(附代码模板)
  • TestDisk与PhotoRec:数据丢失救星的终极恢复指南
  • 终极指南:如何为Axure RP 11快速安装中文语言包
  • 10分钟掌握AI智能分层:LayerDivider让插画编辑变得简单高效
  • AI简史:从1950到2026,科学界的人类群星闪耀时
  • 如何通过SPT-AKI Profile Editor存档编辑器轻松掌控你的塔科夫离线体验
  • 如何实现企业级HTML转Word文档转换,提升80%文档处理效率
  • 从POC到生产环境:DeepSeek模型安全加固实战手记(附17个真实攻防对抗日志片段)
  • 企业内如何实现AI API调用的统一管理与审计
  • 明日方舟游戏素材资源库:创作者与开发者的数字宝藏
  • Windows上安装安卓应用的终极解决方案:APK安装器完整指南
  • sqlmap实战精要:从靶场验证到WAF绕过与盲注攻坚
  • 如何为智能电视选择最佳浏览器:TV Bro的完整使用指南
  • 对接焊缝的坡口形式
  • scTenifoldXct:基于流形对齐与基因调控网络的细胞通讯分析新方法
  • 初次使用 Taotoken 的开发者如何快速查看用量与控制成本
  • C51变量固定内存地址定位的3种方法与实践
  • 为Hermes Agent自定义模型供应商并接入Taotoken服务
  • Java开发者如何快速接入Taotoken实现多模型调用
  • 2026年西安本地合规防水补漏服务机构3家深度梳理与场景适配分析 苏州防水补漏维修公司靠谱品牌排名 - 冠盾建筑修缮
  • 保姆级教程:在Ubuntu 22.04上搞定LIBERO机器人学习环境(含Robosuite配置避坑)
  • 通过curl命令直接测试Taotoken接口连通性与模型响应速度
  • 2026年下半年苏州哪里找靠谱的GEO服务商,强烈推荐聚合AI GEO - 资讯纵览
  • 老旧建筑HVAC节能改造:基于ML-MPC物联网框架的实践