最常见且最危险的内存泄漏点是response对象被全局变量或闭包长期持有:存入模块级list、类属性、闭包变量或未清空缓存字典后,其引用计数永不归零,尤其含body时每个响应占几mb;修复需显式提取字段后丢弃response,而非依赖gc。

response对象被全局变量或闭包长期持有
这是最常见也最危险的泄漏点:只要把 response 对象(尤其是含 response.body 的)存进模块级 list、类属性、闭包变量或未清空的缓存字典,它就彻底脱离作用域,引用计数永不归零。
-
self.items.append(response)这类写法等于把所有 HTML 原文钉在内存里,每个响应可能占几 MB,100 个就是几百 MB - Scrapy 的
response默认不自动释放 body;requests.Response更严重——response.content和response.text都是强引用,且response.history可能链式持有多个响应 - 修复方式不是等 GC,而是立刻切断引用:用完后显式调用
response.release()(仅限 aiohttp backend),或直接提取需要的字段(如response.css(...).getall())后丢弃整个 response
tracemalloc 快照对比定位高增长代码行
tracemalloc 是 Python 3.4+ 内置工具,轻量、无需安装,但必须“对比拍”,单次快照毫无意义。
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
- 在爬虫主循环开始前调用
tracemalloc.start() - 每处理 50–100 个请求后执行
snapshot = tracemalloc.take_snapshot(),保存到列表 - 用
snapshot.compare_to(prev_snapshot, "lineno")找出新增分配最多的行——重点关注list.append、dict.__setitem__、lxml.etree.fromstring、re.compile - 别只看第三方库路径,要顺藤摸瓜找到你自己的哪一行调用了它们
gc.DEBUG_UNCOLLECTABLE 暴露循环引用卡死对象
当 gc.collect() 返回 0 却内存持续上涨,大概率是对象进了 gc.garbage 列表——它们因循环引用 + __del__ 被 GC 归为 “uncollectable”,永远卡住。
- 启动时加
gc.set_debug(gc.DEBUG_UNCOLLECTABLE),控制台会打印所有无法回收的对象类型和数量 - 典型场景:自定义中间件类同时持有了
request和response,且类中定义了__del__ - 修复不是删掉
__del__,而是改用weakref.finalize(obj, callback)替代,避免参与循环引用判定 - 用
gc.get_referents(obj)追查谁还在强引用着那个卡死对象
Scrapy 用户最容易忽略的三个默认陷阱
Scrapy 表面封装了调度逻辑,但以下三点完全依赖开发者主动干预:
-
CLOSESPIDER_PAGECOUNT和CLOSESPIDER_ITEMCOUNT不是内存保护机制,只是终止条件;即使设了,泄漏仍会在终止前累积 - pipeline 中若用
self.cache.append(item)缓存再批量入库,必须配套if len(self.cache) >= 100: self.flush(); self.cache.clear(),否则缓存无限膨胀 - Downloader Middleware 里若用类属性缓存 session 或连接池,需确认是否设置了最大 size 或 TTL 清理策略;否则
requests.Session实例会越积越多
del 的局部变量、一个带 __del__ 的中间件类,它们在长时间运行中悄悄叠加,直到某次 GC 失效后突然 OOM。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










