Python中替代multiprocessing的高效资源友好批量URL爬取方案(非线程)
高效且资源友好的爬虫替代方案
1. AsyncIO + 异步HTTP库(如aiohttp)
爬虫属于IO密集型任务,异步IO是当前最适配的方案之一,基于单线程(或少量线程)的协程模型,资源占用极低,可同时处理数千个并发请求。
- 核心逻辑:用
asyncio实现事件循环,配合aiohttp发起异步HTTP请求,彻底避免进程/线程切换的额外开销。 - 示例代码片段:
import asyncio import aiohttp async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch(session, f"https://example.com/record/{i}") for i in range(10000)] results = await asyncio.gather(*tasks) # 后续处理爬取结果 if __name__ == "__main__": asyncio.run(main())
- 优势:单进程即可支撑极高并发,内存、CPU占用远低于多进程方案,完全适配百万级记录的爬取需求。
2. 优化版concurrent.futures.ProcessPoolExecutor
如果爬虫流程中包含少量CPU密集型操作(比如复杂HTML/JSON解析),可使用ProcessPoolExecutor但优化参数,避免无节制创建进程:
- 关键优化:将
max_workers设置为CPU核心数的1-2倍(而非30-50),利用CPU亲和性减少上下文切换;配合任务分批提交,避免一次性压入百万任务导致内存暴涨。 - 示例代码片段:
from concurrent.futures import ProcessPoolExecutor import requests def crawl_record(record_id): url = f"https://example.com/record/{record_id}" response = requests.get(url) # 解析并返回结果 def main(): record_ids = range(1000000) # 按CPU核心数设置进程数,例如4核设为8 with ProcessPoolExecutor(max_workers=8) as executor: # 分批提交任务,控制内存占用 for chunk in chunks(record_ids, 1000): results = executor.map(crawl_record, chunk) # 处理当前批次结果 def chunks(lst, n): for i in range(0, len(lst), n): yield lst[i:i+n]
- 优势:兼顾CPU密集型处理的效率,同时严格控制资源占用,比手动创建大量进程更合理。
3. Gevent(协程猴子补丁方案)
Gevent是基于libev的协程库,通过猴子补丁(monkey-patch)将同步IO接口转为异步,无需大幅修改原有同步代码即可实现高并发:
- 核心逻辑:用
gevent.monkey.patch_all()替换标准库的同步IO接口,再用gevent.pool.Pool控制并发数。 - 示例代码片段:
from gevent import monkey, pool import requests monkey.patch_all() def crawl_record(record_id): url = f"https://example.com/record/{record_id}" response = requests.get(url) # 解析结果 def main(): record_ids = range(1000000) p = pool.Pool(1000) # 可设置数千并发,资源占用仍保持低位 results = p.map(crawl_record, record_ids) # 处理结果 if __name__ == "__main__": main()
- 优势:对原有同步代码改造极小,可快速实现高并发,资源占用远低于多进程方案。
4. multiprocessing.Pool + 任务队列限流
如果坚持使用多进程方案,可改用multiprocessing.Pool而非手动创建大量进程,同时通过任务队列控制并发任务数:
- 关键:设置
processes为CPU核心数的合理倍数,用imap或imap_unordered分批获取结果,避免一次性加载所有任务。 - 示例代码片段:
from multiprocessing import Pool import requests def crawl_record(record_id): url = f"https://example.com/record/{record_id}" response = requests.get(url) return response.text def main(): record_ids = range(1000000) with Pool(processes=8) as pool: # 迭代获取结果,降低内存占用 for result in pool.imap_unordered(crawl_record, record_ids, chunksize=100): # 处理单条结果 pass
- 优势:自动管理进程生命周期,避免进程泄漏,合理控制资源占用。
内容的提问来源于stack exchange,提问作者Volatil3
相关产品推荐
相关产品推荐

