如何修复异步请求以太坊地址余额时的RuntimeError等错误?
以太坊地址余额异步请求错误排查与修复
问题背景
同步代码可正常请求以太坊地址余额,但处理1000个地址时性能不足,改用异步实现后出现错误。
同步代码(可正常运行)
import requests import time import pandas as pd start = time.time() df = pd.read_csv('ethereumaddresses.csv') Wallet_Address=(df.loc[:,'Address']) results = [] start = time.time() for address in Wallet_Address: url = f"https://blockscout.com/eth/mainnet/api?module=account&action=eth_get_balance&address={address}" response = requests.get(url) results = response.json() print(results) end = time.time() total_time = end - start print(f"It took {total_time} to make {len(Wallet_Address)} API calls")
异步实现代码(运行报错)
import asyncio import aiohttp import time import pandas as pd start = time.time() df = pd.read_csv('Ethereum/ethereumaddresses.csv') Wallet_Address=(df.loc[:,'Address']) results = [] def get_tasks(session): tasks = [] for address in Wallet_Address: url = f"https://blockscout.com/eth/mainnet/api?module=account&action=eth_get_balance&address={address}" tasks.append(session.get(url,ssl=False)) print(address) return tasks session_timeout = aiohttp.ClientTimeout(total=None) async def get_balances(): async with aiohttp.ClientSession(timeout=session_timeout) as session: tasks = get_tasks(session) responses = await asyncio.gather(*tasks) for response in responses: results.append(await response.json()) asyncio.run(get_balances()) end = time.time() total_time = end - start print(f"It took {total_time} seconds to make {len(Wallet_Address)} API calls")
运行错误信息
RuntimeError: await wasn't used with future _OverlappedFuture exception was never retrieved future: <_OverlappedFuture finished exception=OSError(22, 'The I/O operation has been aborted because of either a thread exit or an application request', None, 995, None)> Traceback (most recent call last): File "AppData\Local\Programs\Python\Python310\lib\asyncio\windows_events.py", line 817, in _poll value = callback(transferred, key, ov) File "AppData\Local\Programs\Python\Python310\lib\asyncio\windows_events.py", line 604, in finish_connect ov.getresult() OSError: [WinError 995] The I/O operation has been aborted because of either a thread exit or an application request
问题分析与修复
核心问题
- 无限制并发请求:一次性发起1000个请求,目标服务器会触发限流或直接拒绝连接,导致I/O操作中断,出现WinError 995。
- 全局变量风险:异步环境下使用全局
results变量,虽单线程异步不会直接冲突,但代码耦合性高、可读性差。 - 超时设置不合理:
total=None意味着请求无超时限制,可能导致请求长期挂起占用资源。 - SSL验证随意关闭:
ssl=False存在安全风险,且多数场景下无需关闭。
修复后的代码
import asyncio import aiohttp import time import pandas as pd async def fetch_balance(session, address, semaphore): url = f"https://blockscout.com/eth/mainnet/api?module=account&action=eth_get_balance&address={address}" async with semaphore: try: async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: response.raise_for_status() # 捕获HTTP状态码错误 return await response.json() except Exception as e: print(f"请求地址 {address} 失败: {str(e)}") return {"address": address, "error": str(e)} async def get_balances(addresses): semaphore = asyncio.Semaphore(20) # 限制同时发起20个请求 async with aiohttp.ClientSession() as session: tasks = [fetch_balance(session, addr, semaphore) for addr in addresses] results = await asyncio.gather(*tasks) return results if __name__ == "__main__": start = time.time() df = pd.read_csv('Ethereum/ethereumaddresses.csv') wallet_addresses = df.loc[:, 'Address'].tolist() results = asyncio.run(get_balances(wallet_addresses)) end = time.time() total_time = end - start print(f"完成 {len(wallet_addresses)} 个API请求,耗时 {total_time:.2f} 秒") # 可选:将结果保存到CSV # pd.DataFrame(results).to_csv('eth_balances.csv', index=False)
关键修改说明
- 并发数控制:用
asyncio.Semaphore限制同时请求数(示例设为20),避免触发服务器限流。 - 独立请求封装:每个地址请求封装为
fetch_balance函数,单独处理异常,确保一个请求失败不影响其他请求。 - 合理超时设置:给每个请求设置10秒超时,防止请求无限挂起。
- 避免全局变量:在函数内部维护结果列表,代码更清晰安全。
- Windows兼容优化:用
if __name__ == "__main__":包裹主逻辑,解决Windows下异步运行的线程退出问题。
内容的提问来源于stack exchange,提问作者ChaosX
相关产品推荐
相关产品推荐

