Files
LLM-UI/scraper/forums/base.py
naeel 5c142096de 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-тем при многопоточном парсинге
2026-06-06 11:46:57 +04:00

180 lines
6.8 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, phpBB, ...) реализует этот интерфейс.
core/runner.py вызывает эти методы, ничего не зная о конкретном движке.
Методы для обязательной реализации:
count_pages(soup) -> int
parse_topics(soup) -> list[dict]
parse_posts(soup) -> list[dict]
parse_section_name(soup) -> str | None
page_url(section_url, page_num) -> str
topic_url(section_url, topic) -> str
topic_page_url(topic_url, page_num) -> str
Опционально:
parse_tags(soup) -> list[str]
Атрибуты (должны быть определены в конструкторе адаптера):
data_dir: Path — куда сохранять данные.
delay: float — задержка между запросами (сек).
ban_test_count: int — сколько запросов для проверки бана.
topics_per_file: int — сколько тем в один JSONL-файл.
"""
from abc import ABC, abstractmethod
class BaseAdapter(ABC):
"""Интерфейс адаптера форума.
Все методы, кроме data_dir, delay и т.д., получают
BeautifulSoup-объект и возвращают структурированные данные.
"""
# ─── Атрибуты конфигурации ──────────────────────────────────────────
@property
@abstractmethod
def data_dir(self):
"""Path: корневая директория для сохранения данных."""
...
@property
@abstractmethod
def delay(self) -> float:
"""float: задержка между запросами в секундах."""
...
@property
@abstractmethod
def ban_test_count(self) -> int:
"""int: количество быстрых запросов для проверки бана."""
...
@property
@abstractmethod
def topics_per_file(self) -> int:
"""int: сколько тем писать в один JSONL-файл."""
...
# ─── Определение количества страниц ─────────────────────────────────
@abstractmethod
def count_pages(self, soup) -> int:
"""Сколько страниц в разделе (или теме).
Args:
soup: BeautifulSoup первой страницы раздела/темы.
Returns:
int: количество страниц (минимум 1).
"""
...
# ─── Парсинг списка тем ─────────────────────────────────────────────
@abstractmethod
def parse_topics(self, soup) -> list[dict]:
"""Распарсить список тем со страницы раздела.
Args:
soup: BeautifulSoup страницы раздела.
Returns:
list[dict]: каждая тема — словарь с ключами:
- id: int — ID темы на форуме.
- title: str — заголовок темы.
- url: str | None — URL темы (если известен).
"""
...
# ─── Парсинг постов ─────────────────────────────────────────────────
@abstractmethod
def parse_posts(self, soup) -> list[dict]:
"""Распарсить посты со страницы темы.
Args:
soup: BeautifulSoup страницы темы.
Returns:
list[dict]: каждый пост — словарь с ключами:
- author: str — имя автора.
- date: str — дата/время поста.
- num: str — номер поста (#1, #2...).
- text: str — текст поста.
"""
...
# ─── Парсинг названия раздела ───────────────────────────────────────
@abstractmethod
def parse_section_name(self, soup) -> str | None:
"""Извлечь название раздела из HTML.
Args:
soup: BeautifulSoup страницы раздела.
Returns:
str | None: название (напр. "Бензиновые двигатели")
или None, если не удалось определить.
"""
...
# ─── Формирование URL ───────────────────────────────────────────────
@abstractmethod
def page_url(self, section_url: str, page_num: int) -> str:
"""Сформировать URL страницы раздела.
Args:
section_url: URL раздела (первая страница).
page_num: Номер страницы (1-based).
Returns:
str: полный URL страницы.
"""
...
@abstractmethod
def topic_url(self, section_url: str, topic: dict) -> str:
"""Сформировать полный URL темы.
Args:
section_url: URL раздела.
topic: Словарь темы из parse_topics().
Returns:
str: полный URL темы.
"""
...
@abstractmethod
def topic_page_url(self, topic_url: str, page_num: int) -> str:
"""Сформировать URL страницы внутри темы.
Args:
topic_url: Полный URL темы.
page_num: Номер страницы (1-based, начиная с 2).
Returns:
str: полный URL страницы темы.
"""
...
# ─── Опционально: теги ──────────────────────────────────────────────
def parse_tags(self, soup) -> list[str]:
"""Распарсить теги темы (если движок поддерживает).
По умолчанию — пустой список.
Args:
soup: BeautifulSoup страницы темы.
Returns:
list[str]: список тегов.
"""
return []