必须用信号量控制分页并发量,避免触发限流;分页url需按需生成防内存爆炸;连接池参数需协同配置;响应应流式解析防oom;每页请求须带超时、重试等完备机制。

分页请求必须用信号量控制并发量
直接用 asyncio.gather 并发拉取全部分页 URL,极大概率触发目标服务端限流或 429 错误。真实场景中,你面对的不是本地测试接口,而是有反爬策略的真实 API —— 它们对单 IP 的请求数、频率、User-Agent 一致性都有隐性阈值。
信号量(asyncio.Semaphore)是唯一可控的“节流阀”:
- 设为
10~50是安全起点,视目标稳定性动态调整 - 不要和连接池参数(如
limit_per_host)混淆:信号量管的是“同时发起的请求数”,TCPConnector 管的是“底层复用的连接数” - 必须在
session.get()外层加async with semaphore:,否则起不到限速作用
分页参数生成要避免内存爆炸
百万级数据意味着可能上万页。如果一次性把所有 offset 或 page 构建成列表(比如 [f"?offset={i*20}" for i in range(10000)]),会提前吃光内存,尤其在低配机器上。
更稳妥的做法是按需生成:
- 用生成器函数代替列表推导式:
def page_urls(): yield f"{base_url}?offset=0"; yield f"{base_url}?offset=20"; ... - 配合
asyncio.Queue实现生产者-消费者模式:一边生成下一页 URL,一边由 worker 消费请求 - 若 API 支持游标(cursor)而非 offset,优先用 cursor,避免因数据插入导致的翻页偏移错乱
连接池配置不匹配会导致连接耗尽或性能断崖
很多人只调 limit,却忽略 limit_per_host 和 keepalive_timeout 的协同关系。例如你设了 limit=1000,但 limit_per_host=10,而目标域名只有 1 个——那实际最多就 10 个连接在跑,其余协程全在排队等连接,吞吐量卡死。
推荐组合(针对单域名高并发场景):
-
limit=500:全局最大连接数,别超系统文件描述符上限(ulimit -n查看) -
limit_per_host=100:防止单域名被封,也避免 DNS 缓存竞争 -
keepalive_timeout=30:让空闲连接多活 30 秒,减少重复握手开销 -
force_close=False:显式关闭 keep-alive 会抵消连接复用效果
响应解析阶段最容易忽略流式处理
分页接口返回 JSON 是常态,但大字段(如商品详情、长文本)会让 await response.json() 一次性加载进内存。当并发 + 大响应体叠加,OOM 就在下一秒。
两种轻量方案可选:
- 用
response.content流式读取:async for chunk in response.content.iter_chunked(8192): ...,适合边收边存或校验 - 先用
response.headers.get("content-length")预判大小,超阈值(如 >1MB)则跳过或降级处理 - 避免在
fetch函数里做重逻辑(如正则提取、MongoDB 写入),这些应交给后续 pipeline 异步处理
真正难的不是并发数字堆得多高,而是每一页请求是否都带着超时、重试、日志、状态码校验、连接复用意识——漏掉任意一环,1000 并发就只是看着漂亮的幻觉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











