Files
LLM-UI/vwts_scraper/paginate.py
T
naeel 010b10c43e refactor: vwts_scraper — proper Python package structure
- Split single file into 8 focused modules (399 lines total):
  config.py — constants
  fetch.py — HTTP GET with retries
  paginate.py — page_count + HEAD verification
  parse.py — HTML parsing (topics, posts, tags)
  state.py — per-section state.json
  output.py — JSONL with file rotation
  run.py — main orchestration loop
  __main__.py — CLI entry point
- Run: python -m vwts_scraper <URL>
- Page count method fully tested (13 sections, 100+ topics, 0 errors)
- Resume support (Ctrl+C → state saved)
- Per-section state files (no conflicts between sections)
2026-06-04 08:55:23 +03:00

82 lines
2.8 KiB
Python

"""Определение количества страниц в пагинации XenForo.
Работает ТОЛЬКО внутри div.pageNav — не ловит числа из аватаров,
текста постов, путей к файлам (например /avatars/m/32/32165.jpg).
"""
import re, logging
from bs4 import BeautifulSoup
log = logging.getLogger(__name__)
def count(soup: BeautifulSoup) -> int:
"""Сколько страниц. Если пагинации нет — 1.
Принцип:
<div class="pageNav">
<a>1</a> ← текущая (без page-N в href)
<a href="page-2">2</a> ← соседняя
<a>…</a> ← эллипсис → ValueError → пропускаем
<a href="page-218">218</a> ← последняя
</div>
ВСЕ номера страниц видны на любой странице.
Кнопки <button>Выполнить</button> не трогаем — ищем только <a>.
"""
nav = soup.select_one("div.pageNav")
if not nav:
return 1
nums = set()
for a in nav.select("a"):
href = a.get("href", "")
text = a.text.strip()
# page-N из href
m = re.search(r"page-(\d+)", href)
if m:
nums.add(int(m.group(1)))
# текущая страница: число без page-N в href
try:
nums.add(int(text))
except ValueError:
pass # "…", "Назад", "Вперёд"
return max(nums) if nums else 1
def verify_last(section_url: str, claimed: int, session) -> int:
"""HEAD-запросами проверяет реальную последнюю страницу.
Если сайт показывает 218, но page-218 → 404 (админ потёр часть тем,
пагинацию не пересчитали) — бинарным поиском находит реальную границу.
"""
if claimed <= 1:
return claimed
try:
import requests
r = session.head(f"{section_url}page-{claimed}",
timeout=(10, 30), allow_redirects=True)
if r.status_code < 400:
return claimed
# Бинарный поиск
lo, hi = 1, claimed
while lo < hi:
mid = (lo + hi + 1) // 2
hr = session.head(f"{section_url}page-{mid}",
timeout=(10, 30), allow_redirects=True)
if hr.status_code >= 400:
hi = mid - 1
else:
lo = mid
log.warning(f" Пагинация врала: {claimed} → реально {lo}")
return lo
except Exception:
return claimed # не смогли — верим сайту