You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:01:13