From ea489454c027d6bcdbdcee1fd392a1e065e106cd Mon Sep 17 00:00:00 2001 From: goon-foss Date: Mon, 27 Jul 2026 11:42:05 +0200 Subject: [PATCH] feat(sexu): scraper + extractor HLS, ale bez rejestracji w ingescie Metadane ma najlepsze z tej serii: kanal = realne studio na 97% scen, 96% performerow z refem tpdb/stashdb, dokladna dlugosc w sekundach, swiezosc w godzinach, 120 scen na stronie listingu. Nie wlaczamy go jednak do ALL_BROWSE_SCRAPERS, bo jednostka tresci jest zla: to klipy 5-10 minut, nie sceny. Mediana 571 s na stronie 30 i 459 s na 200, zero filmow >=20 min w calym katalogu. Pilot na 240 scenach dal 1 canonical match, bo dlugosc i tytul, czyli sygnaly resolvera, sa przepisane pod tube. Przy ~340 uploadach dziennie to ~10k nierozwiazywalnych wierszy miesiecznie. Kod zostaje kompletny i zweryfikowany: POST /api/video-info przez Bright Data (CF blokuje sam POST z VPS), HLS playerData.src zamiast mp4 sources (te maja ip= requestera w tokenie), master konczy sie .mp4 wiec lapie go istniejacy hls_needs_passthrough. Gdyby serwis zaczal wrzucac pelne sceny, wystarczy dopisac klase z powrotem do rejestru. Co-Authored-By: Claude Opus 5 --- app/connectors/direct_scrapers/__init__.py | 9 + app/connectors/direct_scrapers/sexu.py | 188 +++++++++++++++++++++ app/extractors/__init__.py | 6 + app/extractors/tubes/sexu.py | 95 +++++++++++ 4 files changed, 298 insertions(+) create mode 100644 app/connectors/direct_scrapers/sexu.py create mode 100644 app/extractors/tubes/sexu.py diff --git a/app/connectors/direct_scrapers/__init__.py b/app/connectors/direct_scrapers/__init__.py index 0b2ba72..9834329 100644 --- a/app/connectors/direct_scrapers/__init__.py +++ b/app/connectors/direct_scrapers/__init__.py @@ -189,6 +189,15 @@ ALL_BROWSE_SCRAPERS: list[type[BaseBrowseScraper]] = [ # na obsadę przepuszcza 62/115, a z nich 97% performerów ma ref tpdb/stashdb. # Pilot: seen 62, new 25, updated 37 — 60% dopina się do scen, które już mamy. KPorner8Scraper, + # SexuScraper (`sexu.py`) — ZBUDOWANY 2026-07-27, ale NIE ingestujemy. Metadane ma + # najlepsze z całej serii (studio 97%, 96% performerów z refem tpdb/stashdb, dokładna + # długość, świeżość w godzinach), tylko jednostka treści jest zła: to **klipy 5-10 + # minut**, nie sceny. Mediana 571 s na stronie 30 i 459 s na 200, ZERO filmów ≥20 min + # w całym katalogu. Pilot na 240 scenach: 1 (jeden) canonical match, bo długość i + # tytuł, czyli sygnały resolvera, są przepisane pod tube. Przy ~340 uploadach dziennie + # to ~10k nierozwiązywalnych wierszy miesięcznie i klip obok pełnej sceny na stronie + # performera. Scraper i extractor zostają w repo — gdyby serwis kiedyś zaczął wrzucać + # pełne sceny, wystarczy dopisać klasę z powrotem tutaj. # FullVideosPornScraper — dodany 2026-07-27 (ocena 3/5, ADD Z ZASTRZEŻENIAMI). # = sextu.com pod nową marką (NIE klon fullmovies.xxx). BRAMKA NA OBSADĘ: 65-75% # katalogu nie ma performera i weszłoby jako puste orphany, więc ingestujemy tylko diff --git a/app/connectors/direct_scrapers/sexu.py b/app/connectors/direct_scrapers/sexu.py new file mode 100644 index 0000000..77398db --- /dev/null +++ b/app/connectors/direct_scrapers/sexu.py @@ -0,0 +1,188 @@ +"""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"]*>\s*([^<]+?)\s*") +_DURATION_RE = re.compile(r'property="video:duration"\s+content="(\d+)"') +_OG_IMAGE_RE = re.compile(r'property="og:image"\s+content="([^"]+)"') +_DATE_RE = re.compile(r'player-block__date">\s*([^<]+?)\s*<') +_REL_DATE_RE = re.compile( + r"(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago", re.IGNORECASE +) +_UNIT_DAYS = { + "second": 0, "minute": 0, "hour": 0, + "day": 1, "week": 7, "month": 30, "year": 365, +} +# Bloki metadanych: `
Tags:
`. +# 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 `