- 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)
92 lines
3.2 KiB
Python
92 lines
3.2 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 # защита от пустого 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
|