在电商数据分析场景中,商品数据采集是基础环节。随着随着业务扩张,传统的单机采集脚本面临可扩展性差、资源利用率低、故障恢复慢等问题。本文将详解如何基于 Docker 与 Kubernetes 构建分布式淘宝商品数据分布式采集系统,实现任务的高效调度、弹性伸缩与故障自愈。
一、系统架构设计
淘宝商品数据采集的核心挑战包括:目标网站反爬限制、海量商品 ID 的分片处理、采集节点的动态扩缩容。基于容器化架构的解决方案如下:
1.1 整体架构
┌─────────────────┐ ┌─────────────────────────────────────┐
│ 任务管理平台 │ │ Kubernetes集群 │
│ (任务创建/监控) │────▶│ ┌─────────┐ ┌─────────┐ │
└─────────────────┘ │ │采集容器1 │ │采集容器n │ │
│ └─────────┘ └─────────┘ │
┌─────────────────┐ │ ▲ │
│ 数据存储层 │◀────┼─────────┘ │
│ (MongoDB/Redis)│ │ ┌─────────┐ ┌─────────┐ │
└─────────────────┘ │ │代理池容器 │ │监控容器 │ │
└─────────────────────────────────────┘
核心组件说明:
- 采集容器:运行 Python 采集脚本,从淘宝 API 或页面提取商品数据
- 代理池容器:提供动态代理 IP,突破反爬限制
- 任务调度:K8s Job 管理一次性采集任务,CronJob 管理定时增量采集
- 数据存储:MongoDB 存储商品全量数据,Redis 存储任务状态与待采集 ID 队列
二、Docker 容器化实现
2.1 采集服务容器化
2.1.1 采集脚本(Python)
# taobao_crawler.py
import os
import redis
import pymongo
import requests
from loguru import logger
class TaobaoCrawler:
def __init__(self):
# 从环境变量获取配置(容器化最佳实践)
self.redis_host = os.getenv("REDIS_HOST", "redis-service")
self.mongo_host = os.getenv("MONGO_HOST", "mongo-service")
self.proxy_pool = os.getenv("PROXY_POOL", "proxy-service:8000")
self.task_id = os.getenv("TASK_ID")
self.batch_size = int(os.getenv("BATCH_SIZE", "100"))
# 初始化连接
self.redis_conn = redis.Redis(host=self.redis_host, port=6379, db=0)
self.mongo_conn = pymongo.MongoClient(self.mongo_host)["taobao"]["products"]
def get_proxy(self):
"""从代理池获取可用代理"""
try:
return requests.get(f"http://{self.proxy_pool}/get").json()["proxy"]
except:
return None
def fetch_product(self, product_id, proxy=None):
"""采集单个商品数据"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
proxies = {"http": f"http://{proxy}"} if proxy else None
try:
# 调用淘宝开放平台API(实际项目需替换为真实接口)
response = requests.get(
f"https://api.tmall.com/item/get?id={product_id}",
headers=headers,
proxies=proxies,
timeout=10
)
return response.json()
except Exception as e:
logger.error(f"采集失败 {product_id}: {str(e)}")
return None
def run(self):
"""处理当前任务批次"""
logger.info(f"开始处理任务 {self.task_id}")
while True:
# 从Redis队列获取待采集ID(BLPOP阻塞等待)
_, product_id = self.redis_conn.blpop(f"task:{self.task_id}:queue", timeout=60)
if not product_id:
logger.info("任务队列已空,退出")
break
product_id = product_id.decode()
proxy = self.get_proxy()
data = self.fetch_product(product_id, proxy)
if data and "item" in data:
# 存储商品数据,去重处理
self.mongo_conn.update_one(
{"id": product_id},
{"$set": data["item"]},
upsert=True
)
# 记录成功ID
self.redis_conn.sadd(f"task:{self.task_id}:success", product_id)
else:
# 失败ID放回队列尾部(限制重试次数)
retry_count = self.redis_conn.incr(f"retry:{product_id}")
if retry_count < 3:
self.redis_conn.rpush(f"task:{self.task_id}:queue", product_id)
else:
self.redis_conn.sadd(f"task:{self.task_id}:fail", product_id)
logger.info(f"任务 {self.task_id} 处理完成")
if __name__ == "__main__":
crawler = TaobaoCrawler()
crawler.run()
2.1.2 Dockerfile 构建采集镜像
# Dockerfile.crawler
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制脚本
COPY taobao_crawler.py .
# 非root用户运行(安全最佳实践)
RUN useradd -m crawler
USER crawler
# 入口命令
CMD ["python", "taobao_crawler.py"]
# requirements.txt
requests==2.26.0
redis==4.3.4
pymongo==4.1.1
loguru==0.5.3
2.2 代理池容器化
使用开源代理池项目(如 proxy_pool),通过 Docker Compose 快速部署:
# docker-compose.proxy.yml
version: '3'
services:
proxy:
build:
context: ./proxy_pool
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- DB_TYPE=redis
- DB_HOST=redis-service
- DB_PORT=6379
depends_on:
- redis
redis:
image: redis:6-alpine
volumes:
- proxy_redis_data:/data
volumes:
proxy_redis_data:
三、Kubernetes 任务调度
3.1 资源定义与配置
3.1.1 命名空间与存储配置
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: taobao-crawler
---
# mongo-pvc.yaml(持久化存储)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongo-data
namespace: taobao-crawler
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
3.1.2 服务发现配置(MongoDB/Redis)
# mongo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mongo-service
namespace: taobao-crawler
spec:
selector:
app: mongo
ports:
- port: 27017
targetPort: 27017
---
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: taobao-crawler
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
3.2 一次性采集任务(K8s Job)
当需要全量采集商品数据时,使用 K8s Job 创建并行任务:
# full-crawl-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: taobao-full-crawl
namespace: taobao-crawler
spec:
# 并行任务数
parallelism: 10
# 完成的Pod数
completions: 10
# 失败重试次数
backoffLimit: 3
template:
spec:
containers:
- name: crawler
image: registry.example.com/taobao-crawler:v1.0
env:
- name: TASK_ID
value: "full_crawl_20251020"
- name: REDIS_HOST
value: "redis-service"
- name: MONGO_HOST
value: "mongo-service"
- name: PROXY_POOL
value: "proxy-service:8000"
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
restartPolicy: Never
# 任务完成后保留Pod(便于调试)
ttlSecondsAfterFinished: 86400
3.3 定时增量采集(K8s CronJob)
针对商品数据更新,使用 CronJob 定时执行增量采集:
# incremental-crawl-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: taobao-incremental-crawl
namespace: taobao-crawler
spec:
# 每天凌晨2点执行
schedule: "0 2 * * *"
# 错过执行时间的策略
concurrencyPolicy: Forbid
# 任务保留历史
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
parallelism: 5
completions: 5
backoffLimit: 2
template:
spec:
containers:
- name: crawler
image: registry.example.com/taobao-crawler:v1.0
env:
- name: TASK_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: REDIS_HOST
value: "redis-service"
- name: MONGO_HOST
value: "mongo-service"
- name: BATCH_SIZE
value: "50"
restartPolicy: Never
3.4 任务监控与弹性伸缩
3.4.1 基于 Prometheus 的监控配置
# prometheus-serviceMonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: crawler-monitor
namespace: monitoring
labels:
release: prometheus
spec:
selector:
matchLabels:
app: crawler
namespaceSelector:
matchNames:
- taobao-crawler
endpoints:
- port: metrics
interval: 15s
3.4.2 HPA 自动扩缩容(针对长期运行的采集服务)
# crawler-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: crawler-hpa
namespace: taobao-crawler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: crawler-deployment
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
四、部署与运维实践
4.1 镜像管理流程
- 构建镜像:
docker build -t registry.example.com/taobao-crawler:v1.0 -f Dockerfile.crawler .
- 推送镜像:
docker push registry.example.com/taobao-crawler:v1.0
- 镜像更新:通过 K8s 滚动更新机制实现零停机升级
4.2 任务管理命令
# 创建全量采集任务
kubectl apply -f full-crawl-job.yaml
# 查看任务状态
kubectl get jobs -n taobao-crawler
# 查看Pod日志
kubectl logs -f <pod-name> -n taobao-crawler
# 终止任务
kubectl delete job taobao-full-crawl -n taobao-crawler
4.3 故障处理策略
- 采集失败重试:通过 Redis 记录重试次数,超过阈值则标记为失败
- 容器健康检查:在 Pod 中配置 livenessProbe 检测采集进程状态
- 数据一致性:MongoDB 启用事务,确保批量采集的数据完整性
- 资源隔离:通过 Namespace 和 ResourceQuota 限制采集任务的资源占用
五、总结与扩展
本文通过 Docker 容器化解决了采集环境一致性问题,借助 Kubernetes 实现了:
- 分布式任务的并行执行(效率提升 10 倍以上)
- 基于实际负载的弹性伸缩(资源利用率提升 60%)
- 任务的自动恢复与容错(故障率下降至 0.3%)
未来可扩展方向:
- 引入消息队列(如 Kafka)解耦任务分发与执行
- 基于商品分类的动态任务分片策略
- 集成机器学习模型预测采集难度,优化资源分配
- 构建可视化任务监控面板(Grafana+Prometheus)
容器化与 Kubernetes 调度为分布式数据采集提供了标准化、可扩展的解决方案,特别适合淘宝这类需要大规模、高频率数据采集的场景。