Python 3.12通过PEP 709内联推导式减少临时对象堆积,优化列表推导式内存开销;建议升级后直接受益,避免手动map/filter,慎用嵌套推导式;配合sys.intern、memoryview、URL指纹、禁用GC、合理线程数及及时释放小对象可显著提升爬虫内存效率。

用PEP 709内联推导式减少临时对象堆积
爬虫中大量使用列表推导式构建URL队列、解析字段或清洗数据时,Python 3.11及之前版本会为每个推导式创建独立的函数对象和闭包环境,导致内存中堆叠大量小对象。Python 3.12通过PEP 709默认启用内联优化,直接把推导式编译进当前帧,省掉函数对象分配开销。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 无需改代码——只要升级到Python 3.12,
[item.strip() for item in raw_list]这类表达式自动受益 - 避免手动包装成
list(map(...))或list(filter(...)),它们不享受内联,且额外引入函数调用开销 - 嵌套推导式(如
[[x for x in row] for row in table])优化效果更明显,但要注意:若外层结果被多次遍历,反而可能因失去惰性而增加内存压力
用稀疏结构替代全量字符串缓存
爬虫常把HTML片段、原始响应体、提取的文本字段暂存在内存中做多轮处理。这些字符串多数短小、重复率高,但Python默认为每个str对象分配独立内存块,碎片化严重。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 对高频复用的标签名、属性值、状态码等,用
sys.intern()强制驻留——比如url_path = sys.intern(response.url.path) - 避免把整个HTTP响应体(尤其是大页面)转成
str再切片;改用response.content(bytes)配合memoryview定位关键区域,例如mv = memoryview(response.content); title_start = mv.find(b'<title>') + 7</title> - 自定义URL去重集合时,别直接存完整URL字符串,改用
hashlib.blake2b(url.encode(), digest_size=8).digest()生成8字节指纹,内存占用降为原来的1/10
禁用GC在流式解析阶段的干扰
当爬虫边下载边解析(如用lxml.iterparse或xml.sax)时,频繁创建/销毁Element对象会触发GC分代回收扫描,拖慢吞吐量,甚至引发暂停。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在解析循环开始前执行
gc.disable(),解析完成后再gc.enable()并调用gc.collect(0)清理第0代 - 不要在解析过程中调用
del显式删对象——引用计数机制已足够及时释放;del反而增加字节码指令和GC跟踪负担 - 若使用
BeautifulSoup,务必传parse_only=SoupStrainer("a")限制加载范围,否则整个DOM树即使未用也会占满内存
警惕n_jobs=-1在内存受限场景下的反效果
很多人给concurrent.futures.ThreadPoolExecutor或scrapy.settings里的CONCURRENT_REQUESTS设高值,以为能压榨CPU,结果发现OOM早于吞吐提升。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 线程数上限建议设为
min(32, os.cpu_count() * 2),而非无脑-1——Python 3.12虽优化了线程切换,但每个线程仍需独立栈空间(默认1MB) - 对I/O密集型任务(如HTTP请求),优先用
asyncio而非多线程;Python 3.12中asyncio基准提速75%,且协程共享同一栈,内存更可控 - 若必须用多进程(如解析JS渲染页),用
multiprocessing.Pool时明确传maxtasksperchild=100,防止子进程长期运行后内存泄漏累积
真正卡住爬虫内存的,往往不是单个大对象,而是成千上万个没被及时释放的小对象——比如每次解析都new一个dict存中间结果,又没清空引用。Python 3.12的优化是“加速释放”,不是“代替释放”。


















