- Throttle: Thread-safe координация задержек - BanDetector: автоопределение бана - fetch: thread-local сессии вместо глобальной - run: ThreadPoolExecutor + диапазоны страниц + мерж - output/state: поддержка worker-папок и merge_states() - __main__: --workers, --no-delay, --no-ban-test - VWTS/forums-survey.md: обзор 20+ автофорумов
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
"""Общая координация задержек и автоопределение бана."""
|
|
|
|
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
|