{"record":{"id":"a044d3cd59d9bd77","repo":"ZhuLinsen/daily_stock_analysis","slug":"akshare-em-e","errorCode":null,"errorMessage":"Akshare(EM) 可能被限流: {e}","messagePattern":"Akshare\\(EM\\) 可能被限流: (.+?)","errorType":"exception","errorClass":"RateLimitError","httpStatus":null,"severity":"error","filePath":"data_provider/akshare_fetcher.py","lineNumber":569,"sourceCode":"                period=\"daily\",\n                start_date=start_date.replace('-', ''),\n                end_date=end_date.replace('-', ''),\n                adjust=\"qfq\"\n            )\n\n            api_elapsed = _time.time() - api_start\n\n            if df is not None and not df.empty:\n                logger.info(f\"[API返回] ak.stock_zh_a_hist 成功: {len(df)} 行, 耗时 {api_elapsed:.2f}s\")\n                return df\n            else:\n                logger.warning(f\"[API返回] ak.stock_zh_a_hist 返回空数据\")\n                return pd.DataFrame()\n\n        except Exception as e:\n            error_msg = str(e).lower()\n            if any(keyword in error_msg for keyword in ['banned', 'blocked', '频率', 'rate', '限制']):\n                raise RateLimitError(f\"Akshare(EM) 可能被限流: {e}\") from e\n            raise e\n\n    def _fetch_stock_data_sina(self, stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:\n        \"\"\"\n        获取普通 A 股历史数据 (新浪财经)\n        数据来源：ak.stock_zh_a_daily()\n        \"\"\"\n        import akshare as ak\n\n        # 转换代码格式：sh600000, sz000001, bj920748\n        symbol = _to_sina_tx_symbol(stock_code)\n\n        self._enforce_rate_limit()\n\n        try:\n            df = _akshare_call_with_timeout(\n                ak.stock_zh_a_daily,\n                symbol=symbol,","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/akshare_fetcher.py#L551-L587","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Back off: wait minutes (not seconds) before retrying this channel; rate-limit windows usually expire.","Increase inter-call sleep/randomization in the fetch strategy and reduce batch size.","Catch RateLimitError and switch to the Sina channel or yfinance for the remainder of the run.","Run the job from a different egress IP or off-peak hours.","Persist fetched data to cache so repeat runs don't re-hit the API."],"exampleFix":"# before\nfor code in codes:\n    df = fetcher.fetch_stock_data(code, start, end)\n\n# after: catch RateLimitError and back off / reroute\nfrom data_provider.exceptions import RateLimitError\nfor code in codes:\n    try:\n        df = fetcher.fetch_stock_data(code, start, end)\n    except RateLimitError:\n        df = alternate_fetcher.fetch_stock_data(code, start, end)  # sina/yfinance\n        time.sleep(30)","handlingStrategy":"retry","validationCode":null,"typeGuard":"from data_provider.exceptions import RateLimitError\n\ndef is_akshare_rate_limited(exc: Exception) -> bool:\n    return isinstance(exc, RateLimitError)","tryCatchPattern":"try:\n    df = fetcher._fetch_stock_data_em(code, start, end)\nexcept RateLimitError:\n    time.sleep(300)                      # throttle windows are minutes, not seconds\n    df = fetcher._fetch_stock_data_sina(code, start, end)  # or switch channel/source","preventionTips":["Rate-limit your own batch jobs (spacing, jitter) rather than relying on ban detection.","Cache daily data so repeat analyses never re-hit Eastmoney within the same day.","Treat RateLimitError as a signal to slow the whole batch, not just the failing symbol."],"tags":["akshare","rate-limit","anti-scraping","eastmoney","data-provider"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}