Files
LLM-UI/scraper/core/fetch.py
naeel 5c142096de scraper: новая архитектура — core/ + forums/ (BaseAdapter, XenForo, phpBB)
- core/ — общий слой: fetch, throttle, output, state, runner
- forums/base.py — абстрактный BaseAdapter
- forums/xenforo/ — адаптер для XenForo (vwts.ru)
- forums/phpbb/ — адаптер для phpBB (нива-лада.рф)
- __main__.py — точка входа CLI
- history/001-initial-structure.md — журнал изменений

Также на ВМ (не в этом коммите):
- lada_scraper/parse.py — пропуск sticky-тем при многопоточном парсинге
2026-06-06 11:46:57 +04:00

119 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""HTTP-запросы: GET с ретраями.
Единая точка вызова HTTP для всех адаптеров.
Не зависит от движка форума.
"""
import time
import logging
import requests
from bs4 import BeautifulSoup
# ─── Модульный логгер ───────────────────────────────────────────────────
log = logging.getLogger(__name__)
# ─── Глобальный throttle (устанавливается в runner.py) ──────────────────
# fetch.get() вызывает throttle.wait() перед каждым запросом.
throttle = None # type: ignore
# ─── Дефолтные значения ─────────────────────────────────────────────────
TIMEOUT = (10, 30) # (connect, read) — requests-таймаут
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
}
RETRIES = 3 # максимум HTTP-попыток на один URL
def make_session() -> requests.Session:
"""Создать HTTP-сессию с дефолтными заголовками.
Returns:
requests.Session с предустановленным User-Agent.
"""
session = requests.Session()
session.headers.update(HEADERS)
return session
def get(url: str, session=None) -> BeautifulSoup | None:
"""GET-запрос с ретраями, парсинг в BeautifulSoup (lxml).
Логика повторных попыток:
1. 404 → сразу None (бесполезно ретраить).
2. 403/429/503 → ждём 10*N сек, потом ретрай.
3. Сеть/Таймаут → ждём 5 сек, потом ретрай.
4. Прочее → ждём 5 сек, потом ретрай.
5. Все RETRIES исчерпаны → None.
Args:
url: Полный URL для загрузки.
session: Сессия (если None — временная).
Returns:
BeautifulSoup или None при недоступности.
"""
# ── Сессия ──────────────────────────────────────────────────────────
if session is None:
session = make_session()
# ── Throttle ────────────────────────────────────────────────────────
if throttle is not None:
throttle.wait()
# ── Цикл ретраев ────────────────────────────────────────────────────
for attempt in range(1, RETRIES + 1):
try:
response = session.get(url, timeout=TIMEOUT)
# 404 — не найдено
if response.status_code == 404:
log.warning(" ⏭️ 404: %s", url[:80])
return None
# 403/429/503 — сервер банит/перегружен
if response.status_code in (403, 429, 503):
wait = 10 * attempt
log.warning(
"⚠️ HTTP %d — попытка %d/%d, жду %d с",
response.status_code, attempt, RETRIES, wait,
)
time.sleep(wait)
continue
# Любая другая HTTP-ошибка
response.raise_for_status()
# Успех
return BeautifulSoup(response.text, "lxml")
except (requests.ConnectionError, requests.Timeout) as exc:
log.warning(
"⚠️ Сеть: %s — попытка %d/%d, жду 5 с",
exc, attempt, RETRIES,
)
time.sleep(5)
except requests.HTTPError as exc:
log.warning(
"⚠️ HTTP %s — попытка %d/%d, жду 5 с",
exc, attempt, RETRIES,
)
time.sleep(5)
except Exception as exc:
log.warning(
"⚠️ Ошибка: %s — попытка %d/%d, жду 5 с",
exc, attempt, RETRIES,
)
time.sleep(5)
# Все попытки исчерпаны
log.error("❌ Не загружено (%d попыток): %s", RETRIES, url[:80])
return None