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:
2026-06-06 11:46:57 +04:00
parent 673bfe5e2a
commit 5c142096de
18 changed files with 2118 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
"""Точка входа CLI для scraper.
Использование:
python -m scraper xenforo URL [--workers N] [--no-delay]
python -m scraper phpbb ID [--base URL] [--workers N]
Примеры:
python -m scraper xenforo https://vwts.ru/forum/vag/benzinovye-dvigateli/
python -m scraper xenforo URL --workers 8
python -m scraper phpbb 22
python -m scraper phpbb 22 --base https://нива-лада.рф/n/
"""
import sys
import logging
from pathlib import Path
# ─── Инициализация логирования ──────────────────────────────────────────
# Должна быть первой, чтобы все логи писались сразу
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S",
handlers=[
logging.StreamHandler(sys.stdout),
],
)
log = logging.getLogger(__name__)
def _print_usage():
"""Вывести справку по использованию."""
print("Использование:")
print(" python -m scraper xenforo URL [опции]")
print(" python -m scraper phpbb FORUM_ID [опции]")
print()
print("XenForo (vwts.ru):")
print(" python -m scraper xenforo https://vwts.ru/forum/vag/.../")
print()
print("phpBB (нива-лада.рф):")
print(" python -m scraper phpbb 22")
print(" python -m scraper phpbb 22 --base https://нива-лада.рф/n/")
print()
print("Опции:")
print(" --workers N количество потоков (по умолчанию 4)")
print(" --no-delay без координации задержек")
print(" --no-ban-test не проверять бан")
print(" --data-dir DIR папка для данных (по умолчанию vwts_data / lada_data)")
print(" --base URL базовый URL форума (для phpBB)")
def main():
"""Точка входа: разбор аргументов → запуск нужного адаптера."""
args = sys.argv[1:]
if not args or args[0] in ("-h", "--help"):
_print_usage()
sys.exit(0)
# ── Определяем движок ───────────────────────────────────────────────
engine = args.pop(0)
if engine == "xenforo":
_run_xenforo(args)
elif engine == "phpbb":
_run_phpbb(args)
else:
print(f"❌ Неизвестный движок: {engine}")
print(" Доступно: xenforo, phpbb")
sys.exit(1)
# ═══════════════════════════════════════════════════════════════════════════
# XenForo
# ═══════════════════════════════════════════════════════════════════════════
def _run_xenforo(args: list[str]):
"""Запуск парсинга для XenForo (vwts.ru).
Args:
args: список аргументов после "xenforo".
"""
if not args or args[0].startswith("-"):
print("❌ Укажи URL раздела. Пример:")
print(" python -m scraper xenforo https://vwts.ru/forum/vag/.../")
sys.exit(1)
section_url = args.pop(0).rstrip("/") + "/"
# Парсим опции
workers = 4
no_delay = False
ban_test = True
data_dir = "vwts_data"
i = 0
while i < len(args):
if args[i] == "--workers" and i + 1 < len(args):
workers = int(args[i + 1])
i += 2
elif args[i] == "--no-delay":
no_delay = True
i += 1
elif args[i] == "--no-ban-test":
ban_test = False
i += 1
elif args[i] == "--data-dir" and i + 1 < len(args):
data_dir = args[i + 1]
i += 2
else:
i += 1
# Импортируем адаптер
from .forums.xenforo.adapter import XenForoAdapter
from .core.runner import run
adapter = XenForoAdapter(data_dir=data_dir)
log.info("=" * 60)
log.info("🚀 scraper — XenForo: %s", section_url.split("/")[-2])
log.info("📂 %s", Path(data_dir) / section_url.split("/")[-2] / "part_*.jsonl")
log.info("🛑 Ctrl+C = пауза | Повторить команду = продолжить")
log.info("=" * 60)
try:
run(adapter, section_url,
workers=workers, no_delay=no_delay, ban_test=ban_test)
except KeyboardInterrupt:
log.info("\n🛑 ПАУЗА. Продолжить: та же команда.")
sys.exit(0)
# ═══════════════════════════════════════════════════════════════════════════
# phpBB
# ═══════════════════════════════════════════════════════════════════════════
def _run_phpbb(args: list[str]):
"""Запуск парсинга для phpBB (нива-лада.рф).
Args:
args: список аргументов после "phpbb".
"""
if not args or args[0].startswith("-"):
print("❌ Укажи ID раздела. Пример:")
print(" python -m scraper phpbb 22")
sys.exit(1)
forum_id = args.pop(0)
# Парсим опции
workers = 4
no_delay = False
ban_test = True
data_dir = "lada_data"
forum_base = "https://нива-лада.рф/n/"
i = 0
while i < len(args):
if args[i] == "--workers" and i + 1 < len(args):
workers = int(args[i + 1])
i += 2
elif args[i] == "--no-delay":
no_delay = True
i += 1
elif args[i] == "--no-ban-test":
ban_test = False
i += 1
elif args[i] == "--data-dir" and i + 1 < len(args):
data_dir = args[i + 1]
i += 2
elif args[i] == "--base" and i + 1 < len(args):
forum_base = args[i + 1]
i += 2
else:
i += 1
# Формируем URL раздела
section_url = f"{forum_base}viewforum.php?f={forum_id}"
from .forums.phpbb.adapter import PhpBBAdapter
from .core.runner import run
adapter = PhpBBAdapter(data_dir=data_dir, forum_base=forum_base)
log.info("=" * 60)
log.info("🚀 scraper — phpBB: f=%s", forum_id)
log.info("📂 %s", Path(data_dir) / f"f{forum_id}" / "part_*.jsonl")
log.info("🛑 Ctrl+C = пауза | Повторить команду = продолжить")
log.info("=" * 60)
try:
run(adapter, section_url, section_slug=f"f{forum_id}",
workers=workers, no_delay=no_delay, ban_test=ban_test)
except KeyboardInterrupt:
log.info("\n🛑 ПАУЗА. Продолжить: та же команда.")
sys.exit(0)
if __name__ == "__main__":
main()