优化yfinance批量获取标普500个股信息的运行性能
问题说明
需求为获取全部标普500成分股列表,拉取每只股票约170个字段的财务信息存入DataFrame,现有代码运行速度极慢,核心卡点为遍历单只股票时逐次发起独立API请求。原本预期yf.Tickers(sp500)可一次性批量拉取所有股票信息,但实际未达到预期效果。
原始代码
import pandas as pd import yfinance as yf import json import datapackage data_url = 'https://datahub.io/core/s-and-p-500-companies/datapackage.json' # 加载Data Package package = datapackage.Package(data_url) # 仅加载表格类型数据 resources = package.resources for resource in resources: if resource.tabular: data = pd.read_csv(resource.descriptor['path']) spSymbols=data['Symbol'] sp500 ="" for i in spSymbols: sp500= sp500 + i +" " tickers= yf.Tickers(sp500) dfStockInfo =[] metricList=[] # 性能瓶颈位置 **** for i in spSymbols: stock = tickers.tickers.get(i) stockInfo= stock.info metricList.append(stockInfo)
核心原因说明
yf.Tickers本质只是多标的的容器对象,初始化阶段不会预拉取任何标的的属性数据,只有访问单个标的的.info/.history等属性时,才会实时发起独立HTTP请求。逐次遍历500只股票串行请求,网络IO等待 占了总耗时的90%以上,是最核心的性能瓶颈,和逐个初始化yf.Ticker拉取数据没有效率差异。
优化方案
方案1:多线程并发请求(改动最小,适配全量info字段需求)
针对需要拉取全量170个info字段的场景,用多线程把串行的网络请求改成并发执行,把网络等待时间重叠,整体速度可提升5~10倍,注意控制并发数避免触发接口限流。
import pandas as pd import yfinance as yf from concurrent.futures import ThreadPoolExecutor, as_completed import datapackage # 拉取标普500成分股列表 data_url = 'https://datahub.io/core/s-and-p-500-companies/datapackage.json' package = datapackage.Package(data_url) sp500_df = None for resource in package.resources: if resource.tabular: sp500_df = pd.read_csv(resource.descriptor['path']) sp_symbols = sp500_df['Symbol'].tolist() def fetch_single_stock_info(symbol: str) -> dict | None: try: # 加超时设置避免单只股票请求卡死 ticker = yf.Ticker(symbol) info = ticker.info return info if info and "symbol" in info else None except Exception as e: print(f"拉取{symbol}数据失败: {str(e)}") return None if __name__ == "__main__": metric_list = [] # 并发数建议设置为10~20,过高容易触发雅虎接口限流封禁 max_concurrent = 15 with ThreadPoolExecutor(max_workers=max_concurrent) as executor: task_map = {executor.submit(fetch_single_stock_info, sym): sym for sym in sp_symbols} for task in as_completed(task_map): res = task.result() if res: metric_list.append(res) # 转换为DataFrame stock_info_df = pd.DataFrame(metric_list)
方案2:使用内置批量接口(适配非全量字段场景,速度最快)
如果不需要全量170个info字段,仅需要行情、估值、基础交易类指标,可直接使用yf.download内置批量接口,该接口本身为批量请求设计,仅需数次请求即可拉取全量标的数据,速度远高于逐次拉取info,但缺点是覆盖字段较少。
# 批量拉取标的行情数据,开启内置多线程 batch_price_data = yf.download( tickers=sp_symbols, period="1y", group_by="ticker", threads=True )
额外优化建议
- 增加本地缓存:可对yfinance的HTTP请求增加本地缓存,重复调试脚本时不需要重复拉取已请求过的数据,大幅降低调试等待时间
- 持久化拉取结果:每次拉取完成后将DataFrame存储为本地parquet/csv文件,避免每次运行脚本都重新发起全量请求
- 增加重试逻辑:针对网络波动导致的请求失败,可增加简单的重试机制,减少数据缺失
内容的提问来源于stack exchange,提问作者javadev
相关产品推荐
相关产品推荐

