Python异步Zip&Map调用Polygon API遇NoneType不可迭代错误求助
问题描述
尝试通过Polygon.io接口获取股票除息日(ex-dividend date)及除息日前一交易日(prior business date)的历史股价,并映射到Pandas DataFrame的新列中。已通过BDay工具处理前一交易日为假期的情况,同时过滤Polygon返回结果不足2条的情况,但运行代码时在zip与await异步调用部分出现TypeError: 'NoneType' object is not iterable错误。
相关代码:
from datetime import datetime from pandas.tseries.offsets import BDay async def get_history(ticker, prior_business_date, ex_dividend_date): ''' Receives the ticker, prior business date and ex-dividend date from the caller as a string First checks to see if the prior business date (date before Ex-Dividend is a holidy If the date is a holiday, subtracts one day from prior business date Function then assigns to value x the prior candlestick date for the corrected date If the date IS NOT a holiday, the function gets the prior candlestick data based on the prior business date Function also returns candlestick data on the ex-dividend date before returning asynchronously to the caller ''' if prior_business_date in get_trading_close_holidays(int(prior_business_date.split('-')[0])): #set the prior_business date to datetime object #deduct 1 day from prior_business_date to get useable data prior_business_date = datetime.strftime(datetime.strptime(prior_business_date,'%Y-%m-%d').date() - BDay(1), '%Y-%m-%d') #print(prior_business_date) #debug url = 'https://api.polygon.io/v2/aggs/ticker/{}/range/1/day/{}/{}?sort=dsc&limit=10'.format(ticker, prior_business_date, ex_dividend_date) r = requests.get(url, headers=headers).json() #print(r['ticker'], r['queryCount']) #debug if (r['queryCount'] < 2) or (r == None): pass else: #print(type(r)) #debug x = r['results'][0]['c'] #first data set in results is prior date; get only close y = r['results'][1]['c'] #second data set in results is ex-dividend date get only close #print(type(x),type(y)) #debug return x, y async def main(): high_volume['prior_biz_close'], high_volume['xdiv_close'] = zip(*await asyncio.gather(*map(get_history,high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date']))) asyncio.run(main())
报错堆栈:
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In [52], line 56 53 high_volume['prior_biz_close'], high_volume['xdiv_close'] = zip(*await asyncio.gather(*map(get_history,high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date']))) 54 #*(get_history(x,y,z) for x,y,z in (df['ticker'], df['prior_business_date'], df['ex_dividend_date']))) ---> 56 asyncio.run(main()) File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/nest_asyncio.py:35, in _patch_asyncio.<locals>.run(main, debug) 33 task = asyncio.ensure_future(main) 34 try: ---> 35 return loop.run_until_complete(task) 36 finally: 37 if not task.done(): File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/nest_asyncio.py:90, in _patch_loop.<locals>.run_until_complete(self, future) 87 if not f.done(): 88 raise RuntimeError( 89 'Event loop stopped before Future completed.') ---> 90 return f.result() File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/futures.py:201, in Future.result(self) 199 self.__log_traceback = False 200 if self._exception is not None: --> 201 raise self._exception 202 return self._result File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py:256, in Task.__step(***failed resolving arguments***) 252 try: 253 if exc is None: 254 # We use the `send` method directly, because coroutines 255 # don't have `__iter__` and `__next__` methods. --> 256 result = coro.send(None) 257 else: 258 result = coro.throw(exc) Cell In [52], line 53, in main() 52 async def main(): ---> 53 high_volume['prior_biz_close'], high_volume['xdiv_close'] = zip(*await asyncio.gather(*map(get_history,high_volume['ticker'], high_volume['prior_business_date'], high_volume['ex_dividend_date']))) TypeError: 'NoneType' object is not iterable
错误原因
- 函数存在无返回值分支:
get_history函数中,当r['queryCount'] < 2或r == None时,仅执行pass没有返回任何值,Python会默认返回None。 - 异步结果包含无效值:
asyncio.gather会收集所有任务的返回结果,其中混入了None。当用zip(*...)尝试解包这些结果时,None不是可迭代对象,直接触发TypeError。 - 同步IO阻塞异步循环:使用同步的
requests.get在异步函数中,会阻塞事件循环,完全失去异步编程的优势;同时API请求失败时,r.json()可能抛出异常或返回不包含queryCount的字典,导致后续代码报错。
解决方案
1. 给函数所有分支添加明确返回值
确保无论是否符合条件,函数都返回二元组,避免出现None。例如不符合条件时返回(None, None),后续可在DataFrame中处理空值。
2. 替换同步requests为异步aiohttp
避免阻塞事件循环,提升异步代码效率。
3. 增强API请求容错处理
检查请求状态码、返回数据完整性,避免因API返回异常导致的键错误或其他问题。
修改后的代码示例
import asyncio import aiohttp from datetime import datetime from pandas.tseries.offsets import BDay async def get_history(ticker, prior_business_date, ex_dividend_date, headers): # 处理节假日调整 if prior_business_date in get_trading_close_holidays(int(prior_business_date.split('-')[0])): prior_date = datetime.strptime(prior_business_date, '%Y-%m-%d').date() - BDay(1) prior_business_date = prior_date.strftime('%Y-%m-%d') url = ( f'https://api.polygon.io/v2/aggs/ticker/{ticker}/range/1/day/' f'{prior_business_date}/{ex_dividend_date}?sort=dsc&limit=10' ) async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: # 检查请求是否成功 if resp.status != 200: return None, None # 捕获JSON解析异常 try: r = await resp.json() except Exception: return None, None # 检查返回数据是否有效 if not isinstance(r, dict) or r.get('queryCount', 0) < 2 or 'results' not in r: return None, None # 确保结果数量足够 if len(r['results']) < 2: return None, None x = r['results'][0]['c'] y = r['results'][1]['c'] return x, y async def main(): # 替换为你的Polygon API密钥 headers = {'Authorization': 'Bearer YOUR_API_KEY'} # 生成异步任务列表 tasks = [ get_history(row['ticker'], row['prior_business_date'], row['ex_dividend_date'], headers) for _, row in high_volume.iterrows() ] # 执行所有任务并收集结果 results = await asyncio.gather(*tasks) # 将结果拆分存入DataFrame high_volume['prior_biz_close'] = [res[0] for res in results] high_volume['xdiv_close'] = [res[1] for res in results] # Jupyter Notebook需适配嵌套事件循环 import nest_asyncio nest_asyncio.apply() asyncio.run(main())
额外说明
- 后续可通过
high_volume.dropna(subset=['prior_biz_close', 'xdiv_close'])过滤获取失败的行。 - 使用
iterrows()生成任务列表比map更直观,便于调试。
内容的提问来源于stack exchange,提问作者Fergus
相关产品推荐
相关产品推荐

