服务端阅读 05月31日 01:07
Scrapy 分布式爬虫如何用 Redis 稳定实现?
Scrapy 本身是单进程内的爬虫框架,要做分布式,关键是把“请求队列”和“去重集合”从本地内存搬到所有节点都能访问的地方。最常见方案是 scrapy-redis:多个 spider 实例共享 Redis 里的 request queue 和 dupefilter set,每个节点从同一个队列取任务,处理完再把新请求推回队列。这套方案的好处是改造成本低,单机 Scrapy 项目不用重写成全新的调度系统。代价是 Redis 变成核心依赖,网络抖动、队列堆积、去重 key 设计不当都会影响全局吞吐。分布式爬虫不是“机器越多越快”,目标站限速、代理质量、数据库写入能力、队列序列化开销都会成为边界。# settings.pySCHEDULER = 'scrapy_redis.scheduler.Scheduler'DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'SCHEDULER_PERSIST = TrueREDIS_URL = 'redis://:password@127.0.0.1:6379/0'ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300,}from scrapy_redis.spiders import RedisSpiderclass ProductSpider(RedisSpider): name = 'product' redis_key = 'product:start_urls' def parse(self, response): yield {'url': response.url, 'title': response.css('h1::text').get()}启动后向 Redis 写入入口地址即可:redis-cli lpush product:start_urls https://example.com/list追问scrapy-redis 到底替换了 Scrapy 的哪些部分?它主要替换调度器和去重器,让请求不再只存在本地内存队列中,而是序列化后放进 Redis。这样多个进程甚至多台机器可以消费同一个任务池,宕机后也能继续从队列恢复。取舍是调度性能会受 Redis 网络和序列化影响,小规模任务未必比单机更快。边界是下载器、解析函数、pipeline 仍然是每个节点本地执行,scrapy-redis 不会自动帮你解决数据库写入冲突。分布式爬虫怎么做去重才可靠?默认 RFPDupeFilter 会根据请求指纹去重,通常由 URL、方法、请求体等信息计算而来。它适合大多数 GET 页面,但对带时间戳、分页参数顺序混乱、POST body 动态变化的请求可能误判或漏判。踩坑点是把 session、随机参数也算进指纹,队列看似很忙,实际一直重复抓同一批内容。实践中可以自定义 request_fingerprint,把业务上无意义的参数过滤掉。机器加多了为什么速度没有提升?爬虫吞吐由最慢的一环决定,可能是目标站限流,也可能是代理池、DNS、Redis、数据库或解析 CPU。机器数量增加后,请求会更集中,目标站更容易返回 403、验证码或空页面。取舍是追求速度会牺牲稳定性,尤其是电商、招聘、内容站这类反爬较强的网站。比较稳的做法是先压测单节点瓶颈,再逐步扩容,同时观察失败率而不是只看 QPS。Redis 队列要不要持久化?SCHEDULER_PERSIST=True 能让爬虫停止后保留队列和去重集合,适合长周期任务和断点续爬。缺点是调试时容易“幽灵任务”反复出现,明明代码改了,旧请求还在 Redis 里继续跑。边界是一次性任务或测试环境可以关闭持久化,避免污染结果。生产环境则要配合 key 命名、过期策略和清理脚本,不然 Redis 内存会慢慢被历史指纹吃掉。分布式下数据入库有什么坑?多个节点同时写同一张表时,最常见问题是重复数据、唯一键冲突和写入压力抖动。去重不能只依赖请求队列,因为同一个商品可能从不同列表页进入,最终还是要在数据库层设计唯一约束。取舍是强一致写入会降低吞吐,批量异步写入速度快但失败重试更复杂。边界做法是 pipeline 里尽量幂等:同一条 item 重复提交,结果也应该是更新或忽略,而不是产生脏数据。