
python 爬虫并发抓取的核心在于合理利用 I/O 等待时间,避免单请求阻塞整个流程。关键不是“开越多线程/协程越好”,而是匹配目标网站的响应特性、自身网络带宽和反爬策略。
用 asyncio + aiohttp 实现高并发 HTTP 请求
aiohttp 是 Python 异步生态中最常用的 HTTP 客户端,配合 asyncio 可轻松实现数百级并发,且内存占用低、响应快。它适合大量轻量级请求(如列表页、API 接口)。
- 需显式创建 Clientsession,复用连接池,避免频繁握手开销
- 限制并发数(如使用 asyncio.Semaphore),防止被目标站限流或封 IP
- 务必设置超时(timeout 参数),避免某个请求卡死拖垮整体任务
- 示例:抓取 100 个 URL,限制同时 20 个请求
async with aiohttp.ClientSession() as session:
sem = asyncio.Semaphore(20)
tasks = [fetch(session, url, sem) for url in urls]
await asyncio.gather(*tasks)
用 requests + ThreadPoolExecutor 控制中等并发
requests 简单易用、兼容性强,配合 concurrent.futures.ThreadPoolExecutor 可快速实现 10–50 级并发,适合对稳定性要求高、需复用 cookies 或 session 的场景(如登录后抓取)。
- 线程数不宜超过 CPU 核心数的 2–4 倍;I/O 密集型任务可稍多,但通常 32 线程已是多数情况上限
- 每个线程应独立维护 requests.Session,避免线程间状态干扰
- 建议配合 retry 模块(如 tenacity)处理临时失败,比手动 try-except 更清晰
注意反爬与请求调度的协同设计
并发本身不解决反爬问题,反而容易触发风控。必须把并发控制和反爬策略绑定设计:
- 随机化请求间隔(如 uniform(0.5, 2.0) 秒),避免固定频率被识别为脚本
- 轮换 User-Agent 和代理 IP(尤其使用 aiohttp 时,可在每次 request 中动态设置 headers)
- 对返回 429/503 的请求做退避重试(exponential backoff),而非立即重发
- 关键:监控响应状态码和响应体特征(如是否含“验证”字样),自动降速或切换代理
避免常见陷阱
并发提升效率的前提是资源不冲突、逻辑不耦合: