Compare commits
3 Commits
d49244de54
...
RFB4
| Author | SHA1 | Date | |
|---|---|---|---|
| 26eb5a9398 | |||
| a97a28de6f | |||
| 764c50e42d |
116
get_data.py
116
get_data.py
@@ -24,7 +24,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|||||||
# 1. Константы / глобальные объекты
|
# 1. Константы / глобальные объекты
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
HOST = "https://deti.russiabasket.org"
|
HOST = "https://ref.russiabasket.org"
|
||||||
|
|
||||||
# Таймзона, в которой мы считаем время матчей / расписания / сна до завтра
|
# Таймзона, в которой мы считаем время матчей / расписания / сна до завтра
|
||||||
APP_TZ = ZoneInfo("Europe/Moscow")
|
APP_TZ = ZoneInfo("Europe/Moscow")
|
||||||
@@ -155,7 +155,7 @@ def _select_lock(path: str):
|
|||||||
return _write_lock_api
|
return _write_lock_api
|
||||||
return _write_lock_out
|
return _write_lock_out
|
||||||
|
|
||||||
def atomic_write_json(path: str, data: Any) -> None:
|
def write_json_simple(path: str, data: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Безопасно записывает JSON:
|
Безопасно записывает JSON:
|
||||||
1. Сериализуем в память (без локов).
|
1. Сериализуем в память (без локов).
|
||||||
@@ -190,6 +190,14 @@ def atomic_write_json(path: str, data: Any) -> None:
|
|||||||
# 2b. Атомарно подменяем
|
# 2b. Атомарно подменяем
|
||||||
os.replace(tmp_path, target)
|
os.replace(tmp_path, target)
|
||||||
|
|
||||||
|
|
||||||
|
def write_json_simple(path: str, data: Any) -> None:
|
||||||
|
full_path = f"static/{path}.json"
|
||||||
|
with open(full_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, separators=(",", ":"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def read_local_json(name: str, in_dir: str = "static") -> Optional[dict]:
|
def read_local_json(name: str, in_dir: str = "static") -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
Безопасно читает static/<name>.json.
|
Безопасно читает static/<name>.json.
|
||||||
@@ -272,7 +280,7 @@ def get_json(session: requests.Session, url: str, name: str) -> Any:
|
|||||||
resp = session.get(url, timeout=10)
|
resp = session.get(url, timeout=10)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
atomic_write_json(f"api_{name}", data)
|
write_json_simple(f"api_{name}", data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -491,6 +499,8 @@ def classify_game_state_from_status(status_raw: str) -> str:
|
|||||||
return "finished"
|
return "finished"
|
||||||
if status in ("", "notstarted", "draft"):
|
if status in ("", "notstarted", "draft"):
|
||||||
return "upcoming"
|
return "upcoming"
|
||||||
|
# if status in ("scheduled"):
|
||||||
|
# return "scheduled"
|
||||||
# всё остальное считаем лайвом
|
# всё остальное считаем лайвом
|
||||||
return "live"
|
return "live"
|
||||||
|
|
||||||
@@ -730,12 +740,12 @@ def render_loop(stop_event: threading.Event, out_name: str = "game") -> None:
|
|||||||
rs = state.get("result", {})
|
rs = state.get("result", {})
|
||||||
if isinstance(rs, dict) and "live_status" in rs:
|
if isinstance(rs, dict) and "live_status" in rs:
|
||||||
live_status_to_write = [rs["live_status"]]
|
live_status_to_write = [rs["live_status"]]
|
||||||
atomic_write_json("live_status", live_status_to_write)
|
write_json_simple("live_status", live_status_to_write)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[RENDER_THREAD] skip live_status write: {e}")
|
logger.debug(f"[RENDER_THREAD] skip live_status write: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
atomic_write_json(out_name, state.get("result", {}))
|
write_json_simple(out_name, state.get("result", {}))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[RENDER_THREAD] skip {out_name}.json write: {e}")
|
logger.debug(f"[RENDER_THREAD] skip {out_name}.json write: {e}")
|
||||||
|
|
||||||
@@ -843,7 +853,7 @@ def Referee(merged: dict, *, out_dir: str = "static") -> None:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
out_path = "referee"
|
out_path = "referee"
|
||||||
atomic_write_json(out_path, referees)
|
write_json_simple(out_path, referees)
|
||||||
logging.info("Сохранил payload: {out_path}")
|
logging.info("Сохранил payload: {out_path}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1049,7 +1059,7 @@ def render_once_after_game(
|
|||||||
Referee(state)
|
Referee(state)
|
||||||
Play_By_Play(state)
|
Play_By_Play(state)
|
||||||
|
|
||||||
atomic_write_json(out_name, state["result"])
|
write_json_simple(out_name, state["result"])
|
||||||
|
|
||||||
logger.info("[RENDER_ONCE] финальные json сохранены успешно")
|
logger.info("[RENDER_ONCE] финальные json сохранены успешно")
|
||||||
|
|
||||||
@@ -1288,7 +1298,7 @@ def Json_Team_Generation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# пишем полный ростер команды
|
# пишем полный ростер команды
|
||||||
atomic_write_json(who, sorted_team)
|
write_json_simple(who, sorted_team)
|
||||||
logger.info(f"Сохранил payload: {who}.json")
|
logger.info(f"Сохранил payload: {who}.json")
|
||||||
|
|
||||||
# топ-игроки по очкам/подборам/ассистам и т.д.
|
# топ-игроки по очкам/подборам/ассистам и т.д.
|
||||||
@@ -1312,7 +1322,7 @@ def Json_Team_Generation(
|
|||||||
player["foul"] = ""
|
player["foul"] = ""
|
||||||
|
|
||||||
top_name = f"top{who.replace('t', 'T')}"
|
top_name = f"top{who.replace('t', 'T')}"
|
||||||
atomic_write_json(top_name, top_sorted_team)
|
write_json_simple(top_name, top_sorted_team)
|
||||||
logger.info(f"Сохранил payload: {top_name}.json")
|
logger.info(f"Сохранил payload: {top_name}.json")
|
||||||
|
|
||||||
# кто прямо сейчас на площадке
|
# кто прямо сейчас на площадке
|
||||||
@@ -1325,7 +1335,7 @@ def Json_Team_Generation(
|
|||||||
key=lambda x: int(x.get("num") or 0),
|
key=lambda x: int(x.get("num") or 0),
|
||||||
)
|
)
|
||||||
started_name = f"started_{who}"
|
started_name = f"started_{who}"
|
||||||
atomic_write_json(started_name, started_team)
|
write_json_simple(started_name, started_team)
|
||||||
logger.info(f"Сохранил payload: {started_name}.json")
|
logger.info(f"Сохранил payload: {started_name}.json")
|
||||||
|
|
||||||
|
|
||||||
@@ -1595,7 +1605,7 @@ def Team_Both_Stat(merged: dict) -> None:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
atomic_write_json("team_stats", result_json)
|
write_json_simple("team_stats", result_json)
|
||||||
logger.info("Сохранил payload: team_stats.json")
|
logger.info("Сохранил payload: team_stats.json")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1666,7 +1676,7 @@ def Pregame_data_json(data: dict) -> None:
|
|||||||
"fouls": round((data_team["totalStats"]["foul"] / data_team["games"]), 1),
|
"fouls": round((data_team["totalStats"]["foul"] / data_team["games"]), 1),
|
||||||
}
|
}
|
||||||
teams.append(temp_team)
|
teams.append(temp_team)
|
||||||
atomic_write_json("team_comparison", teams)
|
write_json_simple("team_comparison", teams)
|
||||||
logger.info("Сохранил payload: team_comparison.json")
|
logger.info("Сохранил payload: team_comparison.json")
|
||||||
|
|
||||||
|
|
||||||
@@ -1691,7 +1701,7 @@ def Pregame_data(pregame_raw: dict, game_stub: dict) -> None:
|
|||||||
}
|
}
|
||||||
Pregame_data_json(out["pregame"])
|
Pregame_data_json(out["pregame"])
|
||||||
# сохраняем файл
|
# сохраняем файл
|
||||||
# atomic_write_json(out["pregame"], "pregame")
|
# write_json_simple(out["pregame"], "pregame")
|
||||||
# logger.info("Сохранил payload: pregame.json")
|
# logger.info("Сохранил payload: pregame.json")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка в Pregame_data: {e}", exc_info=True)
|
logger.error(f"Ошибка в Pregame_data: {e}", exc_info=True)
|
||||||
@@ -1726,7 +1736,7 @@ def Scores_Quarter(merged: dict) -> None:
|
|||||||
else:
|
else:
|
||||||
logger.debug("Нет данных по счёту, сохраняем пустые значения.")
|
logger.debug("Нет данных по счёту, сохраняем пустые значения.")
|
||||||
|
|
||||||
atomic_write_json("scores", score_by_quarter)
|
write_json_simple("scores", score_by_quarter)
|
||||||
logger.info("Сохранил payload: scores.json")
|
logger.info("Сохранил payload: scores.json")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1792,6 +1802,7 @@ def Standing_func(
|
|||||||
(comp.get("name") or "unknown_comp")
|
(comp.get("name") or "unknown_comp")
|
||||||
.replace(" ", "_")
|
.replace(" ", "_")
|
||||||
.replace("|", "")
|
.replace("|", "")
|
||||||
|
.replace("/", "")
|
||||||
)
|
)
|
||||||
|
|
||||||
if item.get("standings"):
|
if item.get("standings"):
|
||||||
@@ -1859,7 +1870,7 @@ def Standing_func(
|
|||||||
standings_payload = df.to_dict(orient="records")
|
standings_payload = df.to_dict(orient="records")
|
||||||
|
|
||||||
filename = f"standings_{league}_{comp_name}"
|
filename = f"standings_{league}_{comp_name}"
|
||||||
atomic_write_json(filename, standings_payload)
|
write_json_simple(filename, standings_payload)
|
||||||
logger.info(f"[STANDINGS_THREAD] сохранил {filename}.json")
|
logger.info(f"[STANDINGS_THREAD] сохранил {filename}.json")
|
||||||
|
|
||||||
elif item.get("playoffPairs"):
|
elif item.get("playoffPairs"):
|
||||||
@@ -1869,7 +1880,7 @@ def Standing_func(
|
|||||||
standings_payload = df.to_dict(orient="records")
|
standings_payload = df.to_dict(orient="records")
|
||||||
|
|
||||||
filename = f"standings_{league}_{comp_name}"
|
filename = f"standings_{league}_{comp_name}"
|
||||||
atomic_write_json(filename, standings_payload)
|
write_json_simple(filename, standings_payload)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[STANDINGS_THREAD] saved {filename}.json (playoffPairs, {len(standings_payload)} rows)"
|
f"[STANDINGS_THREAD] saved {filename}.json (playoffPairs, {len(standings_payload)} rows)"
|
||||||
)
|
)
|
||||||
@@ -1979,27 +1990,6 @@ def get_data_API(
|
|||||||
|
|
||||||
if phase == "live":
|
if phase == "live":
|
||||||
# матч идёт → запускаем live_loop блокирующе
|
# матч идёт → запускаем live_loop блокирующе
|
||||||
t = threading.Thread(
|
|
||||||
target=run_live_loop,
|
|
||||||
args=(league, season, game_id, lang, today_game["game"], stop_event),
|
|
||||||
daemon=False,
|
|
||||||
)
|
|
||||||
t.start()
|
|
||||||
logger.info("[get_data_API] live thread spawned, waiting for it to finish")
|
|
||||||
|
|
||||||
try:
|
|
||||||
t.join()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.info(
|
|
||||||
"[get_data_API] KeyboardInterrupt while waiting live thread -> stop_event"
|
|
||||||
)
|
|
||||||
stop_event.set()
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
logger.info("[get_data_API] live thread finished")
|
|
||||||
return "live_done"
|
|
||||||
|
|
||||||
if phase == "upcoming":
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Матч {game_id} сегодня, но ещё не начался (status={effective_status}). Подготовка pregame."
|
f"Матч {game_id} сегодня, но ещё не начался (status={effective_status}). Подготовка pregame."
|
||||||
)
|
)
|
||||||
@@ -2031,7 +2021,59 @@ def get_data_API(
|
|||||||
logger.exception(
|
logger.exception(
|
||||||
f"[get_data_API] ошибка при подготовке pregame для {game_id}: {e}"
|
f"[get_data_API] ошибка при подготовке pregame для {game_id}: {e}"
|
||||||
)
|
)
|
||||||
|
t = threading.Thread(
|
||||||
|
target=run_live_loop,
|
||||||
|
args=(league, season, game_id, lang, today_game["game"], stop_event),
|
||||||
|
daemon=False,
|
||||||
|
)
|
||||||
|
t.start()
|
||||||
|
logger.info("[get_data_API] live thread spawned, waiting for it to finish")
|
||||||
|
|
||||||
|
try:
|
||||||
|
t.join()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info(
|
||||||
|
"[get_data_API] KeyboardInterrupt while waiting live thread -> stop_event"
|
||||||
|
)
|
||||||
|
stop_event.set()
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
logger.info("[get_data_API] live thread finished")
|
||||||
|
return "live_done"
|
||||||
|
|
||||||
|
if phase == "upcoming":
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Матч {game_id} сегодня, но ещё не начался (status={effective_status}). Подготовка pregame."
|
||||||
|
)
|
||||||
|
|
||||||
|
# дергаем pregame только один раз за матч
|
||||||
|
if not _pregame_done_for_game.get(game_id):
|
||||||
|
try:
|
||||||
|
pregame_raw = fetch_api_data(
|
||||||
|
session,
|
||||||
|
"pregame",
|
||||||
|
host=HOST,
|
||||||
|
league=league,
|
||||||
|
season=season,
|
||||||
|
game_id=game_id,
|
||||||
|
lang=lang,
|
||||||
|
)
|
||||||
|
|
||||||
|
Pregame_data(
|
||||||
|
pregame_raw=pregame_raw,
|
||||||
|
game_stub=today_game["game"],
|
||||||
|
)
|
||||||
|
|
||||||
|
_pregame_done_for_game[game_id] = True
|
||||||
|
logger.info(
|
||||||
|
f"[get_data_API] pregame данные собраны для game_id={game_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
f"[get_data_API] ошибка при подготовке pregame для {game_id}: {e}"
|
||||||
|
)
|
||||||
# матч сегодня, ждём старта
|
# матч сегодня, ждём старта
|
||||||
return "upcoming"
|
return "upcoming"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user