ZhuLinsen/daily_stock_analysis · error · RateLimitError

Akshare(EM) 可能被限流: {e}

Error message

Akshare(EM) 可能被限流: {e}

What it means

RateLimitError raised by _fetch_stock_data_em when ak.stock_zh_a_hist fails and the lowercased error text contains one of the anti-scraping keywords ('banned', 'blocked', '频率', 'rate', '限制'). It is a heuristic classification: the source (Eastmoney) probably throttled or blocked the client, and the fetcher marks it so upstream logic can back off or switch channels instead of hammering.

Source

Thrown at data_provider/akshare_fetcher.py:569

                period="daily",
                start_date=start_date.replace('-', ''),
                end_date=end_date.replace('-', ''),
                adjust="qfq"
            )

            api_elapsed = _time.time() - api_start

            if df is not None and not df.empty:
                logger.info(f"[API返回] ak.stock_zh_a_hist 成功: {len(df)} 行, 耗时 {api_elapsed:.2f}s")
                return df
            else:
                logger.warning(f"[API返回] ak.stock_zh_a_hist 返回空数据")
                return pd.DataFrame()

        except Exception as e:
            error_msg = str(e).lower()
            if any(keyword in error_msg for keyword in ['banned', 'blocked', '频率', 'rate', '限制']):
                raise RateLimitError(f"Akshare(EM) 可能被限流: {e}") from e
            raise e

    def _fetch_stock_data_sina(self, stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:
        """
        获取普通 A 股历史数据 (新浪财经)
        数据来源:ak.stock_zh_a_daily()
        """
        import akshare as ak

        # 转换代码格式:sh600000, sz000001, bj920748
        symbol = _to_sina_tx_symbol(stock_code)

        self._enforce_rate_limit()

        try:
            df = _akshare_call_with_timeout(
                ak.stock_zh_a_daily,
                symbol=symbol,

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Back off: wait minutes (not seconds) before retrying this channel; rate-limit windows usually expire.
  2. Increase inter-call sleep/randomization in the fetch strategy and reduce batch size.
  3. Catch RateLimitError and switch to the Sina channel or yfinance for the remainder of the run.
  4. Run the job from a different egress IP or off-peak hours.
  5. Persist fetched data to cache so repeat runs don't re-hit the API.

Example fix

# before
for code in codes:
    df = fetcher.fetch_stock_data(code, start, end)

# after: catch RateLimitError and back off / reroute
from data_provider.exceptions import RateLimitError
for code in codes:
    try:
        df = fetcher.fetch_stock_data(code, start, end)
    except RateLimitError:
        df = alternate_fetcher.fetch_stock_data(code, start, end)  # sina/yfinance
        time.sleep(30)
Defensive patterns

Strategy: retry

Type guard

from data_provider.exceptions import RateLimitError

def is_akshare_rate_limited(exc: Exception) -> bool:
    return isinstance(exc, RateLimitError)

Try / catch

try:
    df = fetcher._fetch_stock_data_em(code, start, end)
except RateLimitError:
    time.sleep(300)                      # throttle windows are minutes, not seconds
    df = fetcher._fetch_stock_data_sina(code, start, end)  # or switch channel/source

Prevention

When it happens

Trigger: Calling the A-share Eastmoney channel repeatedly in a short window; the exception message from akshare/requests contains e.g. '请求频率' or 'blocked'; triggering protection after sustained scraping from one IP.

Common situations: Scheduled analysis jobs running many symbols with short intervals; shared office/NAT egress IPs already flagged by Eastmoney; debugging loops that re-fetch the same symbol dozens of times.

Related errors


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