- 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-тем при многопоточном парсинге
201 lines
7.3 KiB
Python
201 lines
7.3 KiB
Python
"""Точка входа 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()
|