用生成器替代列表可避免内存暴涨,关键是在请求、解析、提取三层均流式处理,每次yield单条结构化数据而非整页内容,并及时释放临时对象。

为什么 requests + list 会把内存吃光
爬虫一并发几百个请求,再用 list.append() 存所有响应内容,内存暴涨是必然的。不是代码写错了,是数据没流式处理——HTML 文本、JSON 字符串、甚至二进制图片,全堆在内存里等你统一处理。
常见错误现象:MemoryError 出现在解析第 5000 条之后;任务跑着跑着变卡顿,top 看到 Python 进程占 8GB+;用 psutil.Process().memory_info().rss 监控,发现内存只增不减。
- 别用
response.text加list存全文,尤其抓取新闻列表页+详情页这种嵌套结构 - 改用生成器函数 yield 每次只产出一条结构化数据(比如
{'title': ..., 'url': ...}),而不是整页原始 HTML - 如果必须保留原始响应,用
response.raw配合iter_content(chunk_size=8192)流式读,避免一次性.content
用 generator 替换 list 的三个关键位置
生成器不是加个 yield 就完事,得卡在数据真正“成型”的那一刻。多数人只在解析层加生成器,却忘了请求层和提取层同样要流式化。
- 请求层:用
concurrent.futures.ThreadPoolExecutor提交任务,但结果用executor.map()+ 生成器包装,而不是list(executor.map(...)) - 解析层:BeautifulSoup 对象别长期持有,
yield soup.select('h1')[0].get_text()后立刻丢弃soup;用lxml.etree.iterparse()解析大 XML/HTML,比etree.parse()内存低一个数量级 - 提取层:字段提取逻辑封装成独立生成器函数,比如
def extract_items(html): yield {...},别在循环里拼大字典再 append
yield 和 return 在爬虫函数里的实际区别
很多人以为“改成生成器就省内存”,结果发现没变化——因为只是把 return data_list 换成 yield data_list,这等于一次 yield 整个列表,毫无意义。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
正确做法是让每次 yield 只吐出一条原子数据:
def fetch_and_parse(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, 'lxml')
for item in soup.select('.post'):
# ✅ 正确:每次 yield 一个 dict
yield {
'title': item.h2.get_text(),
'link': item.a['href']
}
# ❌ 错误:这里没释放 soup,且 yield 整个列表无意义
# yield [x for x in ...]-
yield后函数暂停,局部变量(如soup)在下次调用前会被 GC 回收——前提是没被闭包捕获 - 避免在生成器里做耗时操作(如写文件、发请求),否则阻塞整个迭代;IO 密集操作应放在调用方,用
for item in fetch_and_parse(urls): save(item) - 生成器本身不执行,直到第一次
next()或进入for循环,调试时记得触发它
requests Session + 生成器组合的坑
复用 Session 能省连接开销,但和生成器混用容易翻车:Session 是有状态的,而生成器可能跨多次调用,中间夹杂重定向、cookie 更新、连接池复用等隐式行为。
- 别在生成器函数里创建
Session实例并反复复用——不同 URL 可能需要不同 headers 或 auth,状态污染风险高 - 更安全的做法:把
Session当参数传入,或在最外层管理生命周期,生成器只负责解析逻辑 - 注意
Response对象的connection属性默认是惰性关闭的,配合生成器时建议显式resp.close(),尤其在 yield 后立即关闭 - 若用
stream=True,务必确保yield后响应体已读完(比如用resp.iter_content()),否则连接会一直占着不放
生成器本身不解决网络延迟或反爬压力,但它把“内存是否爆掉”这个变量,从“看运气”变成了“可预测”。最容易被忽略的是:生成器函数内部的临时对象(比如 BeautifulSoup 实例、正则匹配结果)是否真被及时释放——这取决于你 yield 的粒度,以及有没有意外持有引用。

















