aiohttp.ClientSession()对象复用正确性验证及代码优化问询
核心验证结论
针对你关心的两个会话复用问题,结论如下:
- 代码运行全程仅创建了1个
aiohttp.ClientSession实例:会话的实例化逻辑写在async with语句行,位于遍历批次的for循环外层,只会在进入上下文时执行1次,不会在循环迭代中重复创建会话。 - 会话全程保持打开、跨迭代复用:
async with的上下文生命周期覆盖了整个批量请求逻辑——从进入块创建session开始,到所有批次的请求发送、响应读取、循环执行完毕后,才会触发会话的关闭清理逻辑。循环处理每个分组的过程中,始终复用同一个session对象,完全符合你的设计预期。
可优化的最佳实践
你当前的代码可以正常运行,但存在几处不符合asyncio/aiohttp规范、可提升性能和稳定性的点:
- 清理冗余导入:开头
from asyncio import tasks属于无用导入,代码中没有用到对应模块,可直接删除。 - 避免全局变量:当前存储结果的
data是全局变量,异步场景下容易出现状态污染问题,建议将其定义在main函数内部作为局部变量,最终通过返回值传递结果。 - 正确释放响应资源:你当前直接对
session.get()的返回值调用await、读取text,没有显式管理响应的生命周期。session.get()返回的是异步上下文管理器而非原生协程,不通过async with包裹的话,容易出现连接泄漏,耗尽会话的连接池。 - 替换过时的事件循环写法:
asyncio.get_event_loop()+loop.run_until_complete()是Python3.7版本之前的旧API,现在直接使用asyncio.run()即可启动异步主函数,不需要手动操作事件循环,写法更简洁且兼容性更好。 - 替换手动分批的并发控制逻辑:你手动拆分列表控制并发度的逻辑可运行,但灵活性差,调整并发数需要修改分批逻辑。推荐使用
asyncio.Semaphore实现全局并发数控制,不需要手动拆分URL列表,代码可维护性更高。 - 补充异常处理:当前代码没有单请求级别的异常捕获,只要某一个URL请求超时、返回错误状态码,整个
asyncio.gather就会直接抛出异常中断所有任务。建议给单个请求增加try/except逻辑,避免单请求失败拖垮整个批量任务。 - 显式配置超时:默认的aiohttp请求超时时间较长(5分钟),建议根据业务场景显式配置
ClientTimeout,避免异常请求长时间占用连接资源。
优化后参考代码
import aiohttp import asyncio websites = [ 'http://corndog.io/', 'https://onesquareminesweeper.com/', 'https://checkboxolympics.com/', 'https://binarypiano.com/', 'https://alwaysjudgeabookbyitscover.com/', 'https://cant-not-tweet-this.com/', 'https://cursoreffects.com/', 'http://eelslap.com/', 'https://smashthewalls.com/', 'https://thatsthefinger.com/' ] # 并发数配置,和你原代码每批3个的并发度保持一致 CONCURRENT_LIMIT = 3 async def single_fetch(session, sem, url): # 信号量控制全局并发数 async with sem: try: # 显式管理响应上下文,确保连接正常释放 async with session.get( url, ssl=False, timeout=aiohttp.ClientTimeout(total=10) ) as resp: # 对4xx/5xx状态码主动抛出异常 resp.raise_for_status() page_text = await resp.text() print(f"抓取成功: {url}, 页面长度: {len(page_text)}") return page_text except Exception as e: print(f"抓取失败: {url}, 错误信息: {str(e)}") return None async def main(url_list): result = [] # 初始化并发信号量 sem = asyncio.Semaphore(CONCURRENT_LIMIT) # 整个请求生命周期复用同一个ClientSession async with aiohttp.ClientSession() as session: # 批量创建任务,无需手动分批 all_tasks = [single_fetch(session, sem, url) for url in url_list] task_results = await asyncio.gather(*all_tasks) # 过滤抓取失败的空结果 result = [item for item in task_results if item is not None] return result if __name__ == "__main__": final_data = asyncio.run(main(websites)) print(f"批量抓取完成,共成功获取{len(final_data)}个页面内容")
内容的提问来源于stack exchange,提问作者Lewis Donna
相关产品推荐
相关产品推荐

