批量获取股票行情时,总有一部分请求会失败——网络抖动、限流、股票退市、代码格式错误,原因多种多样。如果把失败和成功的数据混在一起处理,后续的选股、回测或监控结果就会被悄悄污染。
本文要解决的问题很简单:批量行情拿回来之后,怎样可靠地把失败股票从正常数据里挑出来,分别处理。
核心结论是:不要依赖返回码或单个 try/except,而是根据数据返回的结构、请求的明细日志和独立的失败标记,把成功与失败分开记录。下面从最常用的方法开始,逐步增加工程上的严谨性。
这是最直接的方法。大多数行情 API 在批量请求时,对每个标的都会返回一个独立的结果或错误信息。
以 QuantDash 的批量 K 线接口为例,klines.batch 返回一个以标的代码为 key 的字典。如果某个标的查询失败,对应的 key 可能缺失或返回空 DataFrame:
import pandas as pd
from quantdash import QuantDash
qd = QuantDash()
symbols = ["600519.SH", "000001.SZ", "INVALID.CODE", "300750.SZ"]
dfs = qd.klines.batch(
symbols,
period="1d",
count=100,
adjust="forward",
to_dataframe=True,
show_progress=True,
)
# 分离成功与失败
success = {}
failed = []
for sym in symbols:
df = dfs.get(sym)
if df is not None and isinstance(df, pd.DataFrame) and not df.empty:
success[sym] = df
else:
failed.append(sym)
print(f"成功获取: {len(success)} 只")
print(f"失败标的: {failed}")要点在于:不要假设所有请求都会返回有效 DataFrame。批量接口返回的字典可能包含空值、None 或空表,逐一检查才是可靠的做法。
如果使用的是 REST 接口而非 SDK,返回结构通常是:
{
"data": {
"600519.SH": {...},
"000001.SZ": null,
"INVALID.CODE": {"error": "symbol not found"}
}
}处理逻辑也一样:遍历请求时的标的列表,而不是遍历返回的 key 集合。因为失败标的可能根本不会出现在返回的 key 中。
response = response.get("data", {})
for sym in requested_symbols:
item = response.get(sym)
if item and not isinstance(item, dict) or "error" in item:
failed.append(sym)
else:
success.append(sym)遍历请求列表,而不是返回列表——这是最容易忽略的原则。
当单个请求失败抛出异常时,可以按照异常类型和标的代码分别记录。这种方法适用于那些不提供批量接口、需要逐只请求的场景:
from requests.exceptions import RequestException, Timeout, HTTPError
symbols = ["600519.SH", "000001.SZ", "300750.SZ", "688981.SH"]
success = {}
failed = []
for sym in symbols:
try:
df = fetch_single_kline(sym) # 自定义的单个标的获取函数
if df is not None and not df.empty:
success[sym] = df
else:
failed.append((sym, "empty_result"))
except Timeout:
failed.append((sym, "timeout"))
except HTTPError as e:
if e.response.status_code == 429:
failed.append((sym, "rate_limited"))
elif e.response.status_code == 403:
failed.append((sym, "no_permission"))
else:
failed.append((sym, f"http_{e.response.status_code}"))
except RequestException as e:
failed.append((sym, "network_error"))把失败原因也记录下来,远比一个布尔值有用。后续重试时可以按类型决定策略:限流就等待、权限问题就跳过、超时就重试。
常见的写法是:
try:
data = get_batch_data(symbols)
return data
except Exception:
return None这种写法让调用者完全不知道哪些标的成功、哪些失败。如果只想重试失败的几只,就必须重新请求全部标的——既浪费配额又浪费时间。
当没有明确的错误返回,也没有异常可捕获时(例如服务端静默返回了过期数据),就需要靠请求日志来判断。
具体做法是:发起批量请求前,先记录本次请求的标的列表、时间和批次号;收到返回后,比对实际返回的标的与请求列表的差异:
import logging
from datetime import datetime, timezone
logger = logging.getLogger("market_data")
def fetch_and_track(symbols, batch_id=None):
if batch_id is None:
batch_id = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")
logger.info(
"BATCH_START id=%s symbols=%d first5=%s",
batch_id, len(symbols), symbols[:5]
)
result = batch_request(symbols)
returned_symbols = set(result.keys())
requested_set = set(symbols)
missing = requested_set - returned_symbols
extra = returned_symbols - requested_set
if missing:
logger.warning(
"BATCH_MISSING id=%s count=%d symbols=%s",
batch_id, len(missing), sorted(missing)
)
if extra:
logger.warning(
"BATCH_EXTRA id=%s count=%d symbols=%s",
batch_id, len(extra), sorted(extra)
)
return result, list(missing)这种方法在排查数据缺口时尤其有用。几周后回查日志,可以直接定位到某次批量请求到底少了哪些标的,而不是从头重跑所有任务。
方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
按返回结构区分 | 使用 SDK 或结构清晰的 REST 接口 | 简单、直观,不需要改调用方式 | 依赖 API 的返回格式稳定性 |
异常捕获分桶 | 逐只请求或批量接口不返回明细错误 | 错误原因明确,便于分类重试 | 代码量稍大,需要维护异常映射 |
请求日志重建 | 需要长期追溯或排查历史数据缺口 | 可审计、可追溯,适合生产环境 | 需要额外的日志存储和分析工具 |
实际项目中,方法一和方法三通常一起使用:方法一做实时分离,方法三做事后审计。方法二则在处理零散请求或第三方不稳定接口时作为补充。
Q:批量请求中部分失败,应该全部重试还是只重试失败的?
只重试失败的。全部重试不仅浪费配额,还可能把成功的数据覆盖成旧版本(如果接口的返回顺序不固定)。重试前建议加一个短延迟,避免刚释放限流又撞上。
Q:失败股票重试几次后仍失败,应该跳过还是终止整个任务?
取决于下游任务。如果是历史补库,建议跳过并记录,等全部跑完再集中处理失败的标的;如果是盘中实时监控,连续失败几次后应该报警而不是静默跳过,让人工判断是否需要介入。
Q:API 返回了 HTTP 200,但某个标的数据是空的,算失败吗?
算。HTTP 状态码表示请求本身成功了,但业务数据为空,对下游来说和失败没有区别。应该按照业务条件来判定成功与否,而不是仅靠 HTTP 状态码。
Q:空 DataFrame 和 None 分别怎么处理?
两者都应该归入失败。空 DataFrame 意味着 API 认为该标的当前没有数据(可能停牌、退市或不在交易时段),None 往往表示服务端无法处理该请求。实践中把两者都归入失败清单,但可以附加不同的失败原因标签,便于后续区别处理。
Q:频繁出现批量请求失败,问题更可能出在哪里?
先排查标的代码格式是否正确(后缀、大小写、空格),其次是 API Key 的权限范围是否覆盖了目标市场,最后才是服务端稳定性。代码格式错误是最高频的原因,排查顺序不要搞反。
批量行情请求不可能百分之百成功。真正的工程能力不在于避免失败,而在于失败发生后能精确知道哪些标的失败了、失败原因是什么,以及如何不把失败数据带进下游分析。
核心做到三点:
做好分离之后,后续的重试、补查、报警和审计才有可靠的基础。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。