"""sexu.com — browse scraper. Dodany 2026-07-27.
Najlepsze metadane z całej tegorocznej serii kandydatów. Próbka 25 najnowszych scen:
obsada 20/25 (80%), **kanał 25/25 i to realne studia** (New Sensations, Brutal X,
Moms Bang Teens, 2 Girls 1 Camera), dokładna długość w sekundach 25/25, data 25/25.
Świeżość: cała próbka miała 7-8 godzin. Listing daje 120 scen na stronę.
Dlatego **nie ma tu bramki na obsadę** (w odróżnieniu od fullvideosporn czy 8kporner).
Te 20% scen bez performera i tak niesie studio + dokładną długość + oryginalny tytuł,
czyli komplet sygnałów, których resolver potrzebuje do canonical matcha. Bramka
odrzuciłaby dobre dane.
**Dwie pułapki w HTML, obie sprawdzone:**
1. **Tagi trzeba wyciąć z bloku.** Linków `/tag/` jest na stronie 25, ale realnych
tagów sceny tylko 5 (reszta to sidebar „popularne wyszukiwania"). Bez zawężenia
do bloku „Tags:" wpisalibyśmy każdej scenie te same 20 śmieci.
2. **W bloku tagów siedzą też linki `/search?q=`** — to fraz-y pod SEO („pleases",
„hole", „indian milf"), nie tagi. Bierzemy wyłącznie `/tag/`.
**Studio.** Kanał to zwykle studio, ale przy uploadach amatorskich bywa nazwany po
performerze (`/channel/priya+rai` przy scenie z Priya Rai). Taki kanał odrzucamy,
bo performer w tabeli studios to zanieczyszczenie, nie dana.
Data jest względna („7 hours ago", „yesterday"), więc liczymy ją wstecz od teraz.
To i tak data uploadu na tube, nie premiery studia.
Playback: `POST /api/video-info` (extractor `sexu`) — wymaga Bright Data, bo
Cloudflare blokuje POST z VPS. Sam scrape listingu i detali idzie bez proxy.
"""
from __future__ import annotations
import html as html_mod
import logging
import re
from datetime import UTC, datetime, timedelta
from app.connectors.base import (
RawPerformer,
RawPlaybackSource,
RawScene,
RawStudio,
RawTag,
)
from app.connectors.direct_scrapers._browse_base import BaseBrowseScraper
from app.normalize.text import slugify
log = logging.getLogger(__name__)
_BASE = "https://sexu.com"
_SCENE_HREF_RE = re.compile(r'href="(/\d+/)"')
_TITLE_RE = re.compile(r"
`.
# Linki sceny są między tytułem a `_container` (tam zaczyna się formularz „Suggest…").
_BLOCK_END_RE = re.compile(r"_container|player-tags__title")
def _block(detail_html: str, label: str) -> str:
"""Fragment HTML należący do bloku `