Files

116 lines
5.0 KiB
Python

"""Основной цикл: обход страниц раздела → темы → посты → сохранение."""
import sys, logging
from pathlib import Path
from urllib.parse import urlparse
from .config import OUTPUT_DIR
from .fetch import get, session
from .paginate import count, verify_last
from .parse import parse_topics, parse_posts, parse_tags
from .state import load, save
from .output import save_topic
log = logging.getLogger(__name__)
def run(section_url: str):
"""Главный цикл скрейпера.
Args:
section_url: полный URL раздела, напр.
https://vwts.ru/forum/vag/benzinovye-dvigateli/
"""
section_slug = section_url.rstrip("/").split("/")[-1]
# загружаем состояние (если было прервано — продолжим)
st = load(section_slug)
if st["topics_done"]:
log.info(f"🔄 Продолжаем: {len(st['topics_done'])} тем, "
f"part_{st['file_index']:04d}.jsonl")
# ── Страница 1: название раздела + количество страниц ────────────────
soup = get(section_url)
if not soup:
log.error("❌ Сайт недоступен")
sys.exit(1)
section_name = soup.select_one("h1")
section_name = section_name.text.strip() if section_name else section_slug
total_pages = count(soup)
# Проверяем: последняя страница реально существует?
total_pages = verify_last(section_url, total_pages, session)
log.info(f"📋 {section_name} | страниц: {total_pages}")
# ── Обход страниц раздела ───────────────────────────────────────────
consecutive_404 = 0
for pg in range(1, total_pages + 1):
if pg in st["pages_done"]:
consecutive_404 = 0
continue
# page-1 = базовый URL (чтобы не было лишнего редиректа)
url = section_url if pg == 1 else f"{section_url}page-{pg}"
log.info(f"📄 [{pg}/{total_pages}]")
page_soup = get(url)
if not page_soup:
consecutive_404 += 1
# 3 пустых страницы подряд = раздел закончился раньше
if consecutive_404 >= 3:
log.info(f" ⏹️ {consecutive_404} пустых подряд — завершаем")
break
log.warning(f" ⚠️ Пропущена стр.{pg} "
f"(пустых подряд: {consecutive_404})")
continue
consecutive_404 = 0
topics = parse_topics(page_soup)
log.info(f" Тем: {len(topics)}")
# ── Обход тем на странице ──────────────────────────────────────
for i, tp in enumerate(topics, 1):
tid = tp["id"]
if str(tid) in st["topics_done"]:
continue
log.info(f" [{i}/{len(topics)}] #{tid}: {tp['title'][:80]}")
topic_soup = get(tp["url"])
if not topic_soup:
# не удалось — помечаем чтобы не ретраить бесконечно
st["topics_done"][str(tid)] = tp["title"]
save(section_slug, st)
continue
topic_pages = count(topic_soup)
posts, tags = [], parse_tags(topic_soup)
# ── Обход страниц темы ────────────────────────────────────
base = f"{urlparse(section_url).scheme}://{urlparse(section_url).netloc}"
for tpp in range(1, topic_pages + 1):
if tpp == 1:
posts += parse_posts(topic_soup)
else:
tp2 = get(f"{base}/forum/topic/{tid}/page-{tpp}")
if tp2:
posts += parse_posts(tp2)
log.info(f" {len(posts)} постов ({topic_pages} стр.), "
f"теги: {tags}")
save_topic(section_slug, section_name, tp, posts, tags, st)
st["topics_done"][str(tid)] = tp["title"]
save(section_slug, st)
st["pages_done"].append(pg)
save(section_slug, st)
# ── Итог ────────────────────────────────────────────────────────────
log.info("=" * 60)
log.info(f"✅ '{section_name}' — {st['topic_count']} тем "
f{st['file_index']+1} частях")
for f in sorted((OUTPUT_DIR / section_slug).glob("part_*.jsonl")):
log.info(f" 📄 {f.name} ({f.stat().st_size/1024/1024:.1f} MB)")
log.info("=" * 60)