"""Общая координация задержек и автоопределение бана.""" import time, threading, logging import requests from .config import DELAY, TIMEOUT, HEADERS log = logging.getLogger(__name__) class Throttle: """Thread-safe координатор задержек. Все потоки ждут своей очереди — максимум 1 запрос в DELAY секунд. """ def __init__(self, delay: float = DELAY, enabled: bool = True): self._delay = delay self._enabled = enabled self._lock = threading.Lock() self._last = 0.0 # монотонное время последнего запроса @property def enabled(self) -> bool: return self._enabled def wait(self): """Ждать своей очереди. Без координации — просто sleep(delay).""" if not self._enabled: time.sleep(self._delay) return with self._lock: elapsed = time.monotonic() - self._last if elapsed < self._delay: time.sleep(self._delay - elapsed) self._last = time.monotonic() def disable(self): self._enabled = False class BanDetector: """Проверяет, банит ли сервер за быстрые запросы.""" @staticmethod def test(base_url: str, count: int = 5) -> bool: """Шлёт count запросов без задержки. Возвращает True если банят. Args: base_url: любой URL того же хоста (напр. главная раздела) count: сколько запросов подряд Returns: True — банит (есть 403/429), False — не банит """ log.info(f"🔍 Проверка бана: {count} запросов подряд...") s = requests.Session() s.headers.update(HEADERS) banned = False for i in range(count): try: r = s.get(base_url, timeout=TIMEOUT) log.info(f" [{i+1}/{count}] HTTP {r.status_code}") if r.status_code in (403, 429): banned = True break except Exception as e: log.warning(f" [{i+1}/{count}] ошибка: {e}") banned = True break if banned: log.warning("⚠️ Сервер банит быстрые запросы — включаю координацию") else: log.info("✅ Бан не обнаружен — без координации") s.close() return banned