Scrapy 分布式爬虫基于阿里云 Redis 集群实战:反向海淘 Taocarts 商品采集日 10 万数据踩坑全复盘

简介: Taocarts团队为解决单机Scrapy爬虫吞吐低、易丢任务、OOM、难扩容等痛点,基于阿里云Redis集群重构分布式架构:多ECS协同抓取、断点续爬、全局去重、合规限流(15req/min/IP),集成RDS+OSS+MaxCompute,日采达10.6万条,任务丢失率降至0.03%,成本降38%。

一、业务场景与原生单机爬虫痛点

我所在团队负责 Taocarts 反向海淘代购系统商品采集模块,业务需求是每日合规采集 1688、淘宝零售商品基础数据,同步至 Bidfins 跨境前端展示,初期使用单机 Scrapy 爬虫上线后暴露出大量不可控线上问题。

单机架构存在四大致命短板:第一,单进程抓取上限仅 8000 条 / 天,无法支撑大促 10 万 + 日采集需求;第二,任务无持久化,服务器重启、进程崩溃后全部待爬 URL 丢失,只能全量重爬,浪费接口调用成本;第三,去重基于内存 Set,单机内存超过 16G 频繁 OOM,大量重复请求触发平台风控 IP 封禁;第四,无法横向扩容,业务高峰期只能临时换高配 ECS,资源利用率不足 20%,云成本极高。

初期尝试自建单机 Redis 做任务存储,但无集群主从同步、无持久化策略,Redis 宕机后采集任务全部丢失,多次造成商品价格、库存同步滞后,引发前端超卖投诉。经过两周重构,基于阿里云 Redis 集群版搭建分布式爬虫架构,实现多 ECS 节点协同抓取、任务断点续爬、分布式全局 URL 去重,稳定支撑日 10 万商品采集,下文完整拆解架构、可运行代码、线上踩坑与阿里云优化方案。

二、分布式爬虫整体架构拆解

整体四层架构:采集层(多台阿里云 ECS 部署 Scrapy 节点)、调度层(阿里云 Redis 集群 6.0)、存储层(阿里云 RDS MySQL + OSS)、计算层(MaxCompute 离线清洗)。

  1. Redis 集群职责:存储待爬任务队列、分布式指纹去重、请求限流计数器、代理 IP 池状态缓存;
  2. ECS 节点:多台轻量应用服务器横向扩容,无状态,可随时弹性增减;
  3. 合规约束:单 IP 每分钟请求≤15 次,搭配代理池轮换,所有采集仅用于自有代购业务,不对外分发数据,符合平台数据合规要求。

2.1 阿里云 Redis 集群关键配置要点

开通阿里云 Redis 集群版,选择 2 主 2 从架构,开启 RDB+AOF 双重持久化,开启集群读写分离,连接地址使用内网 VPC 地址,避免公网延迟;安全组仅开放 ECS 内网 IP 访问,RAM 子账号分配 Redis 只读 / 读写细分权限,杜绝全量权限泄露。

三、完整可运行工程代码(Python3.9 + scrapy-redis 0.8.0)

3.1 settings.py 分布式 Redis 核心配置(对接阿里云内网 Redis)

# 分布式爬虫开启
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 任务持久化,重启不丢失
SCHEDULER_PERSIST = True
# 阿里云Redis集群内网连接地址,替换为实例内网端点
REDIS_URL = "redis://:xxxx@r-xxxx.redis.rds.aliyuncs.com:6379/0"
# 爬虫并发、限流配置(合规采集核心)
CONCURRENT_REQUESTS = 12
DOWNLOAD_DELAY = 4
RANDOMIZE_DOWNLOAD_DELAY = True
# 全局请求限速,Redis计数器控制单IP 15次/分钟
DOWNLOADER_MIDDLEWARES = {
    'taocarts_spider.middlewares.RateLimitMiddleware': 543,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
    'scrapy_redis.downloadmiddleware.RedisRetryMiddleware': 560,
}
# 数据入库Pipeline:RDS MySQL + OSS图片存储
ITEM_PIPELINES = {
    'taocarts_spider.pipelines.MysqlPipeline': 300,
    'taocarts_spider.pipelines.OssImgPipeline': 350,
}
# 关闭本地文件缓存,全部缓存至Redis
HTTPCACHE_ENABLED = False

3.2 自定义限流中间件 RateLimitMiddleware.py(Redis 计数器限流,规避风控封禁)

import redis
import time
from scrapy.http import HtmlResponse
from scrapy.utils.response import response_status_message
from scrapy_redis import get_redis_from_settings

class RateLimitMiddleware:
    def __init__(self, settings):
        self.redis = get_redis_from_settings(settings)
        self.rate_key = "crawl:rate:ip_limit"
        self.max_request = 15  # 单IP每分钟最大请求15次,合规阈值
        self.expire = 60

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings)

    def process_request(self, request, spider):
        # Redis自增计数,控制请求频率
        current = self.redis.incr(self.rate_key)
        if current == 1:
            self.redis.expire(self.rate_key, self.expire)
        if current > self.max_request:
            spider.logger.warning(f"请求超限,休眠5s,当前计数:{current}")
            time.sleep(5)
        return None

3.3 分布式爬虫主 spider taobao_spider.py(RedisSpider,多节点共享任务)

from scrapy_redis.spiders import RedisSpider
from taocarts_spider.items import GoodsItem
import json

class TaobaoGoodsRedisSpider(RedisSpider):
    name = "taobao_goods"
    # Redis队列key,多节点共用
    redis_key = "taobao:goods:start_urls"
    custom_settings = {
        "LOG_LEVEL": "INFO"
    }

    def parse(self, response):
        # 解析商品基础字段
        goods_list = response.xpath("//div[@class='item J_MouserOnverReq']")
        for goods in goods_list:
            item = GoodsItem()
            item["goods_id"] = goods.xpath("./@data-nid").get()
            item["title"] = goods.xpath(".//a[@class='J_ClickStat']/@title").get().strip()
            item["price"] = goods.xpath(".//strong/text()").get()
            item["img_url"] = goods.xpath(".//img/@src").get()
            item["source_type"] = "taobao"
            yield item
            # 分页请求推入Redis队列,分布式共享
            next_page = response.xpath("//a[contains(text(),'下一页')]/@href").get()
            if next_page:
                yield response.follow(next_page, callback=self.parse)

3.4 OSS 图片存储 Pipeline oss_pipeline.py(阿里云 OSS Python SDK)

import oss2
from scrapy.utils.project import get_project_settings

class OssImgPipeline:
    def __init__(self):
        settings = get_project_settings()
        # 阿里云OSS RAM子账号密钥,最小权限
        auth = oss2.Auth(settings.get("OSS_ACCESS_KEY_ID"), settings.get("OSS_ACCESS_KEY_SECRET"))
        self.bucket = oss2.Bucket(auth, settings.get("OSS_ENDPOINT"), settings.get("OSS_BUCKET_NAME"))
        self.img_prefix = "crawl/goods/"

    def process_item(self, item, spider):
        if item.get("img_url"):
            img_name = f"{item['source_type']}/{item['goods_id']}.jpg"
            try:
                # 流式上传商品图至OSS,配合CDN加速前端访问
                self.bucket.put_object_from_url(f"{self.img_prefix}{img_name}", item["img_url"])
                # 覆盖为OSS CDN访问地址
                item["oss_img_url"] = f"https://cdn.xxxx.oss-cn-shanghai.aliyuncs.com/{self.img_prefix}{img_name}"
            except Exception as e:
                spider.logger.error(f"OSS上传失败 goods_id:{item['goods_id']}, err:{str(e)}")
                item["oss_img_url"] = ""
        return item

四、线上分布式爬虫八大踩坑复盘(一线真实故障,高收录加分核心)

坑 1:单机 Redis 无集群,主节点宕机任务全部丢失

现象:凌晨阿里云单节点 Redis 磁盘满宕机,待爬 URL 队列清空,当日 3 万采集任务全部丢失,前端商品库存停滞 4 小时。
根因:未开通 Redis 集群版,无从节点热备,仅开启 RDB 未定时持久化。
阿里云修复方案:升级 2 主 2 从 Redis 集群,开启每日自动 RDB 快照备份,配置云监控磁盘使用率告警阈值 80%。

坑 2:多 ECS 节点并发过高,未做 Redis 分布式限流,IP 批量封禁

现象:扩容至 4 台 ECS 后并发达到 40,单 IP 一分钟请求超 60 次,采集代理 IP 池 120 个 IP 全部被平台风控封禁,采集中断 12 小时。
根因:原限流中间件仅单机计数,未使用 Redis 全局计数器,多节点计数隔离。
修复:改造 RateLimitMiddleware 使用全局 Redis key 做全分布式限流,统一管控所有节点请求频率。

坑 3:OSS 图片上传频繁超时,公网 Endpoint 延迟高

现象:爬虫上传商品图平均超时 3s,大量图片存储失败,前端无商品封面。
根因:爬虫 ECS 与 OSS 使用公网域名传输,跨公网带宽波动大。
优化:切换阿里云 VPC 内网 OSS Endpoint,内网传输无流量费,上传耗时降至 200ms 内。

坑 4:Scrapy-Redis 指纹去重内存暴涨,Redis 内存占用超 90%

现象:运行 7 天 Redis 内存占用从 10G 涨到 28G,集群触发内存淘汰,待爬任务被随机删除。
根因:RFPDupeFilter 永久存储指纹,无过期清理逻辑,商品 URL 指纹无限堆积。
解决方案:自定义去重过滤器,给指纹 key 设置 30 天过期,每日定时清理无效商品指纹,Redis 内存稳定控制在 10G 以内。

坑 5:ECS 轻量服务器内存不足,Playwright 辅助爬虫 OOM 被杀

现象:带有动态渲染页面的采集节点,每日凌晨内存溢出进程被系统 kill,断点续爬失效。
优化:ECS 升级 2C4G 配置,阿里云云监控配置内存使用率告警,Redis 任务分段分片抓取,单次不超过 500 条 URL。

坑 6:RDS MySQL 写入频繁超时,爬虫批量入库无事务分批提交

现象:Pipeline 单次一次性写入 1000 条商品数据,数据库连接池耗尽,写入超时。
修复:MysqlPipeline 改造批量分批次提交,单次 200 条开启事务,阿里云 RDS 开启读写分离,写入走主库、离线查询走从库。

坑 7:Redis 集群内网端口未限制安全组,存在公网扫描爆破风险

现象:云安全中心告警 Redis 暴力破解扫描记录,安全组开放 6379 全网段访问。
修复:安全组仅允许爬虫 ECS 内网网段访问 6379,RAM 子账号精细化权限,禁止 Redis 公网访问。

坑 8:爬虫无日志归集,异常封禁只能登录服务器看本地日志

现象:批量 IP 封禁后,无法快速定位是哪类 URL 触发风控,本地日志分散多台 ECS 排查效率极低。
阿里云优化:接入 SLS 日志服务,所有爬虫 stdout/stderr 日志实时归集,配置关键词 “封禁、403、timeout” 告警,钉钉实时推送异常。

五、阿里云架构优化后量化性能提升数据

  1. 采集吞吐量:8000 条 / 天 → 10.6 万条 / 天,扩容至 6 台 ECS 无风控封禁;
  2. 任务丢失率:12% → 0.03%,Redis 集群持久化保障崩溃续爬;
  3. 商品图片加载速度:前端海外用户平均 4.2s → 600ms,OSS 搭配全球 CDN;
  4. 云资源成本:同等采集量下,集群 Redis 比多台单机 Redis 节省 38% 云服务费;
  5. 故障排查耗时:单次采集故障 4 小时定位 → 10 分钟通过 SLS 日志定位根因。
目录
相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
33252 201
如何保证分布式文件系统的数据一致性
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36823 22
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24905 16
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36824 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29949 52

热门文章

最新文章