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-тем при многопоточном парсинге
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user