
Python 爬虫并发抓取的核心在于合理利用 I/O 等待时间,避免单请求阻塞整个流程。关键不是“开越多线程/协程越好”,而是匹配目标网站的响应特性、自身网络带宽和反爬策略。
用 asyncio + aiohttp 实现高并发 HTTP 请求
aiohttp 是 Python 异步生态中最常用的 HTTP 客户端,配合 asyncio 可轻松实现数百级并发,且内存占用低、响应快。它适合大量轻量级请求(如列表页、API 接口)。
- 需显式创建 ClientSession,复用连接池,避免频繁握手开销
- 限制并发数(如使用 asyncio.Semaphore),防止被目标站限流或封 IP
- 务必设置超时(timeout 参数),避免某个请求卡死拖垮整体任务
- 示例:抓取 100 个 URL,限制同时 20 个请求
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 更清晰
注意反爬与请求调度的协同设计
并发本身不解决反爬问题,反而容易触发风控。必须把并发控制和反爬策略绑定设计:
立即学习“Python免费学习笔记(深入)”;
- 随机化请求间隔(如 uniform(0.5, 2.0) 秒),避免固定频率被识别为脚本
- 轮换 User-Agent 和代理 IP(尤其使用 aiohttp 时,可在每次 request 中动态设置 headers)
- 对返回 429/503 的请求做退避重试(exponential backoff),而非立即重发
- 关键:监控响应状态码和响应体特征(如是否含“验证”字样),自动降速或切换代理
避免常见陷阱
并发提升效率的前提是资源不冲突、逻辑不耦合:
- 不要在协程里调用 time.sleep() —— 会阻塞整个事件循环,改用 await asyncio.sleep()
- 全局共享变量(如计数器、列表)在多线程/协程下需加锁(threading.Lock)或用线程安全结构(如 queue.Queue)
- DNS 解析可能成为瓶颈,可在 aiohttp 中启用 TCPConnector(use_dns_cache=True, limit=100)
- 大量并发写文件易导致 IO 冲突,建议先缓存结果,再批量落盘










