Files
naeel 1cfa6d1595 fix: audit findings — edge cases in paginate, output, run
- paginate.py: max(nums) on empty set → guard
- paginate.py: HEAD fallback to GET if server doesn't support HEAD
- output.py: disk health check every 500 topics
- run.py: avoid page-1 extra redirect
- __main__.py: URL validation (must contain vwts.ru)
2026-06-04 08:58:09 +03:00

92 lines
3.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Определение количества страниц в пагинации 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 # защита от пустого pageNav
def _head_or_get(session, url: str, timeout=(10, 30)) -> int:
"""HEAD-запрос с fallback на GET (не все серверы поддерживают HEAD)."""
try:
r = session.head(url, timeout=timeout, allow_redirects=True)
if r.status_code in (405, 501): # HEAD не поддерживается
r = session.get(url, timeout=timeout,
allow_redirects=True, stream=True)
r.close()
return r.status_code
except Exception:
return 500
def verify_last(section_url: str, claimed: int, session) -> int:
"""HEAD-запросами проверяет реальную последнюю страницу.
Если сайт показывает 218, но page-218 → 404 (админ потёр часть тем,
пагинацию не пересчитали) — бинарным поиском находит реальную границу.
"""
if claimed <= 1:
return claimed
try:
status = _head_or_get(session, f"{section_url}page-{claimed}")
if status < 400:
return claimed
# Бинарный поиск последней существующей страницы
lo, hi = 1, claimed
while lo < hi:
mid = (lo + hi + 1) // 2
hr = _head_or_get(session, f"{section_url}page-{mid}")
if hr >= 400:
hi = mid - 1
else:
lo = mid
log.warning(f" Пагинация врала: {claimed} → реально {lo}")
return lo
except Exception:
return claimed