From 7b44ab3d5d90a2651a839eafef791123b83a5ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Wed, 29 Oct 2025 11:44:13 +0300 Subject: [PATCH] =?UTF-8?q?get=5Fjson=20=D0=BF=D0=BE=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=8F=D0=BB=20=D0=BC=D0=B5=D1=81=D1=82=D0=B0=D0=BC=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- get_data_new.py | 57 +++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/get_data_new.py b/get_data_new.py index f251e98..36496b6 100644 --- a/get_data_new.py +++ b/get_data_new.py @@ -1,5 +1,5 @@ import time -import os +import os, tempfile import json import tempfile import argparse @@ -9,6 +9,7 @@ import logging.config from datetime import datetime, timedelta, timezone from zoneinfo import ZoneInfo from typing import Any, Dict, List, Tuple, Optional +from pathlib import Path import pandas as pd import numpy as np @@ -23,7 +24,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed # 1. Константы / глобальные объекты # ============================================================================ -HOST = "https://ref.russiabasket.org" +HOST = "https://deti.russiabasket.org" # Таймзона, в которой мы считаем время матчей / расписания / сна до завтра APP_TZ = ZoneInfo("Europe/Moscow") @@ -35,7 +36,8 @@ TELEGRAM_BOT_TOKEN = "7639240596:AAH0YtdQoWZSC-_R_EW4wKAHHNLIA0F_ARY" TELEGRAM_CHAT_ID = -4803699526 # Глобальный лок для потокобезопасной записи JSON -_write_lock = threading.Lock() +_write_lock_api = threading.Lock() +_write_lock_out = threading.Lock() _pregame_done_for_game = {} # Карта всех ручек API, с интервалами опроса в секундах. @@ -146,28 +148,41 @@ logger.handlers[2].formatter.use_emoji = True # ============================================================================ -def atomic_write_json(data: Any, name: str, out_dir: str = "static") -> None: +def _select_lock(path: str): + filename = os.path.basename(path) + # все сырые файлы мы называем api_*.json (api_game.json, api_box-score.json, ...) + if filename.startswith("api_"): + return _write_lock_api + return _write_lock_out + +def atomic_write_json(path: str, data: Any) -> None: """ - Потокобезопасная запись JSON в static/.json. - - Запись делается через временный файл + os.replace, чтобы: - - читатели не получили битый файл во время перезаписи; - - не было гонок между render_loop и poll_game_live. + Безопасно записывает JSON: + 1. Сериализуем в память (без локов). + 2. Под коротким локом - пишем tmp и делаем os.replace(). """ - os.makedirs(out_dir, exist_ok=True) - filename = os.path.join(out_dir, f"{name}.json") + # 1. Готовим данные заранее + # ensure_ascii=False чтобы не терять юникод, indent=None чтобы не раздувать файл + payload = json.dumps(data, ensure_ascii=False, separators=(",", ":")) - with _write_lock: - with tempfile.NamedTemporaryFile( - "w", delete=False, dir=out_dir, encoding="utf-8" - ) as tmp_file: - json.dump(data, tmp_file, ensure_ascii=False, indent=2) - tmp_file.flush() - os.fsync(tmp_file.fileno()) - tmp_name = tmp_file.name + target = Path(path) + tmp_fd, tmp_path = tempfile.mkstemp( + dir=target.parent, + prefix=target.name + ".tmp.", + text=True, + ) + os.close(tmp_fd) # мы будем писать сами - os.replace(tmp_name, filename) + lock = _select_lock(path) + with lock: + # 2a. Записываем полностью во временный файл + with open(tmp_path, "w", encoding="utf-8") as f: + f.write(payload) + f.flush() + os.fsync(f.fileno()) + # 2b. Атомарно подменяем + os.replace(tmp_path, target) def read_local_json(name: str, in_dir: str = "static") -> Optional[dict]: """ @@ -251,7 +266,7 @@ def get_json(session: requests.Session, url: str, name: str) -> Any: resp = session.get(url, timeout=10) resp.raise_for_status() data = resp.json() - atomic_write_json(data, f"api_{name}") + atomic_write_json(f"api_{name}", data) return data