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

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'])))
---&gt; 56 asyncio.run(main())

File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/nest_asyncio.py:35, in _patch_asyncio.&lt;locals&gt;.run(main, debug)
     33 task = asyncio.ensure_future(main)
     34 try:
---&gt; 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.&lt;locals&gt;.run_until_complete(self, future)
     87 if not f.done():
     88     raise RuntimeError(
     89         'Event loop stopped before Future completed.')
---&gt; 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:
--&gt; 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.
--&gt; 256         result = coro.send(None)
    257     else:
    258         result = coro.throw(exc)

Cell In [52], line 53, in main()
     52 async def main():
---&gt; 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
错误原因
  1. 函数存在无返回值分支:get_history函数中,当r['queryCount'] < 2或r == None时,仅执行pass没有返回任何值,Python会默认返回None。
  2. 异步结果包含无效值:asyncio.gather会收集所有任务的返回结果,其中混入了None。当用zip(*...)尝试解包这些结果时,None不是可迭代对象,直接触发TypeError。
  3. 同步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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:40:17