ZhuLinsen/daily_stock_analysis · error · RateLimitError
Akshare 可能被限流: {e}
Error message
Akshare 可能被限流: {e} What it means
RateLimitError raised by the ETF fetch path (ak.fund_etf_hist_em) when the caught exception's lowercased message contains anti-scraping keywords ('banned', 'blocked', '频率', 'rate', '限制') — same heuristic as the stock channel. Any other failure mode on this path is re-raised as DataFetchError ('Akshare 获取 ETF 数据失败').
Source
Thrown at data_provider/akshare_fetcher.py:724
# 记录返回数据摘要
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 akView on GitHub (pinned to 5159bd72e8)
Solutions
- Wait for the throttle window to pass (minutes) before retrying ETF fetches.
- Add spacing/jitter between ETF requests and cache results to avoid refetching.
- Catch RateLimitError at the caller and fall back to another data source for ETF quotes if configured.
- Reduce the symbol count per run or stagger schedules across instances.
- If the error persists across long waits, verify the message isn't a false positive (a different error containing the word '限制').
Example fix
# before
for etf in ['510300', '510500', '512100']:
df = fetcher.fetch_stock_data(etf, start, end)
# after: pace + cache
import time
cache = {}
for etf in ['510300', '510500', '512100']:
if etf not in cache:
cache[etf] = fetcher.fetch_stock_data(etf, start, end)
time.sleep(3) Defensive patterns
Strategy: retry
Type guard
from data_provider.exceptions import RateLimitError, DataFetchError
def is_etf_rate_limited(exc: Exception) -> bool:
return isinstance(exc, RateLimitError)
def is_etf_fetch_failed(exc: Exception) -> bool:
return isinstance(exc, DataFetchError) and '获取 ETF 数据失败' in str(exc) Try / catch
try:
df = fetcher.fetch_stock_data(etf_code, start, end)
except RateLimitError:
time.sleep(300)
df = fetcher.fetch_stock_data(etf_code, start, end) # single retry after backoff
except DataFetchError as e:
if '获取 ETF 数据失败' in str(e):
df = alternate_fetcher.fetch_stock_data(etf_code, start, end)
raise Prevention
- Space out ETF fetches and cache fund_etf_hist_em results per day.
- Distinguish RateLimitError (backoff, retry) from DataFetchError (switch source) in handlers — this code path raises both.
- Avoid parallel instances scraping the same Eastmoney endpoint from one IP.
When it happens
Trigger: Calling AkshareFetcher with an ETF code (detected by _is_etf_code) while Eastmoney throttles the fund_etf_hist_em endpoint: repeated ETF fetches in a short window produce an error containing a rate/ban keyword.
Common situations: Batch fetching a large ETF watchlist; CI or network smoke tests that hit the endpoint repeatedly; running multiple instances of the analyzer from the same IP concurrently.
Related errors
- Akshare(EM) 可能被限流: {e}
- Akshare 所有渠道获取失败: {last_error}
- Akshare 获取 ETF 数据失败: {e}
- {call_name} 调用超过 {wait_seconds:g}s,已放弃等待
- {call_name} 调用进程未返回结果
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/a0eb6e27fdb6d10d.
Report an issue: GitHub.