- 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-тем при многопоточном парсинге
110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""Пагинация XenForo (vwts.ru и другие форумы на XenForo).
|
|
|
|
Страницы раздела: /forum/vag/benzinovye-dvigateli/page-2
|
|
Страницы темы: /forum/topic/12345/page-2
|
|
|
|
HTML-структура пагинации:
|
|
<div class="pageNav">
|
|
<a>1</a> ← текущая (без href)
|
|
<a href="page-2">2</a> ← соседняя
|
|
<a>…</a> ← эллипсис (пропускаем)
|
|
<a href="page-218">218</a> ← последняя
|
|
</div>
|
|
|
|
Все номера страниц видны на любой странице раздела.
|
|
Кнопки <button> не трогаем — ищем только <a>.
|
|
"""
|
|
|
|
import re
|
|
import logging
|
|
from bs4 import BeautifulSoup
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def count_pages(soup: BeautifulSoup) -> int:
|
|
"""Определить количество страниц в пагинации XenForo.
|
|
|
|
Ищем:
|
|
<div class="pageNav"> → все <a>
|
|
Из href вытаскиваем /page-N
|
|
Из текста — числа
|
|
Возвращаем максимум
|
|
|
|
Args:
|
|
soup: BeautifulSoup страницы раздела или темы.
|
|
|
|
Returns:
|
|
int: количество страниц (минимум 1).
|
|
"""
|
|
nav = soup.select_one("div.pageNav")
|
|
|
|
# Нет пагинации — всего 1 страница
|
|
if nav is None:
|
|
return 1
|
|
|
|
nums = set()
|
|
|
|
# Проходим по всем ссылкам внутри пагинации
|
|
for link in nav.select("a"):
|
|
href = link.get("href", "")
|
|
text = link.text.strip()
|
|
|
|
# Из href вытаскиваем page-N
|
|
match = re.search(r"page-(\d+)", href)
|
|
if match:
|
|
nums.add(int(match.group(1)))
|
|
|
|
# Из текста — тоже числа (текущая страница без href)
|
|
try:
|
|
nums.add(int(text))
|
|
except ValueError:
|
|
# "…", "Назад", "Вперёд" — пропускаем
|
|
pass
|
|
|
|
# Защита от пустой пагинации
|
|
return max(nums) if nums else 1
|
|
|
|
|
|
def page_url(section_url: str, page_num: int) -> str:
|
|
"""Сформировать URL страницы раздела XenForo.
|
|
|
|
Формат:
|
|
page-1 (первая) → сам section_url
|
|
page-N (N>1) → section_url + page-N
|
|
|
|
Args:
|
|
section_url: URL раздела (с / на конце).
|
|
page_num: Номер страницы (1-based).
|
|
|
|
Returns:
|
|
str: полный URL страницы.
|
|
"""
|
|
if page_num <= 1:
|
|
return section_url
|
|
|
|
# Убеждаемся что URL заканчивается на /
|
|
base = section_url.rstrip("/") + "/"
|
|
return f"{base}page-{page_num}"
|
|
|
|
|
|
def topic_page_url(topic_url: str, page_num: int) -> str:
|
|
"""Сформировать URL страницы темы XenForo.
|
|
|
|
Формат:
|
|
page-1 → сам topic_url
|
|
page-N → topic_url + page-N
|
|
|
|
Args:
|
|
topic_url: Полный URL темы.
|
|
page_num: Номер страницы (1-based).
|
|
|
|
Returns:
|
|
str: URL страницы темы.
|
|
"""
|
|
if page_num <= 1:
|
|
return topic_url
|
|
|
|
base = topic_url.rstrip("/") + "/"
|
|
return f"{base}page-{page_num}"
|