fix(data): akshare top_holders Length mismatch bug 根治
bug 根因 (实证 akshare 1.18.x stock_gdfx_em.py:418):
报告期未披露 (如 sz002673 的 20260630 在 2026-07-26 还未发) 时,
东财 PageSDLTGD 接口返 sdltgd=[], akshare pd.DataFrame([])+reset_index()
得 1 列 df, 然后 columns=[12 列] 抛
ValueError('Length mismatch: Expected axis has 1 elements, new values have 12')
这是确定性无数据 (非瞬时故障), 但 call_ak_with_retry 当网络错重试 3 次
(14s 退避) + 噪声 ERROR 日志, 浪费时间且消耗断路器配额。
修复:
新增 _safe_top_10_em 包装 ak.stock_gdfx_free_top_10_em: 子串匹配
'Length mismatch' (pandas 错误信息稳定) → 返空 df 带 8 列 schema
(TOP_HOLDERS_COLUMNS), 不抛; 其他 ValueError/ConnectionError 透传重试。
fetch_top_holders_one_period 改调此包装。
验证:
- Mac: sz002673/20260630 从 ~14s (3 重试) 降到 0.04s, 返 0×8 空 df
- 有效期 sz002673/20251231, sh600519/20250930 正常返 10×8 数据
- 9/9 新单测通过 (tests/data_platform/test_top_holders_parse.py)
This commit is contained in:
@@ -463,13 +463,53 @@ def fetch_financial_abstract(symbol: str) -> pd.DataFrame:
|
||||
|
||||
# ======================== top_holders (per-stock × per-period) ========================
|
||||
|
||||
# akshare stock_gdfx_free_top_10_em 成功时返回的 8 列 schema
|
||||
# (源码 stock_gdfx_em.py 实测). 报告期未披露 (sdltgd=[]) 时 akshare 抛
|
||||
# ValueError("Length mismatch"), 这里捕获后用此 schema 返空 df,
|
||||
# 保证 parquet 列与有效期一致 (downstream reader 不会列数漂移).
|
||||
TOP_HOLDERS_COLUMNS = [
|
||||
"名次", "股东名称", "股东性质", "股份类型",
|
||||
"持股数", "占总流通股本持股比例", "增减", "变动比率",
|
||||
]
|
||||
|
||||
|
||||
def _safe_top_10_em(symbol: str, date: str) -> pd.DataFrame:
|
||||
"""akshare stock_gdfx_free_top_10_em 包装: 容忍空 sdltgd 响应。
|
||||
|
||||
bug 根因 (实证 akshare 1.18.x stock_gdfx_em.py):
|
||||
报告期未披露 (如当年 Q2 季报未发) 时东财 PageSDLTGD 接口返 sdltgd=[],
|
||||
akshare pd.DataFrame([]).reset_index() 得 1 列 df, 然后 columns=[12 列]
|
||||
抛 ValueError("Length mismatch: Expected axis has 1 elements, new values have 12").
|
||||
这是确定性无数据 (非瞬时故障), 但 call_ak_with_retry 会当网络错重试 3 次
|
||||
(14s 退避) + 噪声 ERROR 日志。
|
||||
|
||||
本包装预判该特定 ValueError (子串匹配 pandas 错误信息, 稳定):
|
||||
- Length mismatch → 返空 df (带 TOP_HOLDERS_COLUMNS schema), 不抛
|
||||
- 其他 ValueError / ConnectionError → 透传给 call_ak_with_retry 走重试
|
||||
"""
|
||||
try:
|
||||
return ak.stock_gdfx_free_top_10_em(symbol=symbol, date=date)
|
||||
except ValueError as e:
|
||||
if "Length mismatch" in str(e):
|
||||
logger.debug(
|
||||
"stock_gdfx_free_top_10_em(%s, %s) Length mismatch → sdltgd 空 (报告期未披露), 返空 df",
|
||||
symbol, date,
|
||||
)
|
||||
return pd.DataFrame(columns=TOP_HOLDERS_COLUMNS)
|
||||
raise
|
||||
|
||||
|
||||
def fetch_top_holders_one_period(
|
||||
symbol: str, period: str,
|
||||
) -> pd.DataFrame:
|
||||
"""stock_gdfx_free_top_10_em(symbol='sh600519', date='20250930') 单期。
|
||||
symbol 小写前缀, period YYYYMMDD."""
|
||||
symbol 小写前缀, period YYYYMMDD.
|
||||
|
||||
通过 _safe_top_10_em 包装: 报告期未披露 (sdltgd=[]) 时 akshare 抛
|
||||
Length mismatch ValueError, 这里捕获返空 df (避免 3 次无意义重试, 确定性
|
||||
无数据不应消耗断路器配额)。"""
|
||||
return _df_or_empty(call_ak_with_retry(
|
||||
ak.stock_gdfx_free_top_10_em,
|
||||
_safe_top_10_em,
|
||||
f"top_holders/{symbol}/{period}",
|
||||
symbol=symbol, date=period,
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user