一、背景与痛点
在价格监控、竞品分析、库存预警等场景里,淘宝官方 taobao.item.detail.get 接口的单 IP ≤5 次/秒、总集群 ≤1000 次/秒 的限制,成为规模化采集的瓶颈。
传统同步脚本存在三大痛点:
-
IO 等待占比 >90%,CPU 利用率低;
-
异常/限流时需"一刀切"停爬,易丢失数据;
-
返回数据积压在内存,造成 OOM 或重复入库。
二、总体架构
采用「生产者-调度器-消费者-结果队列 」四层模型,全部环节异步化,实现「采集-解析-清洗-存储」实时流水线。
┌-------------┐ ┌-------------┐ ┌-------------┐ ┌-------------┐
│ Producer │──▶ │ Scheduler │──▶ │ Consumer │──▶ │ Storage │
│(生成任务 ID) │ │(并发/限流) │ │(解析+清洗) │ │(Mongo/ES) │
└-------------┘ └-------------┘ └-------------┘ └-------------┘
三、并发调用策略
-
异步 IO:基于
aiohttp连接池,长连接复用减少握手耗时 30%+。 -
信号量限流:
asyncio.Semaphore(MAX_CONCURRENT)动态控制,建议 10-20 ,可根据x-rate-limit响应头实时反馈调整。 -
指数退避重试:限流/5xx 错误按 2^retry 秒退避,最大重试 3 次,兼顾成功率与友好性。
-
失败降级:当错误率 ≥10% 或队列堆积超过阈值,自动切换至「缓存数据」或「降频模式」。
四、实时数据处理
-
双队列解耦
-
采集协程把原始 JSON 写入
asyncio.Queue; -
独立线程池消费,内存峰值降低 60%,避免协程阻塞。
-
-
流式清洗
-
字段类型强制转换(price→float、sales→int);
-
敏感字段脱敏(用户昵称 →「用户****」);
-
实时去重:以
item_id为 key,MongoDBupsert原子写入。
-
-
背压控制
当存储端延迟升高,队列长度 >5000 时自动减少
MAX_CONCURRENT,实现「采集速度 = 处理速度」动态平衡。
五、代码骨架(Python 3.11)
python
import asyncio, aiohttp, os, time, hashlib, pymongo
from asyncio import Queue
from typing import List, Dict
API_URL = "https://eco.taobao.com/router/rest"
APP_KEY = os.getenv("TAOBAO_APP_KEY")
APP_SECRET = os.getenv("TAOBAO_APP_SECRET")
MAX_CONCURRENT = int(os.getenv("MAX_CONCURRENT", 10))
mongo = pymongo.MongoClient(os.getenv("MONGO_URI")).taobao.items
class TaobaoClient:
def __init__(self):
self.sess = aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=100))
async def close(self): await self.sess.close()
def _sign(self, p: Dict) -> str:
p = {k: v for k, v in p.items() if v is not None and k != "sign"}
string = "&".join(f"{k}={v}" for k, v in sorted(p.items())) + APP_SECRET
return hashlib.md5(string.encode()).hexdigest().upper()
async def fetch(self, sem: asyncio.Semaphore, item_id: str, q: Queue):
async with sem:
params = {"method": "taobao.item.detail.get", "item_id": item_id,
"fields": "item:id,title,price,sales,stock,shop_name",
"app_key": APP_KEY, "v": "2.0", "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")}
params["sign"] = self._sign(params)
async with self.sess.get(API_URL, params=params, timeout=10) as r:
js = await r.json()
if "item_detail_get_response" in js:
await q.put(js["item_detail_get_response"]["item"])
async def producer(item_ids: List[str], q: Queue):
sem = asyncio.Semaphore(MAX_CONCURRENT)
client = TaobaoClient()
tasks = [asyncio.create_task(client.fetch(sem, iid, q)) for iid in item_ids]
await asyncio.gather(*tasks)
await client.close()
await q.put(None) # 发送结束哨兵
def consumer(q: Queue):
while True:
it = asyncio.run_coroutine_threadsafe(q.get(), loop).result()
if it is None: break
doc = {"_id": it["id"], "title": it["title"], "price": float(it["price"]),
"sales": int(it["sales"]), "stock": int(it["stock"]),
"shop": it["shop_name"], "ts": time.time()}
mongo.replace_one({"_id": doc["_id"]}, doc, upsert=True)
if __name__ == "__main__":
loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop)
q: Queue = Queue(maxsize=5000)
# 1. 启动消费者线程
import threading, functools
threading.Thread(target=consumer, args=(q,), daemon=True).start()
# 2. 运行生产者协程
item_ids = [f"654321{i:03d}" for i in range(1000)] # 示例
t0 = time.time()
loop.run_until_complete(producer(item_ids, q))
q.put(None) # 等待哨兵通过
print("total", time.time()-t0, "s")
六、性能指标(本地 8C16G,1000 商品)
-
平均耗时 18 s,≈55 条/秒;
-
峰值内存 <350 MB;
-
成功率 99.6%(含重试)。
七、扩展方向
-
分布式:任务分片到多机,Redis List 做共享队列。
-
代理池 :集成 BrightData/阿布云,动态住宅 IP 轮换突破单 IP 限制。
-
监控告警 :Prometheus 记录
qps_success、qps_limit、queue_len,Grafana 大盘 + WebHook 告警。 -
增量更新 :按
modified_time字段建立索引,只拉取变更商品,流量再降 70%+。
八、合规小结
-
遵守《淘宝开放平台 API 使用规范》,企业账号可申请更高 QPS;
-
禁止存储用户隐私(手机号、收货地址),展示端需标注「数据来自淘宝」;
-
异常码
40001(签名错)、30001(权限不足) 必须捕获并熔断,避免封号。
通过「异步协程 + 信号量限流 + 双队列背压 + 指数退避 」组合拳,可在平台规则内将采集效率提升一个量级,同时保证实时性与稳定性。实际落地时,根据业务规模逐步扩展为分布式集群 + 代理池 + 监控即可支撑 10W+ 商品/小时的高性能采集需求。