update
This commit is contained in:
parent
b9a359d501
commit
63dd380a70
@ -27,9 +27,14 @@ LOGGER = get_logger(__name__)
|
|||||||
API_DEFAULT_LIMIT = 5000
|
API_DEFAULT_LIMIT = 5000
|
||||||
LOG_EXTRA = {"stage": "data_ingest"}
|
LOG_EXTRA = {"stage": "data_ingest"}
|
||||||
|
|
||||||
_CALL_QUEUE = deque()
|
|
||||||
_CALL_BUCKETS: Dict[str, deque] = defaultdict(deque)
|
_CALL_BUCKETS: Dict[str, deque] = defaultdict(deque)
|
||||||
|
|
||||||
|
RATE_LIMIT_ERROR_PATTERNS: Tuple[str, ...] = (
|
||||||
|
"最多访问该接口",
|
||||||
|
"超过接口限制",
|
||||||
|
"Frequency limit",
|
||||||
|
)
|
||||||
|
|
||||||
API_RATE_LIMITS: Dict[str, int] = {
|
API_RATE_LIMITS: Dict[str, int] = {
|
||||||
"stock_basic": 180,
|
"stock_basic": 180,
|
||||||
"daily": 480,
|
"daily": 480,
|
||||||
@ -115,7 +120,7 @@ def _normalize_date_str(value: Optional[str]) -> Optional[str]:
|
|||||||
return text or None
|
return text or None
|
||||||
|
|
||||||
|
|
||||||
def _respect_rate_limit(endpoint: str | None, cfg) -> None:
|
def _respect_rate_limit(endpoint: str | None) -> None:
|
||||||
def _throttle(queue: deque, limit: int) -> None:
|
def _throttle(queue: deque, limit: int) -> None:
|
||||||
if limit <= 0:
|
if limit <= 0:
|
||||||
return
|
return
|
||||||
@ -135,12 +140,8 @@ def _respect_rate_limit(endpoint: str | None, cfg) -> None:
|
|||||||
time.sleep(max(0.1, sleep_time))
|
time.sleep(max(0.1, sleep_time))
|
||||||
queue.append(time.time())
|
queue.append(time.time())
|
||||||
|
|
||||||
max_calls = cfg.max_calls_per_minute
|
|
||||||
if max_calls > 0:
|
|
||||||
_throttle(_CALL_QUEUE, max_calls)
|
|
||||||
|
|
||||||
bucket_key = endpoint or "_default"
|
bucket_key = endpoint or "_default"
|
||||||
endpoint_limit = API_RATE_LIMITS.get(bucket_key, max_calls)
|
endpoint_limit = API_RATE_LIMITS.get(bucket_key, 60)
|
||||||
_throttle(_CALL_BUCKETS[bucket_key], endpoint_limit or 0)
|
_throttle(_CALL_BUCKETS[bucket_key], endpoint_limit or 0)
|
||||||
|
|
||||||
|
|
||||||
@ -165,11 +166,26 @@ def _fetch_paginated(endpoint: str, params: Dict[str, object], limit: int | None
|
|||||||
extra=LOG_EXTRA,
|
extra=LOG_EXTRA,
|
||||||
)
|
)
|
||||||
while True:
|
while True:
|
||||||
_respect_rate_limit(endpoint, get_config())
|
_respect_rate_limit(endpoint)
|
||||||
call = getattr(client, endpoint)
|
call = getattr(client, endpoint)
|
||||||
try:
|
try:
|
||||||
df = call(limit=limit, offset=offset, **clean_params)
|
df = call(limit=limit, offset=offset, **clean_params)
|
||||||
except Exception: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
|
message = str(exc)
|
||||||
|
if any(pattern in message for pattern in RATE_LIMIT_ERROR_PATTERNS):
|
||||||
|
per_minute = API_RATE_LIMITS.get(endpoint or "", 0)
|
||||||
|
wait_time = 60.0 / per_minute + 1 if per_minute else 30.0
|
||||||
|
wait_time = max(wait_time, 30.0)
|
||||||
|
LOGGER.warning(
|
||||||
|
"接口限频触发:%s,原因=%s,等待 %.1f 秒后重试",
|
||||||
|
endpoint,
|
||||||
|
message,
|
||||||
|
wait_time,
|
||||||
|
extra=LOG_EXTRA,
|
||||||
|
)
|
||||||
|
time.sleep(wait_time)
|
||||||
|
continue
|
||||||
|
|
||||||
LOGGER.exception(
|
LOGGER.exception(
|
||||||
"TuShare 接口调用异常:endpoint=%s offset=%s params=%s",
|
"TuShare 接口调用异常:endpoint=%s offset=%s params=%s",
|
||||||
endpoint,
|
endpoint,
|
||||||
@ -874,7 +890,7 @@ def fetch_stock_basic(exchange: Optional[str] = None, list_status: str = "L") ->
|
|||||||
list_status,
|
list_status,
|
||||||
extra=LOG_EXTRA,
|
extra=LOG_EXTRA,
|
||||||
)
|
)
|
||||||
_respect_rate_limit("stock_basic", get_config())
|
_respect_rate_limit("stock_basic")
|
||||||
fields = "ts_code,symbol,name,area,industry,market,exchange,list_status,list_date,delist_date"
|
fields = "ts_code,symbol,name,area,industry,market,exchange,list_status,list_date,delist_date"
|
||||||
df = client.stock_basic(exchange=exchange, list_status=list_status, fields=fields)
|
df = client.stock_basic(exchange=exchange, list_status=list_status, fields=fields)
|
||||||
return _df_to_records(df, _TABLE_COLUMNS["stock_basic"])
|
return _df_to_records(df, _TABLE_COLUMNS["stock_basic"])
|
||||||
@ -1113,7 +1129,7 @@ def fetch_trade_calendar(start: date, end: date, exchange: str = "SSE") -> Itera
|
|||||||
end_date,
|
end_date,
|
||||||
extra=LOG_EXTRA,
|
extra=LOG_EXTRA,
|
||||||
)
|
)
|
||||||
_respect_rate_limit("trade_cal", get_config())
|
_respect_rate_limit("trade_cal")
|
||||||
df = client.trade_cal(exchange=exchange, start_date=start_date, end_date=end_date)
|
df = client.trade_cal(exchange=exchange, start_date=start_date, end_date=end_date)
|
||||||
if df is not None and not df.empty and "is_open" in df.columns:
|
if df is not None and not df.empty and "is_open" in df.columns:
|
||||||
df["is_open"] = pd.to_numeric(df["is_open"], errors="coerce").fillna(0).astype(int)
|
df["is_open"] = pd.to_numeric(df["is_open"], errors="coerce").fillna(0).astype(int)
|
||||||
@ -1159,7 +1175,7 @@ def fetch_stk_limit(
|
|||||||
def fetch_index_basic(market: Optional[str] = None) -> Iterable[Dict]:
|
def fetch_index_basic(market: Optional[str] = None) -> Iterable[Dict]:
|
||||||
client = _ensure_client()
|
client = _ensure_client()
|
||||||
LOGGER.info("拉取指数基础信息(market=%s)", market or "all", extra=LOG_EXTRA)
|
LOGGER.info("拉取指数基础信息(market=%s)", market or "all", extra=LOG_EXTRA)
|
||||||
_respect_rate_limit("index_basic", get_config())
|
_respect_rate_limit("index_basic")
|
||||||
df = client.index_basic(market=market)
|
df = client.index_basic(market=market)
|
||||||
return _df_to_records(df, _TABLE_COLUMNS["index_basic"])
|
return _df_to_records(df, _TABLE_COLUMNS["index_basic"])
|
||||||
|
|
||||||
@ -1186,7 +1202,7 @@ def fetch_index_daily(start: date, end: date, ts_code: str) -> Iterable[Dict]:
|
|||||||
def fetch_fund_basic(asset_class: str = "E", status: str = "L") -> Iterable[Dict]:
|
def fetch_fund_basic(asset_class: str = "E", status: str = "L") -> Iterable[Dict]:
|
||||||
client = _ensure_client()
|
client = _ensure_client()
|
||||||
LOGGER.info("拉取基金基础信息:asset_class=%s status=%s", asset_class, status, extra=LOG_EXTRA)
|
LOGGER.info("拉取基金基础信息:asset_class=%s status=%s", asset_class, status, extra=LOG_EXTRA)
|
||||||
_respect_rate_limit("fund_basic", get_config())
|
_respect_rate_limit("fund_basic")
|
||||||
df = client.fund_basic(market=asset_class, status=status)
|
df = client.fund_basic(market=asset_class, status=status)
|
||||||
return _df_to_records(df, _TABLE_COLUMNS["fund_basic"])
|
return _df_to_records(df, _TABLE_COLUMNS["fund_basic"])
|
||||||
|
|
||||||
@ -1213,7 +1229,7 @@ def fetch_fund_nav(start: date, end: date, ts_code: str) -> Iterable[Dict]:
|
|||||||
def fetch_fut_basic(exchange: Optional[str] = None) -> Iterable[Dict]:
|
def fetch_fut_basic(exchange: Optional[str] = None) -> Iterable[Dict]:
|
||||||
client = _ensure_client()
|
client = _ensure_client()
|
||||||
LOGGER.info("拉取期货基础信息(exchange=%s)", exchange or "all", extra=LOG_EXTRA)
|
LOGGER.info("拉取期货基础信息(exchange=%s)", exchange or "all", extra=LOG_EXTRA)
|
||||||
_respect_rate_limit("fut_basic", get_config())
|
_respect_rate_limit("fut_basic")
|
||||||
df = client.fut_basic(exchange=exchange)
|
df = client.fut_basic(exchange=exchange)
|
||||||
return _df_to_records(df, _TABLE_COLUMNS["fut_basic"])
|
return _df_to_records(df, _TABLE_COLUMNS["fut_basic"])
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user