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

创建新aiohttp.ClientSession仍报'Session is closed'错误求助

问题分析与解决方案

问题根源

你遇到的Session is closed错误,核心原因是全局定义的TCPConnector被多个ClientSession共享,第一个session关闭时会连带关闭这个connector,后续新建的session使用已关闭的connector就会触发错误。

看你的代码,connector = aiohttp.TCPConnector(ssl=ssl_context)是全局变量,每次调用compile_calls_get且ssl_verify=True时,都会把这个全局connector传给新的ClientSession。而async with aiohttp_session as session:结束时,session会自动关闭,同时会关闭它使用的connector(默认share_connector=False,即session独占connector)。所以第一次请求后,这个全局connector已经被关闭,第二次重试时新建的session用这个connector,自然就报session closed的错。

解决方案

方案1:每次创建session时新建专属connector

把全局的connector移到compile_calls_get函数内部,每次新建session时都创建对应的connector,避免复用已关闭的实例:

async def compile_calls_get(url_set: list,
                            headers: Optional[dict] = None,
                            ssl_verify: bool = False) -> tuple:
    if ssl_verify:
        ssl_context = ssl.create_default_context(cafile=creds.PROXY_PATH)
        connector = aiohttp.TCPConnector(ssl=ssl_context)
        async with aiohttp.ClientSession(headers=headers, connector=connector) as session:
            extracted_data = await asyncio.gather(*[fetch_get(url=url, session=session) for url in url_set])
        return extracted_data
    else:
        connector = aiohttp.TCPConnector(ssl=False)
        async with aiohttp.ClientSession(headers=headers, connector=connector) as session:
            extracted_data = await asyncio.gather(*[fetch_get(url=url, session=session) for url in url_set])
        return extracted_data

这样每次请求都会创建全新的connector和session,互相独立,不会出现之前的关闭冲突。

方案2:让connector被多个session共享(设置share_connector=True)

如果想复用connector来提升性能,可以在创建ClientSession时指定share_connector=True,这样session关闭时不会关闭connector,直到所有使用它的session都关闭后再手动关闭:

# 保留全局connector
ssl_context = ssl.create_default_context(cafile=creds.PROXY_PATH)
connector = aiohttp.TCPConnector(ssl=ssl_context)
proxy = "example_proxy"

async def compile_calls_get(url_set: list,
                            headers: Optional[dict] = None,
                            ssl_verify: bool = False) -> tuple:
    if ssl_verify:
        async with aiohttp.ClientSession(headers=headers, connector=connector, share_connector=True) as session:
            extracted_data = await asyncio.gather(*[fetch_get(url=url, session=session) for url in url_set])
        return extracted_data
    else:
        connector_no_ssl = aiohttp.TCPConnector(ssl=False)
        async with aiohttp.ClientSession(headers=headers, connector=connector_no_ssl, share_connector=True) as session:
            extracted_data = await asyncio.gather(*[fetch_get(url=url, session=session) for url in url_set])
        return extracted_data

# 注意:程序结束时需要手动关闭全局connector
# 比如在extract_data_asynchronously最后或者调用结束后加:
# asyncio.run(connector.close())

不过这种方式需要注意在程序生命周期结束时手动关闭connector,避免资源泄漏。

额外优化:给请求添加内置重试逻辑

现在的重试是把所有失败的URL收集后重新批量请求,其实可以在fetch_get函数内部直接添加重试逻辑,不需要外层循环,代码更简洁:

async def fetch_get(url: str, session: aiohttp.ClientSession, max_retries: int = 3) -> Any:
    for attempt in range(max_retries + 1):
        try:
            async with session.get(url=url, proxy=proxy) as response:
                if 500 <= response.status < 600:
                    if attempt == max_retries:
                        return {"success": response.status, "url": url, "data": None}
                    await asyncio.sleep(2 ** attempt)  # 指数退避,避免频繁请求
                    continue
                data = await response.text()
                return {"success": response.status, "url": url, "data": data}
        except aiohttp.ClientError as e:
            if attempt == max_retries:
                return {"success": -1, "url": url, "data": str(e)}
            await asyncio.sleep(2 ** attempt)

这样每个请求会自动重试指定次数,外层只需要一次批量请求即可,减少循环逻辑。

内容的提问来源于stack exchange,提问作者Lazar Jovanovic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 02:20:44