scraper: новая архитектура — core/ + forums/ (BaseAdapter, XenForo, phpBB)
- 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-тем при многопоточном парсинге
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Пагинация phpBB.
|
||||
|
||||
phpBB использует параметр ?start=N для пагинации, где:
|
||||
N = (page - 1) * items_per_page
|
||||
|
||||
Страницы раздела:
|
||||
viewforum.php?f=22&start=25 ← страница 2
|
||||
viewforum.php?f=22&start=50 ← страница 3
|
||||
...
|
||||
|
||||
Страницы темы:
|
||||
viewtopic.php?f=22&t=123&start=10 ← страница 2
|
||||
viewtopic.php?f=22&t=123&start=20 ← страница 3
|
||||
...
|
||||
|
||||
HTML-структура пагинации:
|
||||
<ul class="pagination">
|
||||
<li><a href="./viewforum.php?f=22&start=25">2</a></li>
|
||||
<li><a href="./viewforum.php?f=22&start=50">3</a></li>
|
||||
...
|
||||
<li><a href="./viewforum.php?f=22&start=1800">72</a></li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def count_pages(soup: BeautifulSoup, per_page: int) -> int:
|
||||
"""Определить количество страниц в пагинации phpBB.
|
||||
|
||||
Алгоритм:
|
||||
1. Ищем ul.pagination (или ul.page-nav).
|
||||
2. Собираем все start=N из href.
|
||||
3. Находим max_start.
|
||||
4. Вычисляем: pages = max_start / per_page + 1.
|
||||
|
||||
Args:
|
||||
soup: BeautifulSoup страницы раздела или темы.
|
||||
per_page: Количество элементов на странице (темы=25, посты=10).
|
||||
|
||||
Returns:
|
||||
int: количество страниц (минимум 1).
|
||||
"""
|
||||
# Ищем контейнер пагинации (два варианта класса)
|
||||
nav = soup.find("ul", class_="pagination")
|
||||
if nav is None:
|
||||
nav = soup.find("ul", class_="page-nav")
|
||||
if nav is None:
|
||||
return 1
|
||||
|
||||
# Собираем все start=N из ссылок
|
||||
starts = set()
|
||||
for link in nav.select("a"):
|
||||
href = link.get("href", "")
|
||||
match = re.search(r"[?&]start=(\d+)", href)
|
||||
if match:
|
||||
starts.add(int(match.group(1)))
|
||||
|
||||
if not starts:
|
||||
return 1
|
||||
|
||||
max_start = max(starts)
|
||||
pages = (max_start // per_page) + 1
|
||||
return pages
|
||||
|
||||
|
||||
def page_url(base_url: str, page_num: int, per_page: int) -> str:
|
||||
"""Сформировать URL страницы раздела phpBB.
|
||||
|
||||
Формат:
|
||||
page=1 → base_url (без ?start=)
|
||||
page>1 → base_url + &start=N
|
||||
|
||||
Args:
|
||||
base_url: URL страницы без параметра start (напр. viewforum.php?f=22).
|
||||
page_num: Номер страницы (1-based).
|
||||
per_page: Количество элементов на страницу.
|
||||
|
||||
Returns:
|
||||
str: URL страницы с ?start= (если page>1).
|
||||
"""
|
||||
if page_num <= 1:
|
||||
return base_url
|
||||
|
||||
start = (page_num - 1) * per_page
|
||||
separator = "&" if "?" in base_url else "?"
|
||||
return f"{base_url}{separator}start={start}"
|
||||
|
||||
|
||||
def topic_page_url(topic_url: str, page_num: int, per_page: int) -> str:
|
||||
"""Сформировать URL страницы темы phpBB.
|
||||
|
||||
Args:
|
||||
topic_url: Полный URL темы.
|
||||
page_num: Номер страницы (1-based, начиная с 2).
|
||||
per_page: Количество постов на страницу.
|
||||
|
||||
Returns:
|
||||
str: URL страницы темы с ?start=.
|
||||
"""
|
||||
if page_num <= 1:
|
||||
return topic_url
|
||||
|
||||
start = (page_num - 1) * per_page
|
||||
separator = "&" if "?" in topic_url else "?"
|
||||
return f"{topic_url}{separator}start={start}"
|
||||
Reference in New Issue
Block a user