Python使用ThreadPoolExecutor多线程爬虫内存不足进程被Kill问题求助
问题核心原因
futures_done集合只增不减:调用future.result()后未将对应future从集合中删除,数百万个已处理的future对象长期驻留内存,最终占满资源。- 遍历逻辑效率极低:每次新增已完成任务时都会全量遍历
futures_done集合,集合规模越大遍历耗时越高,直接导致程序运行速度持续下降。 - 任务队列无限制膨胀:现有逻辑无法限制待执行任务的总量,提交任务的速度远快于任务处理速度,大量待执行任务持有host等资源持续占用内存。
- 资源未及时释放:requests响应对象、lxml解析树没有主动释放,高并发场景下GC回收不及时会进一步拉高内存占用。
优化方案
- 处理完单个future后立即将其从集合中移除,避免无效对象堆积
- 限制待执行任务的数量,保持待处理任务数和工作线程数处于同一量级,不要一次性提交数百万个任务
- 无需累计到1000个才处理结果,每完成一个任务就直接获取结果释放资源
- 用
concurrent.futures.as_completed替代手动维护两个future集合,简化逻辑降低出错概率 - 可选优化:换用异步HTTP库(如httpx、aiohttp)替代多线程+requests,内存占用会低很多
修正后可运行代码
import sys import requests import concurrent.futures import urllib3 from lxml.html import fromstring urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def title(host): try: url = "https://" + host # 增加stream=True减少大响应内存占用 r = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=3, verify=False, stream=True) tree = fromstring(r.content.decode('utf-8', errors='ignore')) title = tree.findtext('.//title') res = f"{host}: {title}" # 主动释放资源 del tree, r return res except: return None if __name__ == "__main__": max_workers = int(sys.argv[2]) with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {} with open(sys.argv[1], 'r', encoding='utf-8') as f: for line in f: host = line.strip() if not host: continue # 限制待执行任务数不超过工作线程数的2倍,避免队列膨胀 while len(futures) >= max_workers * 2: # 等待任意一个任务完成 done, _ = concurrent.futures.wait(futures.keys(), return_when=concurrent.futures.FIRST_COMPLETED) for future in done: res = future.result() if res: print(res) # 完成后立即删除future释放内存 del futures[future] # 提交新任务 future = executor.submit(title, host) futures[future] = host # 处理剩余所有未完成的任务 for future in concurrent.futures.as_completed(futures.keys()): res = future.result() if res: print(res)
内容的提问来源于stack exchange,提问作者stackingbugs
相关产品推荐
相关产品推荐

