【爬虫入门第6讲】Python爬虫并发方案全解析
Python爬虫并发方案全解析:多线程、多进程与异步协程实战指南
引言
在爬虫开发中,性能瓶颈往往不在于代码语法,而在于对网络I/O、并发模型和资源调度的底层理解。当我们需要批量抓取网页数据时,串行请求的效率显然无法满足需求。Python提供了三种主流的并发方案:多线程(threading)、多进程(multiprocessing)和异步协程(asyncio + aiohttp)。本文将深入剖析这三种方案的适用场景、使用方法及优缺点,帮助你根据实际需求做出最佳选择。
一、多线程(threading / ThreadPoolExecutor)
1. 适用场景
多线程适合I/O密集型任务,如网络请求、文件读写等。由于Python的GIL(全局解释器锁)限制,同一时刻只有一个线程执行Python字节码,因此多线程无法利用多核CPU提升计算性能。但对于I/O密集型任务,线程可以在阻塞时让出GIL,实现并发效果。
典型应用:小批量网页爬取、同时处理多个客户端请求、日志记录等轻量级并发场景。
2. 使用方法
方式一:threading.Thread 直接创建
importthreadingimportrequestsimporttimedeffetch_url(url):try:resp=requests.get(url,timeout=5)print(f"成功爬取:{url}状态码:{resp.status_code}")exceptExceptionase:print(f"失败:{url}错误:{e}")urls=["https://www.baidu.com","https://www.qq.com","https://www.sina.com.cn"]threads=[]forurlinurls:t=threading.Thread(target=fetch_url,args=(url,))threads.append(t)t.start()fortinthreads:t.join()方式二:Queue + 多线程(任务分发)
使用Queue队列做任务分发,线程安全,适合入门爬虫:
importthreadingimportqueueimportrequests url_list=["https://www.baidu.com","https://www.qq.com","https://www.sina.com.cn"]q=queue.Queue()forurlinurl_list:q.put(url)defspider():whilenotq.empty():url=q.get()try:resp=requests.get(url,timeout=5)print(f"成功爬取:{url}状态码:{resp.status_code}")exceptExceptionase:print(f"失败:{url}错误:{e}")finally:q.task_done()thread_num=3for_inrange(thread_num):t=threading.Thread(target=spider)t.daemon=Truet.start()q.join()方式三:ThreadPoolExecutor 线程池
使用concurrent.futures.ThreadPoolExecutor简化线程管理:
fromconcurrent.futuresimportThreadPoolExecutorimportrequestsdeffetch(url):resp=requests.get(url,timeout=5)returnresp.status_code urls=["https://www.baidu.com","https://www.qq.com","https://www.sina.com.cn"]withThreadPoolExecutor(max_workers=3)asexecutor:futures=[executor.submit(fetch,url)forurlinurls]forfinfutures:print(f.result())3. 优缺点
优点:
- 实现简单,易于理解,适合入门
- 共享内存空间,数据传递方便
- 轻量级调度,I/O密集场景高效
缺点:
- 受GIL限制,无法真正并行执行CPU密集型任务
- 线程切换开销较大,实际有效并行度有限
- 需要处理线程安全问题(如加锁)
二、多进程(multiprocessing / ProcessPoolExecutor)
1. 适用场景
多进程适合CPU密集型任务,如HTML解析、数据清洗、图像处理等。每个进程拥有独立的内存空间和Python解释器,可以绕过GIL限制,充分利用多核CPU。
典型应用:大规模数据解析、分布式爬虫、需要跨CPU核心并行计算的任务。
2. 使用方法
方式一:multiprocessing.Process
importmultiprocessingdefcpu_task(n):total=sum(i*iforiinrange(n))print(f"进程{multiprocessing.current_process().name}完成")if__name__=="__main__":p1=multiprocessing.Process(target=cpu_task,args=(5_000_000,))p2=multiprocessing.Process(target=cpu_task,args=(5_000_000,))p1.start()p2.start()p1.join()p2.join()方式二:进程间通信(Queue)
defproducer(q):foriinrange(5):q.put(f"数据{i}")q.put(None)defconsumer(q):whileTrue:data=q.get()ifdataisNone:breakprint(f"消费:{data}")if__name__=="__main__":q=multiprocessing.Queue()p1=multiprocessing.Process(target=producer,args=(q,))p2=multiprocessing.Process(target=consumer,args=(q,))p1.start()p2.start()p1.join()p2.join()方式三:ProcessPoolExecutor 进程池
fromconcurrent.futuresimportProcessPoolExecutordefsquare(n):returnn*nwithProcessPoolExecutor(max_workers=4)asexecutor:results=executor.map(square,range(10))print(list(results))3. 优缺点
优点:
- 绕过GIL,真正实现并行计算
- 适合CPU密集型任务,性能提升明显
- 进程间隔离性强,数据安全性高
缺点:
- 内存占用高,每个进程有独立内存空间
- 进程间通信复杂,需使用Queue、Pipe等工具
- 创建和销毁进程的开销较大
三、异步协程(asyncio + aiohttp)
1. 适用场景
异步协程适合高并发I/O密集型任务,如大规模网页爬取、实时数据抓取、Web服务等。它在单线程内通过事件循环实现并发,可以管理数万级并发连接,资源占用极低。
典型应用:百万级网页抓取、实时数据监控、高性能API服务。
2. 使用方法
基础异步抓取
importasyncioimportaiohttpimporttime url_list=["https://www.baidu.com","https://www.qq.com","https://www.sina.com.cn","https://www.163.com","https://www.taobao.com"]asyncdeffetch(session,url):try:asyncwithsession.get(url,timeout=aiohttp.ClientTimeout(total=5))asresp:print(f"成功爬取:{url}状态码:{resp.status}")returnawaitresp.text()exceptExceptionase:print(f"失败:{url}错误:{e}")asyncdefmain():asyncwithaiohttp.ClientSession()assession:tasks=[fetch(session,url)forurlinurl_list]awaitasyncio.gather(*tasks)if__name__=="__main__":start=time.time()asyncio.run(main())end=time.time()print(f"\n协程异步爬虫总耗时:{end-start:.2f}秒")批量控制与限流
使用信号量(Semaphore)控制并发数量,避免对目标服务器造成过大压力:
asyncdeffetch_with_semaphore(sem,session,url):asyncwithsem:returnawaitfetch(session,url)asyncdefmain():sem=asyncio.Semaphore(10)# 限制并发数为10asyncwithaiohttp.ClientSession()assession:tasks=[fetch_with_semaphore(sem,session,url)forurlinurl_list]awaitasyncio.gather(*tasks)批量分页处理
batch_size=100foriinrange(0,len(urls),batch_size):batch=urls[i:i+batch_size]results=awaitasyncio.gather(*[fetch(session,url)forurlinbatch])3. 优缺点
优点:
- 单线程内实现高并发,资源占用极低(每个协程仅需几KB内存)
- 速度远超多线程,尤其适合大批量请求
- 代码逻辑清晰,避免回调地狱
缺点:
- 所有I/O操作必须是异步兼容的,不能直接使用
requests.get()、time.sleep()等同步函数 - 调试相对复杂,需要掌握事件循环和异步语法
- 不适合CPU密集型任务(会阻塞事件循环)
四、三种方案对比总结
| 维度 | 多线程 | 多进程 | 异步协程 |
|---|---|---|---|
| 适用场景 | I/O密集型(小批量) | CPU密集型 | I/O密集型(大批量) |
| 并发能力 | 中等 | 高(受CPU核心数限制) | 极高(万级连接) |
| 资源占用 | 中等 | 高 | 低 |
| 开发复杂度 | 低 | 中 | 高 |
| GIL影响 | 受限制 | 绕过GIL | 单线程无GIL问题 |
| 速度表现 | 一般 | CPU任务快,I/O任务慢 | I/O任务极快 |
| 典型耗时 | 2.2秒(10个任务) | 7.6秒(CPU任务) | 2.5秒(100+协程) |
五、选型建议
1. 根据任务类型选择
- I/O密集型 + 小批量(几十个请求):多线程即可,简单高效
- I/O密集型 + 大批量(成千上万请求):优先选择异步协程,性能碾压
- CPU密集型(数据解析、计算):多进程是唯一选择
- 混合型(抓取+解析):可采用多进程+异步协程的混合架构,多进程负责解析,异步协程负责抓取
2. 实际项目中的最佳实践
- 连接复用:使用
aiohttp.ClientSession复用TCP连接,减少握手开销 - 限速控制:添加随机延时,避免触发反爬机制
- 重试机制:失败请求自动重试,提高抓取成功率
- 批量写入:数据存储时批量操作,减少I/O调用
- 监控告警:统计抓取成功率、延迟、内存占用等指标
3. 反爬应对策略
多线程/多进程可模拟多IP请求,降低封禁风险;而异步协程由于单线程特性,在反爬场景中需要额外配置代理池。
结语
Python爬虫的并发方案没有绝对的“最优解”,只有“最适合”。理解每种方案的底层原理和适用场景,才能在实际项目中做出明智的选择。对于大多数爬虫场景,异步协程凭借其极致的并发能力和低资源消耗,正成为越来越多开发者的首选。但如果你需要处理CPU密集型任务,或者项目对开发速度要求更高,多线程和多进程依然是可靠的选择。
