1. 工业级数据采集的挑战与架构选型
在工业供应商数据采集这个垂直领域,我们面临着几个特有的技术挑战。首先是数据源的异构性——不同供应商的网站结构千差万别,从老旧的ASP.NET站点到现代React单页应用都有分布。其次是反爬机制的多样性,从基础的User-Agent检测到复杂的行为指纹分析都需要应对。最重要的是数据规模,单个供应商的SKU数据可能就超过百万条,全行业累计需要处理千万级的数据量。
经过多次技术验证,我们最终选择了Scrapy-Redis作为核心架构。这个组合的优势在于:
- Scrapy提供了成熟的爬虫开发框架,其内置的Selector和Item Pipeline机制能高效处理异构页面
- Redis作为分布式队列和状态存储,天然支持断点续传和去重功能
- 两者的结合可以轻松实现横向扩展,通过增加Worker节点就能提升采集吞吐量
关键决策点:为什么不用纯Scrapy集群?因为在实测中发现,当任务量超过500万时,原生的Scrapy集群在任务分配和状态同步上会出现明显延迟,而Redis的发布/订阅机制能更好地支撑大规模任务调度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心架构设计与组件交互
2.1 系统拓扑结构
我们的生产环境部署了6个Worker节点,每个节点运行8个Scrapy实例,通过Docker进行容器化管理。架构中的关键组件包括:
- 调度中心:运行Scrapy-Redis的spider调度器,负责将待抓取URL分配到Redis队列
- Redis集群:采用3主3从的哨兵模式,存储:
- 待抓取队列(spider:requests)
- 去重指纹集合(spider:dupefilter)
- 项目缓存(spider:items)
- 存储集群:MongoDB分片集群,按供应商ID进行数据分片
python复制# 典型的核心配置示例
REDIS_URL = 'redis://:password@redis-master:6379/0'
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
'custom.pipelines.MongoPipeline': 400
}
2.2 断点续传实现机制
我们改进了原生的Scrapy-Redis断点续传方案,主要优化点包括:
- 请求指纹生成算法:默认的sha1(url)在遇到动态参数时会导致重复采集。我们改用
sha1(method + canonical_url + sorted(params))的生成方式 - 状态持久化:每小时将Redis中的去重集合快照保存到S3,防止Redis崩溃导致指纹丢失
- 断点恢复策略:当检测到异常终止时,自动对比MongoDB中已存数据和Redis指纹集,重建待抓取队列
bash复制# 断点恢复时的数据校验命令示例
redis-cli --eval check_continuation.lua , spider:dupefilter mongodb://collection
3. 千万级去重的工程实践
3.1 内存优化方案
当去重集合超过500万条时,原生的Redis Set结构会消耗超过2GB内存。我们通过以下方案优化:
-
采用Bloom Filter:使用redisbloom模块,将内存占用降低到原来的1/10
python复制from redisbloom.client import Client rb = Client() rb.bfCreate('dupefilter', 0.001, 10000000) -
分层去重策略:
- 第一层:Bloom Filter快速过滤95%的重复请求
- 第二层:精确的Redis Set校验剩余5%的疑似新请求
-
定期冷热数据分离:
sql复制-- 每周将30天前的指纹转移到冷存储 EXPIREAT spider:dupefilter:hot $(date +%s -d "+30 days")
3.2 分布式锁的精细控制
在高并发环境下,我们遇到了多个Worker同时处理同一供应商数据导致解析冲突的问题。解决方案是引入基于Redis的分布式锁:
python复制def process_item(self, item, spider):
lock_key = f"lock:{item['supplier_id']}"
with self.redis.lock(lock_key, timeout=300, blocking_timeout=5):
if self.mongo.find_one({'sku': item['sku']}):
raise DropItem("Duplicate item")
self.mongo.insert_one(dict(item))
4. 负载均衡的实战技巧
4.1 动态优先级队列
我们发现简单的FIFO队列会导致某些响应慢的供应商阻塞整个采集流程。改进方案:
-
根据历史响应时间动态调整URL优先级
python复制def adjust_priority(url): avg_time = redis.zscore('response_stats', url.netloc) or 5.0 return max(0, 10 - int(avg_time)) -
使用Redis的有序集合实现优先级队列
python复制REDIS_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
4.2 自适应限流策略
针对不同网站的反爬策略,我们实现了动态限流:
-
基于响应码的自动调速
python复制def process_response(self, request, response, spider): if response.status == 429: self.crawler.engine.pause() time.sleep(random.randint(30, 60)) self.crawler.engine.unpause() return response -
按域名维度的并发控制
python复制CONCURRENT_REQUESTS_PER_DOMAIN = 2 DOWNLOAD_DELAY = 0.5 + random.random()
5. 异常处理与监控体系
5.1 分布式错误追踪
我们开发了基于ELK的错误收集系统,关键实现包括:
-
在Scrapy的extension中捕获异常事件
python复制def spider_error(self, failure, response, spider): error_data = { 'spider': spider.name, 'url': response.url, 'exception': str(failure.value), 'timestamp': datetime.utcnow().isoformat() } self.es.index(index='scrapy-errors', body=error_data) -
在Grafana中配置的错误看板监控:
- 错误类型分布
- 高频错误URL
- 时段错误率波动
5.2 健康检查与自动恢复
每个Worker节点都运行健康检查服务,主要功能:
-
心跳检测(每5秒上报)
bash复制while true; do echo 'HEALTHY' | nc -w 1 monitor 8080 sleep 5 done -
自动隔离故障节点
python复制def check_workers(): dead_nodes = [] for node in redis.smembers('active_nodes'): if redis.get(f'heartbeat:{node}') is None: dead_nodes.append(node) if dead_nodes: redis.srem('active_nodes', *dead_nodes)
6. 性能优化关键指标
经过3个月的生产运行,我们的系统达到了以下性能指标:
| 指标项 | 初始方案 | 优化方案 |
|---|---|---|
| 日均采集量 | 120万 | 680万 |
| 平均响应时间 | 2.3s | 0.8s |
| 去重准确率 | 99.2% | 99.998% |
| 断点恢复耗时 | 45min | <3min |
实现这些优化的关键技术点包括:
-
TCP连接复用:启用Scrapy的HTTP缓存并调大CONCURRENT_REQUESTS
python复制HTTPCACHE_ENABLED = True CONCURRENT_REQUESTS = 100 -
智能重试策略:对网络错误使用指数退避重试
python复制RETRY_TIMES = 3 RETRY_HTTP_CODES = [500, 502, 503, 504] RETRY_EXCEPTIONS = [TimeoutError, ConnectionError] -
内存限制防护:防止解析大文件导致OOM
python复制DOWNLOAD_MAXSIZE = 10 * 1024 * 1024 # 10MB DOWNLOAD_WARNSIZE = 2 * 1024 * 1024 # 2MB
这套系统目前稳定运行在AWS的6台c5.2xlarge实例上,每月处理超过2亿条工业品数据。在实际部署中发现,最大的性能瓶颈往往不是网络IO,而是XPath解析的CPU消耗。我们通过预编译XPath表达式和限制解析深度,使CPU利用率降低了40%。
