From b485511f3f94bfd226dd3ba6f445465327e3fee5 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Mon, 27 Jul 2026 12:04:34 +0200 Subject: [PATCH] feat(fullporner): browse scraper pelnych scen + extractor bez tokenu Pelne sceny, nie klipy: mediana ~33 min, zero ponizej 10 min, ~140-240 dziennie, listing chronologiczny mimo naglowka Featured. Extractor sklada URL sam, bez tokenu i bez wygasania: id w iframe jest zapisane wspak, a koncowka to maska bitowa jakosci (1/2/4/8 = 360/480/720/1080). Id bywa numeryczne albo szesnastkowe, a to drugie to ~25% katalogu. Blokada CDN jest na naglowku (bez UA 403, bez Referera 404), NIE na fingerprincie TLS, wiec telefon przechodzi -> mobile_direct_ok. Zweryfikowane 206 + faststart do 1080p. Metadane cienkie: brak studia, mediana 3 tagi, tytuly przepisane pod SEO. Za to data jako unix timestamp i dlugosc co do sekundy. Bramka na obsade, bo sceny bez performera scalily sie 0/23 w dry-runie. Phash miniatury liczony celowo i to on robi robote: dry-run resolvera bez niego dawal 31% scalen, pilot z nim dal 65% (seen 62, new 22, updated 40, errors 0). 85 z 89 performerow ma ref tpdb/stashdb. Co-Authored-By: Claude Opus 5 --- app/connectors/direct_scrapers/__init__.py | 11 ++ app/connectors/direct_scrapers/fullporner.py | 167 +++++++++++++++++++ app/extractors/__init__.py | 5 + app/extractors/tubes/fullporner.py | 77 +++++++++ 4 files changed, 260 insertions(+) create mode 100644 app/connectors/direct_scrapers/fullporner.py create mode 100644 app/extractors/tubes/fullporner.py diff --git a/app/connectors/direct_scrapers/__init__.py b/app/connectors/direct_scrapers/__init__.py index 9834329..e2c376d 100644 --- a/app/connectors/direct_scrapers/__init__.py +++ b/app/connectors/direct_scrapers/__init__.py @@ -48,6 +48,7 @@ from app.connectors.direct_scrapers.galaxyporn import GalaxyPornScraper from app.connectors.direct_scrapers.pornbusy import PornBusyScraper from app.connectors.direct_scrapers.pornmike import PornMikeScraper from app.connectors.direct_scrapers.kporner8 import KPorner8Scraper +from app.connectors.direct_scrapers.fullporner import FullPornerScraper from app.connectors.direct_scrapers.watchporn import WatchPornScraper from app.connectors.direct_scrapers.youperv import YoupervScraper from app.connectors.direct_scrapers.xhamster import XHamsterScraper @@ -189,6 +190,16 @@ 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, + # FullPornerScraper — dodany 2026-07-27. PEŁNE sceny (mediana ~33 min, zero poniżej + # 10 min), ~140-240 dziennie, listing chronologiczny mimo nagłówka „Featured". + # Playback najtrwalszy w portfolio: mp4 do 1080p bez tokenu i bez wygasania. + # Metadane cienkie: BRAK studia, mediana 3 tagi, tytuły przepisane pod SEO — za to + # data jako unix timestamp i długość co do sekundy. Dry-run resolvera na 120 + # scenach: 31% dopina się do tego, co mamy (composite waży performera + długość), + # przeciek duplikatów ~15% po odjęciu tła kontrolnego. Bramka na obsadę, bo sceny + # bez performera scaliły się 0/23. phash liczony celowo — to jedyny sygnał dla + # bulk_dedup na te duplikaty, po tytule nie pójdzie. + FullPornerScraper, # 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 diff --git a/app/connectors/direct_scrapers/fullporner.py b/app/connectors/direct_scrapers/fullporner.py new file mode 100644 index 0000000..4e86049 --- /dev/null +++ b/app/connectors/direct_scrapers/fullporner.py @@ -0,0 +1,167 @@ +"""fullporner.com — browse scraper. Dodany 2026-07-27. + +Pełne sceny, nie klipy: mediana długości 2067 s na stronie 1, 1911 s na 40 i 1969 s +na 300, zero filmów poniżej 10 minut w próbce. To jest różnica, przez którą sexu +odpadł, a ten wchodzi. + +Listing jest ściśle chronologiczny, mimo że nagłówek mówi „Featured Videos" (strona +1-3 = dziś, 10 = doba, 50 = 9 dni, 500 = 86 dni), 24 sceny na stronę. Daje to +~140-240 pełnych scen dziennie i katalog co najmniej 12 tys. scen wstecz. + +**Metadane są cienkie i trzeba to wiedzieć wprost.** Nie ma studia. Tagów jest +mediana 3. Tytuły są w 100% przepisane pod SEO, więc nie niosą nic z tytułu +studyjnego („POV Blowjob Queen Takes It Hard and Deep" tam, gdzie kanon ma „Blacked +– Dylan Moore – Petite Blonde Squirt"). Za to data jest podana jako **unix timestamp** +(dokładna, nie względna) i długość co do sekundy. + +Dry-run resolvera na 120 scenach (w rollbacku): 37 (31%) dopięło się do scen, które +już mamy, przez `composite_auto` — composite radzi sobie mimo złych tytułów, bo waży +performera i długość. Reszta to nowa podaż. Zmierzony przeciek duplikatów, po +odjęciu tła z kontroli na przesuniętej długości: ~15% katalogu. Liczymy phash +miniaturki właśnie po to, żeby późniejszy `bulk_dedup` miał czym te duplikaty łapać. + +**Bramka na obsadę.** Sceny bez performera (19%) w dry-runie scaliły się w 0 na 23 +przypadków — bez performera composite nie ma na czym pracować, a studia tu nie ma +i tytuł jest zmyślony, więc taka scena nie ma ŻADNEGO sygnału kanonicznego. Wchodzą +tylko sceny z obsadą. + +Playback: `xiaoshenke.net` (extractor `fullporner`), bezpośredni mp4 do 1080p bez +tokenu i bez wygasania. +""" +from __future__ import annotations + +import html as html_mod +import logging +import re +from datetime import UTC, datetime + +from app.connectors.base import ( + RawFingerprint, + RawPerformer, + RawPlaybackSource, + RawScene, + RawTag, +) +from app.connectors.direct_scrapers._browse_base import ( + BaseBrowseScraper, + compute_thumbnail_phash, +) +from app.normalize.text import slugify + +log = logging.getLogger(__name__) + +_BASE = "https://fullporner.com" +_THUMB = "https://imgs.xiaoshenke.net/thumb/{}.jpg" + +_SCENE_HREF_RE = re.compile(r'href="(/watch/[a-f0-9]+)"') +_TITLE_RE = re.compile(r"]*>\s*([^<]+?)\s*") +# `1785081937
36:33
` — data i długość +# siedzą obok siebie w `video-info`, więc bierzemy je jednym wzorcem. +_INFO_RE = re.compile( + r'class="create">(\d+)\s*\s*
\s*([0-9:]+)\s*
' +) +_PERF_BLOCK_RE = re.compile(r"\s*Pornstars?:\s*(.*?)

", re.IGNORECASE | re.DOTALL) +_PERF_RE = re.compile(r'href="/pornstar/[^"]+"[^>]*>\s*([^<]+?)\s*') +_TAG_BLOCK_RE = re.compile(r'class="[^"]*tag-link[^"]*"[^>]*>(.*?)

', re.DOTALL) +_TAG_RE = re.compile(r'href="/category/[^"]+"[^>]*>#\s*([^<]+?)\s*') +# `