ZhuLinsen/daily_stock_analysis · error · DataFetchError

Akshare 获取 ETF 数据失败: {e}

Error message

Akshare 获取 ETF 数据失败: {e}

What it means

Raised by AkshareFetcher._fetch_etf_data when ak.fund_etf_hist_em throws an exception that does not match the rate-limit keyword list ('banned', 'blocked', '频率', 'rate', '限制'). It is a DataFetchError wrapping the original akshare exception, so the DataFetcherManager fallback chain can catch it and move to the next data source. The keyword check runs first, so genuine anti-crawler bans surface as RateLimitError instead of this error.

Source

Thrown at data_provider/akshare_fetcher.py:726

            if df is not None and not df.empty:
                logger.info(f"[API返回] ak.fund_etf_hist_em 成功: 返回 {len(df)} 行数据, 耗时 {api_elapsed:.2f}s")
                logger.info(f"[API返回] 列名: {list(df.columns)}")
                logger.info(f"[API返回] 日期范围: {df['日期'].iloc[0]} ~ {df['日期'].iloc[-1]}")
                logger.debug(f"[API返回] 最新3条数据:\n{df.tail(3).to_string()}")
            else:
                logger.warning(f"[API返回] ak.fund_etf_hist_em 返回空数据, 耗时 {api_elapsed:.2f}s")
            
            return df
            
        except Exception as e:
            error_msg = str(e).lower()
            
            # 检测反爬封禁
            if any(keyword in error_msg for keyword in ['banned', 'blocked', '频率', 'rate', '限制']):
                logger.warning(f"检测到可能被封禁: {e}")
                raise RateLimitError(f"Akshare 可能被限流: {e}") from e
            
            raise DataFetchError(f"Akshare 获取 ETF 数据失败: {e}") from e
    
    def _fetch_us_data(self, stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:
        """
        获取美股历史数据
        
        数据来源:ak.stock_us_daily()(新浪财经接口)
        
        Args:
            stock_code: 美股代码,如 'AMD', 'AAPL', 'TSLA'
            start_date: 开始日期,格式 'YYYY-MM-DD'
            end_date: 结束日期,格式 'YYYY-MM-DD'
            
        Returns:
            美股历史数据 DataFrame
        """
        import akshare as ak
        
        # 防封禁策略 1: 随机 User-Agent

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Inspect the wrapped cause (e.__cause__) in logs — the akshare exception text identifies the real failure (timeout vs TypeError vs JSONDecodeError).
  2. If it is a TypeError/AttributeError, pin or upgrade akshare to the version the fetcher was written against and retest with `ak.fund_etf_hist_em` in a REPL.
  3. If it is network-related, verify connectivity to eastmoney.com and increase the request timeout / retry via the fetcher's base options.
  4. Let DataFetcherManager fall through to the next configured source for ETF data rather than retrying akshare immediately.

Example fix

// before
try:
    df = fetcher.fetch('510300', '2025-01-01', '2025-06-30')
except DataFetchError as e:
    print(e)  # cause lost in print

// after
import traceback
try:
    df = fetcher.fetch('510300', '2025-01-01', '2025-06-30')
except DataFetchError as e:
    logging.error('ETF fetch failed: %s | cause: %s', e, e.__cause__)
    df = manager.fetch('510300', ...)  # fallback chain
Defensive patterns

Strategy: fallback

Validate before calling

import akshare as ak
# smoke-test the ETF interface before a batch run
df = ak.fund_etf_hist_em(symbol='510300', period='daily', start_date='20250101', end_date='20250630')
assert not df.empty

Type guard

def is_akshare_etf_available() -> bool:
    try:
        import akshare as ak
        return callable(getattr(ak, 'fund_etf_hist_em', None))
    except ImportError:
        return False

Try / catch

try:
    df = akshare_fetcher.fetch(etf_code, start, end)
except RateLimitError:
    raise  # let rate-limit policy handle it
except DataFetchError as e:
    logger.warning('akshare ETF failed (%s); falling back', e)
    df = manager.fetch(etf_code, start, end)

Prevention

When it happens

Trigger: Calling fetch on an ETF code (e.g. '510300') where ak.fund_etf_hist_em raises a non-rate-limit exception: network timeout, missing akshare optional dependency, akshare interface signature change after upgrade, or an East Money endpoint returning malformed JSON. Any exception whose lowercased message lacks the ban keywords lands here.

Common situations: akshare piped to a version where fund_etf_hist_em renamed arguments; proxy/DNS failures in CI; East Money occasionally returning HTML error pages that break JSON parsing; running on a network where eastmoney.com is unreachable.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/48d29e891150dd36. Report an issue: GitHub.