{"record":{"id":"d9772138503615c6","repo":"ZhuLinsen/daily_stock_analysis","slug":"efinance-failure-message","errorCode":null,"errorMessage":"efinance 可能被限流: {failure_message}","messagePattern":"efinance 可能被限流: (.+?)","errorType":"exception","errorClass":"RateLimitError","httpStatus":null,"severity":"error","filePath":"data_provider/efinance_fetcher.py","lineNumber":471,"sourceCode":"                    f\"endpoint={EASTMONEY_HISTORY_ENDPOINT}, stock_code={stock_code}, \"\n                    f\"range={beg_date}~{end_date_fmt}, elapsed={api_elapsed:.2f}s\"\n                )\n            \n            return df\n            \n        except Exception as e:\n            api_elapsed = time.time() - api_start\n            category, failure_message = self._build_history_failure_message(\n                stock_code=stock_code,\n                beg_date=beg_date,\n                end_date=end_date_fmt,\n                exc=e,\n                elapsed=api_elapsed,\n            )\n\n            if category == \"rate_limit_or_anti_bot\":\n                logger.warning(failure_message)\n                raise RateLimitError(f\"efinance 可能被限流: {failure_message}\") from e\n\n            logger.error(failure_message)\n            raise DataFetchError(f\"efinance 获取数据失败: {failure_message}\") from e\n    \n    def _fetch_etf_data(self, stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:\n        \"\"\"\n        获取 ETF 基金历史数据\n\n        Exchange-traded ETFs have OHLCV data just like regular stocks, so we use\n        ef.stock.get_quote_history (the stock K-line API) which returns full\n        open/high/low/close/volume data.\n\n        Previously this method used ef.fund.get_quote_history which only returns\n        NAV data (单位净值/累计净值) without volume or OHLC, causing:\n        - Issue #541: 'got an unexpected keyword argument beg'\n        - Issue #527: ETF volume/turnover always showing 0\n\n        Args:","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/efinance_fetcher.py#L453-L489","documentation":"Raised as RateLimitError from EfinanceFetcher._fetch_stock_data when _build_history_failure_message classifies the caught exception as 'rate_limit_or_anti_bot' — Eastmoney throttled or bot-challenged the request. Distinct from generic failure (error 151) so callers can back off rather than fail over blindly.","triggerScenarios":"ef.stock.get_quote_history raising with HTTP 4xx/5xx patterns, connection resets, or anti-bot payloads during bursty A-share K-line fetching; classification depends on _build_history_failure_message's category rules.","commonSituations":"Batch jobs fetching hundreds of A-share codes sequentially; running alongside other Eastmoney consumers from the same IP; CI network-smoke runs hitting Eastmoney repeatedly.","solutions":["Back off and retry after a delay — the fetcher already does random_sleep, so add exponential backoff around the call or reduce batch size.","Let DataFetcherManager fail over to akshare/tushare for the affected codes in this run.","Cache daily bars so repeated analyses within a day don't re-hit Eastmoney."],"exampleFix":"# before\nfor code in codes:\n    df = manager.get_daily_data(code, ...)\n# after\nimport time\nfor i, code in enumerate(codes):\n    try:\n        df = manager.get_daily_data(code, ...)\n    except RateLimitError:\n        time.sleep(30 * (i // 10 + 1))  # exponential-ish backoff\n        df = manager.get_daily_data(code, ...)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from data_provider.base import RateLimitError  # efinance rate-limit signal\nfor attempt in range(3):\n    try:\n        df = efinance_fetcher.get_daily_data(code, ...)\n        break\n    except RateLimitError:\n        time.sleep(2 ** attempt * 30)\nelse:\n    df = manager.get_daily_data(code, ...)  # fail over after backoff","preventionTips":["Throttle A-share batch loops (sleep between calls, batch size caps).","Cache daily K-lines per day so repeated runs don't re-hit Eastmoney.","Distinguish RateLimitError from DataFetchError: backoff vs failover."],"tags":["efinance","rate-limit","anti-bot","eastmoney","retryable"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}