feat: 数据采集健壮性改进
- 新增 src/core/retry.py: 指数退避 + 抖动的重试装饰器 - bzzoiro: 改进重试逻辑 - 429/5xx/网络错误均触发指数退避重试 - 4xx 直接抛(不重试) - understat: 新增重试(原无重试) - injuries: 新增重试 + 文件缓存 7 天过期 - sources.py: 延迟导入修复循环导入
This commit is contained in:
+30
-8
@@ -4,8 +4,11 @@
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -42,13 +45,17 @@ async def fetch_injuries(*, date: str | None = None, fixture_id: int | None = No
|
||||
cache_dir = _CACHE_DIR
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 缓存命中
|
||||
# 缓存命中 (7 天内有效)
|
||||
cache_key = f"injuries_{date}_{fixture_id}_{league_id}.json"
|
||||
cache_file = cache_dir / cache_key
|
||||
if cache_file.exists():
|
||||
logger.debug("injuries cache hit: %s", cache_key)
|
||||
with open(cache_file, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
age_hours = (time.time() - cache_file.stat().st_mtime) / 3600
|
||||
if age_hours < 168: # 7 天
|
||||
logger.debug("injuries cache hit: %s (%.1fh old)", cache_key, age_hours)
|
||||
with open(cache_file, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
logger.debug("injuries cache expired: %s (%.1fh old)", cache_key, age_hours)
|
||||
|
||||
headers = {
|
||||
"x-apisports-key": api_key,
|
||||
@@ -63,11 +70,26 @@ async def fetch_injuries(*, date: str | None = None, fixture_id: int | None = No
|
||||
params["league"] = league_id
|
||||
|
||||
url = f"{API_BASE}/injuries"
|
||||
client = get_client()
|
||||
resp = await client.get(url, headers=headers, params=params)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
# 重试
|
||||
last_exc: Exception | None = None
|
||||
for attempt in range(3):
|
||||
try:
|
||||
client = get_client()
|
||||
resp = await client.get(url, headers=headers, params=params, timeout=30)
|
||||
resp.raise_for_status()
|
||||
break
|
||||
except Exception as e:
|
||||
last_exc = e
|
||||
if attempt == 2:
|
||||
raise
|
||||
delay = min(2 ** attempt, 8) + random.uniform(0, 1)
|
||||
logger.warning("injuries fetch failed, retry %d in %.1fs: %s", attempt + 1, delay, e)
|
||||
await asyncio.sleep(delay)
|
||||
else:
|
||||
raise RuntimeError(f"injuries fetch failed: {last_exc}")
|
||||
|
||||
data = resp.json()
|
||||
injuries = data.get("response", [])
|
||||
|
||||
# 写缓存
|
||||
|
||||
Reference in New Issue
Block a user