"""Парсинг HTML форума XenForo (vwts.ru). Структуры: Список тем в разделе:
← контейнер темы
Заголовок темы
Пост в теме:
← контейнер поста

Автор

← тело поста ...
Теги темы:
  • Название тега
  • """ import re import logging from bs4 import BeautifulSoup log = logging.getLogger(__name__) def parse_topics(soup: BeautifulSoup) -> list[dict]: """Распарсить список тем со страницы раздела XenForo. Ищем: div.structItem → a с href, содержащим /topic/ID/ Args: soup: BeautifulSoup страницы раздела. Returns: list[dict]: [{id, title, url}, ...]. """ items = [] for container in soup.select("div.structItem"): # Ссылка на тему link = container.select_one("div.structItem-title a") if link is None: continue href = link.get("href", "") # Из href вытаскиваем ID темы: /topic/12345/ match = re.search(r"/topic/(\d+)/", href) if match is None: continue topic_id = int(match.group(1)) title = link.text.strip() # Формируем полный URL (если относительный) if href.startswith("/"): full_url = f"https://vwts.ru{href}" elif href.startswith("http"): full_url = href else: full_url = f"https://vwts.ru/forum/topic/{topic_id}/" items.append({ "id": topic_id, "title": title, "url": full_url, }) return items def parse_posts(soup: BeautifulSoup) -> list[dict]: """Распарсить посты со страницы темы XenForo. Ищем: article.message → автор, дата, номер, текст Args: soup: BeautifulSoup страницы темы. Returns: list[dict]: [{author, date, num, text}, ...]. """ posts = [] for article in soup.select("article.message"): # ── Автор ─────────────────────────────────────────────────────── author_elem = article.select_one("h4.message-name") author = author_elem.get_text(strip=True) if author_elem else "?" # ── Дата ──────────────────────────────────────────────────────── time_elem = article.select_one("time.u-dt") date = time_elem.get("datetime", "") if time_elem else "" # ── Номер поста (#1, #2...) ───────────────────────────────────── num_elem = article.select_one("ul.message-attribution-opposite a") num = num_elem.text.strip() if num_elem else "" # ── Текст поста ───────────────────────────────────────────────── content = article.select_one("div.message-content") text = "" if content is not None: # Удаляем скрытые элементы (спойлеры, скрытые блоки) for hidden in content.select( "div[style*='none'], span[style*='none'], details", ): hidden.decompose() # Извлекаем текст text = content.get_text("\n", strip=True) posts.append({ "author": author, "date": date, "num": num, "text": text, }) return posts def parse_tags(soup: BeautifulSoup) -> list[str]: """Распарсить теги темы XenForo. Ищем: li.tagItem → a → текст тега Args: soup: BeautifulSoup страницы темы. Returns: list[str]: список тегов (может быть пустым). """ return [ tag.text.strip() for tag in soup.select("li.tagItem a") ] def parse_section_name(soup: BeautifulSoup) -> str | None: """Извлечь название раздела из h1. Args: soup: BeautifulSoup страницы раздела. Returns: str: название раздела или None. """ h1 = soup.select_one("h1") if h1 is not None: return h1.text.strip() return None