goon/app/connectors/direct_scrapers/__init__.py
goon-foss d716b0c75d feat(pornbusy): browse scraper + loadvid POST-manifest producer
pornbusy.com exposes every field as a discrete itemprop node (title,
duration, uploadDate, thumbnail, description) plus a dedicated
<div id="video-actors"> cast block with no sidebar pollution. Measured
orphan risk is low: 80% of a 100-scene sample have a performer that
already carries a tpdb/stashdb ref, and 21% of titles strongly match
scenes we already hold. No studio field exists on the site.

Homepage pagination is broken (pages 1/2/3 return an identical set), so
the listing is driven off sitemap_index.xml -> 10 post-sitemaps sorted by
lastmod, chunked in crawl_page the same way the PlayTube base does it.

Playback dispatches on embedURL and covers ~69% of the catalog: the
seekplayer family (already handled by the engine after the earlier host
regex widening) and zpi.cx (the embedURL is the file itself), plus loadvid
at ~41%, which needed new plumbing.

loadvid hands back the CONTENT of an m3u8 over POST /videos/resolve-token
(CSRF + videoToken from the embed page) and has no manifest URL at all:
GET on that endpoint is 405 and the guessable .m3u8 paths are 404. So
make_token grew an optional `producer` marker and /proxy/hls calls the
producer instead of GETting a URL. Segments in the returned manifest are
absolute and fully portable (verified: 206 on a Range request with no
headers, no referer, no token), so the phone still pulls them straight
from the CDN and only the manifest travels through the VPS.

upload18 (~12%) and the tail are deliberately left unresolved: their token
embeds the fetcher's /24, so resolving server-side would force the whole
video through the VPS.

Verified: 19 scenes/page, 19/19 with duration, 17/19 with cast, 19/19 with
tags (names read from the title attribute since an icon element precedes
the anchor text), playback 8/8 via loadvid, and /proxy/hls returning a
116KB manifest with 869 absolute segments. Pilot ingest of 3 pages: 59
seen, 35 merged into existing scenes, 24 new, 0 errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-07-27 08:45:00 +02:00

283 lines
19 KiB
Python

"""Direct tube scrapers.
Każdy scraper hit'uje tube bezpośrednio HTTPm — różne tube'y to różne rate limit
budgets, więc mogą iść równolegle. Wszystkie feedują sceny do tej samej
`Source(name=SCRAPER_SOURCE_NAME)` z external_id `f"{sitetag}:{url}"`. Resolver
mergeuje idempotentnie po tym kluczu.
Nazwa źródła: do 2026-06-07 brzmiała `"pornapp"` — myląca pozostałość po usuniętym
zewnętrznym porn-app API (sugerowała zależność od obcego serwisu, której NIE MA —
to nasze własne direct-scrapery tubów). Przemianowana na `"tube-scraper"`; wiersz
`sources` zaktualizowany w DB (UPDATE name) więc cała historia ingest_runs została.
Search-based ścieżka (per performer name); category browse'ng przez `categoriesUrl`.
UWAGA — speculative scrapers: większość aggregator + special tubes (xmoviesforyou,
watchporn, siska, porn4days, porndish, xxxfreewatch, latestleaks, mypornerleak,
porndittcom, perverzija, fpoxxx, ...) ma URL templates + regex'y oparte na typowych
WordPress conventions. Wymagają post-deploy verification — gdy któryś nie zwraca
wyników, sprawdź real search HTML + popraw template/regex w odpowiednim pliku.
"""
# Umbrella Source.name dla wszystkich direct-scraperów (deep-crawl, browse-latest,
# performer-driven). Rename z legacy "pornapp" 2026-06-07 (mylące — nie ma zależności
# od zewnętrznego porn-app API).
SCRAPER_SOURCE_NAME = "tube-scraper"
from app.connectors.direct_scrapers._browse_base import BaseBrowseScraper
from app.connectors.direct_scrapers.base import BaseDirectTubeScraper
from app.connectors.direct_scrapers.eporner import EpornerScraper
from app.connectors.direct_scrapers.fpoxxx import FpoxxxScraper
from app.connectors.direct_scrapers.hdporn92 import HDPorn92Scraper # noqa: F401 — kept for backref; disabled
from app.connectors.direct_scrapers.hqporner import HQPornerScraper
from app.connectors.direct_scrapers.latestleaks import LatestLeaksScraper
from app.connectors.direct_scrapers.latestpornvideo import LatestPornVideoScraper
from app.connectors.direct_scrapers.mypornerleak import MyPornerLeakScraper
from app.connectors.direct_scrapers.mypornerleak_browse import MyPornerLeakBrowseScraper
from app.connectors.direct_scrapers.perverzija import PerverzijaScraper
from app.connectors.direct_scrapers.porn4days import Porn4DaysScraper
from app.connectors.direct_scrapers.porndish import PornDishScraper
from app.connectors.direct_scrapers.porntrex import PornTrexScraper
from app.connectors.direct_scrapers.porntrex_browse import PornTrexBrowseScraper
from app.connectors.direct_scrapers.xnxx_browse import XnxxBrowseScraper
from app.connectors.direct_scrapers.youporn_browse import YouPornBrowseScraper
from app.connectors.direct_scrapers.siska import SiskaScraper
from app.connectors.direct_scrapers.sxyland import SxyLandScraper
from app.connectors.direct_scrapers.sxyprn import SxyPrnScraper
from app.connectors.direct_scrapers.galaxyporn import GalaxyPornScraper
from app.connectors.direct_scrapers.pornbusy import PornBusyScraper
from app.connectors.direct_scrapers.watchporn import WatchPornScraper
from app.connectors.direct_scrapers.youperv import YoupervScraper
from app.connectors.direct_scrapers.xhamster import XHamsterScraper
from app.connectors.direct_scrapers.xmoviesforyou import XMoviesForYouScraper
from app.connectors.direct_scrapers.xnxx import XnxxScraper
from app.connectors.direct_scrapers.xvideos import XVideosScraper
from app.connectors.direct_scrapers.xxxfreewatch import XxxFreeWatchScraper # noqa: F401 — kept for backref; delisted
from app.connectors.direct_scrapers.youporn import YouPornScraper
ALL_DIRECT_SCRAPERS: list[type[BaseDirectTubeScraper]] = [
# Existing 4 (verified, in production)
HQPornerScraper,
# HDPorn92Scraper — wyłączony 2026-05-18. Scene pages to SEO shell: ZERO player iframe
# (tylko happyleafmotion ads), JS hijackuje wszystkie kliki → `go.rmishe.com/smartpop/...`
# popunder redirect. Mobile WebView page-as-hoster pokazuje ad redirect zamiast video.
# 33,598 playback_sources mass-marked dead, 27,374 solo-orphan scenes deleted.
SxyLandScraper,
# ZeroDayXXScraper (0dayxx) — USUNIĘTY CAŁKOWICIE 2026-06-22 (user request). Orphan
# factory (0.1% canonical), zastępujemy lepszymi źródłami. Dane/pliki/extractor skasowane.
# Mainstream (URL templates well-known)
# PornHub + RedTube — USUNIĘTE CAŁKOWICIE 2026-06-22 (user request). Disabled od
# 2026-05-12 (0.4% canonical match), zamrożone dane skasowane z DB, pliki scraperów
# i ekstraktory usunięte. Powód: skrócone amatorskie clipy, nigdy nie matchują studio.
XVideosScraper,
XnxxScraper,
XHamsterScraper,
YouPornScraper,
PornTrexScraper,
EpornerScraper,
# Aggregators (WordPress-like ?s= search; speculative — verify post-deploy)
# XMoviesForYouScraper — wyłączony 2026-05-12 (post audit fix). 100% scen serwuje
# streamtape (DEAD_HOSTER_RE — malware drive-by .reg) + opcjonalnie playmogo/mixdrop.
# Mixdrop zrebrandował na m1xdrop.bz, yt-dlp out-of-date, packer/JS extract = fail.
# Playmogo = DoodStream CAPTCHA. Porn-app sam olewa xmoviesforyou (brak handlera w
# jadx). 1,321 solo-orphan scen.
# WatchPornScraper — RE-ENABLE 2026-07-02, przeniesiony do ALL_BROWSE_SCRAPERS.
# Site przebudowany na KVS (DoodStream-CAPTCHA z 2026-05-12 zniknął); browse
# /latest-updates/ + flashvars get_file direct mp4 (extractor watchporn). Patrz watchporn.py.
# SiskaScraper — przeniesiony do ALL_BROWSE_SCRAPERS (browse-konwersja 2026-06-20,
# bo search siski zepsuty site-side — `?s=` ignoruje query). Patrz siska.py.
# Porn4DaysScraper — wyłączony 2026-05-12 (post audit fix). 100% scen na streamtape
# only (DEAD_HOSTER_RE blacklist - malware drive-by .reg downloads). SERVER1_URL =
# streamtape, brak SERVER2/SERVER3 backup. Porn-app sam olewa porn4days. 10,346
# solo-orphan scen.
# PornDishScraper — przeniesiony do ALL_BROWSE_SCRAPERS (browse-konwersja 2026-06-24,
# watchdog GOON-16: search `?s=` zamarzł 2026-05-07). WordPress → browse przez WP REST
# API (/wp-json/wp/v2/posts) jak perverzija: tytuł/data/thumb/studio(category)/tagi.
# XxxFreeWatchScraper — wyłączony 2026-05-18. 790 scen, 0% canonical match, 100% solo-orphan.
# Cloudflare 403 z VPS IP, mobile WebView teoretycznie działa ale 0/790 scen miało jakikolwiek
# match do TPDB/StashDB. Pure orphan factory. Solo scenes deleted, scraper disabled.
# LatestPornVideoScraper — przeniesiony do ALL_BROWSE_SCRAPERS (browse-konwersja 2026-06-22,
# user 1da0375e: search-driven nie brał feedu "latest" → stary zestaw w apce).
# LatestLeaksScraper — wyłączony 2026-05-12 (source quality report): 16,438 scen, 0.0%
# canonical match. Slug-concat tytuły, brak studio/duration/date signali. Solo orphany
# usunięte (~15k scen).
MyPornerLeakScraper,
# Added 2026-05-12 (theporndude survey): jeden z 14 free tubes na liście który
# zwraca consistent search results. KVS engine, slug-aware scene URLs. Mostly
# orphan ingest (auto-screenshots, no canonical phash match — sprawdzone), ale
# może łapać sceny popularnych performerów których jeszcze nie mamy w TPDB.
# PornHat (pornhatcom) + PornDitt (porndittcom) — USUNIĘTE CAŁKOWICIE 2026-06-22
# (user request). Orphan factories (0.2% / weak-signal canonical match), zastępujemy
# lepszymi źródłami. Dane/pliki scraperów/extractory skasowane.
# Special
SxyPrnScraper,
# PerverzijaScraper — przeniesiony do ALL_BROWSE_SCRAPERS (browse-konwersja 2026-06-22,
# user request). Search `?s=` → 429, homepage JS-renderowane; browse przez WP REST API
# (/wp-json/wp/v2/posts) daje tytuł/datę/thumb/studio(category)/tagi. Playback embed-iframe.
# FpoxxxScraper — przeniesiony do ALL_BROWSE_SCRAPERS (browse-konwersja 2026-06-22,
# user request). fpo.xxx to KVS, nie WordPress → search `?s=` zwracał 0; browse z
# `/new-<n>/` daje listing tile (tytuł/thumb/duration). Playback i tak phone-side (KVS).
]
# Browse-mode scrapers — iterują `latest-vids` listing zamiast search-by-performer.
# Phash thumbnail fingerprint (waga 0.40 w composite scoring) auto-mergeuje do
# canonical (TPDB/StashDB) gdy tube hot-linkuje studio thumbnail. Schedulowane
# raz dziennie, pages 1-5. Patrz `_browse_base.BaseBrowseScraper` +
# `app/scheduler/browse_latest.py`.
#
# **Pilot results (2026-05-12):**
# - ShyfapScraper: 0/23 match (0%) — robi własne thumbnails ≠ canonical
# (phash Hamming 12-16). Plus rebranduje tytuły. **Wyłączony.**
# - FreshpornoScraper: 39/59 match (66%) — hot-linkuje studio thumbnaile
# (phash Hamming 0). Oryginalne tytuły + channels=studio 1:1. **Aktywny.**
from app.connectors.direct_scrapers.freshporno import FreshpornoScraper # noqa: E402
from app.connectors.direct_scrapers.porn00 import Porn00Scraper # noqa: E402
from app.connectors.direct_scrapers.porndoe import PornDoeScraper # noqa: E402
from app.connectors.direct_scrapers.pornxp import PornXPScraper # noqa: E402
from app.connectors.direct_scrapers.shyfap import ShyfapScraper # noqa: E402, F401
from app.connectors.direct_scrapers.yesporn import YesPornVipScraper # noqa: E402
from app.connectors.direct_scrapers.fullmovies import FullmoviesScraper # noqa: E402
from app.connectors.direct_scrapers.hdporngg import HDPornGGScraper # noqa: E402
from app.connectors.direct_scrapers.hqfap import HQFapScraper # noqa: E402
from app.connectors.direct_scrapers.hqporner_browse import HQPornerBrowseScraper # noqa: E402
from app.connectors.direct_scrapers.javflix import JavflixScraper # noqa: E402
from app.connectors.direct_scrapers.vjav import VjavScraper # noqa: E402
from app.connectors.direct_scrapers.supjav import SupjavScraper # noqa: E402
from app.connectors.direct_scrapers.neporn import NepornScraper # noqa: E402
from app.connectors.direct_scrapers.superporn import SuperpornScraper # noqa: E402
from app.connectors.direct_scrapers.eporner_api import EpornerApiScraper # noqa: E402
from app.connectors.direct_scrapers.xvideos_browse import XVideosBrowseScraper # noqa: E402
ALL_BROWSE_SCRAPERS: list[type[BaseBrowseScraper]] = [
PerverzijaScraper,
PornDishScraper,
# WatchPornScraper — re-enabled 2026-07-02 jako browse (site przebudowany na KVS,
# DoodStream-CAPTCHA zniknął). Browse /latest-updates/: og:title, JSON-LD duration/
# uploadDate, /models/ performerzy, /tags/ tagi, /categories/ studio. Playback KVS
# get_file direct mp4 (extractor watchporn, VPS-side, token nie IP-bound).
WatchPornScraper,
# YoupervScraper — dodany 2026-07-26 (ocena: orphan-risk LOW, najlepszy kandydat od
# hqporner). Ripy paysite, tytuły `Studio - Performer - Title` (71% próbki), performerzy
# z linków, duration + release_date ISO, ~60-70 scen/dzień. Playback: direct mp4 bez
# tokena (extractor youpervcom, VPS-side, CDN pilnuje tylko Referera).
YoupervScraper,
# GalaxyPornScraper — dodany 2026-07-26 (ocena 5/5). Ripy paysite, obsada w
# wydzielonym `<div id="video-actors">` (zero pollution), studio+data z prefiksu
# tytułu, paginacja zdrowa. Duration liczone z thumbnail.vtt playera (nie ma go
# w HTML, a NULL ukryłby sceny pod filtrem min_duration_sec). Playback: iframe
# seekplayer → istniejący silnik, HLS przez /proxy/hls.
GalaxyPornScraper,
# PornBusyScraper — dodany 2026-07-27 (ocena 4.5/5). Metadane w `itemprop` (duration/
# uploadDate/thumb/desc) + obsada w `<div id="video-actors">` bez pollution. Homepage
# pagination ZEPSUTA (str. 1/2/3 identyczne) → listing z sitemapy, newest-first.
# Brak pola studio. Playback: loadvid/seekplayer/zpi ≈ 69% katalogu.
PornBusyScraper,
# Browse równolegle do istniejącego search scrapera (wzorzec xvideos/eporner):
# search zostaje (pokrycie back-catalogu performerów), browse gwarantuje świeżość
# wprost z feedu (watchdog 48h zamiast 168h). Konwersja 2026-06-24 (user request).
PornTrexBrowseScraper,
MyPornerLeakBrowseScraper,
YouPornBrowseScraper,
XnxxBrowseScraper,
FreshpornoScraper,
FpoxxxScraper,
# LatestPornVideoScraper — browse od 2026-06-22 (user 1da0375e: search-driven
# nie brał feedu "latest"). Listing card: tytuł (z embedded "<Studio> YY MM DD"),
# thumb (studio+date w nazwie), category-* jako tag. Performerów listing nie ma
# czysto (brak `actors-*`) → puste, dorabia canonical-merge. Playback: luluvid
# iframe → extractor latestpornvideocom (_embed_iframe) → telefon resolwuje.
LatestPornVideoScraper,
# SiskaScraper — re-enabled 2026-06-20 jako browse (user fa4083a2). Search siski
# zepsuty site-side (`?s=` ignoruje query), więc latest-browse z `/page/<n>/`.
# Komplet metadanych z kafelka listingu (tytuł/duration/thumb/performer/studio/
# kategoria). Playback: playmogo + luluvid → telefon resolwuje phone-side.
SiskaScraper,
# PornXPScraper — pilot 2026-05-17 (20 scen): studio 100%, performer 95%,
# release_date 100%, duration 100%, stream_url 100%, phash 100%. Najlepsze
# sygnały spośród browse-mode scraperów. Stream direct mp4 (sv.porn-xp.com)
# 360/720 quality. Release year z `Released: <year>` na detail.
PornXPScraper,
# Porn00Scraper — pilot 2026-05-17 (16 scen): brak studio (0%) + brak release
# date (0%) ALE performer 100%, duration 100%, stream_url 100% (KVS video_alt_url
# 720p). Tytuł zachowuje studio prefix ("Studio Title - Scene Name") → title
# fuzzy match (rapidfuzz token_set_ratio) może załapać canonical. Monitorować.
Porn00Scraper,
# PornDoeScraper — dołączony 2026-05-21 (theporndude audit). Każda scena ma
# kompletny JSON-LD VideoObject: title + uploadDate + duration + named studio
# (producer/publisher) + named performers (actor[]) + thumbnail. Najbogatsze
# strukturalne metadane spośród browse scraperów — composite fuzzy match ma
# komplet sygnałów. Phash hit-rate niski (własne crop-thumbnaile), studio +
# performer + date + duration nadrabiają.
PornDoeScraper,
# YesPornVipScraper — dołączony 2026-05-27 (user audit). JSON-LD VideoObject
# + `<meta property="video:duration|release_date|tag">` per scena (Goon ma
# duration w sekundach gotowe + ISO 8601 release_date z timezone). Studio +
# performerzy z `btn gold` linków (`/channels/<slug>/` + `/models/<slug>/`).
# 941k organic monthly (SE Ranking, comparable z porndoe 731k / porntrex 790k).
# Scraper-of-paysites (DogFart / HardX / TeamSkeet / Vixen) — wysokie expected
# canonical match dla studio scenes. Korekta: theporndude scorecard rank 26
# ('yespornvip.com', score -0.5, auth wall) dotyczył **innej domeny** — pdude.link
# redirect do porndudecams affiliate. Prawdziwa kanoniczna domena to TLD `.vip`.
YesPornVipScraper,
# FullmoviesScraper + HDPornGGScraper — dołączone 2026-06-01. KVS engine (sponsor_groups
# stack, `/videos/<slug>/` + `/latest-updates/`). Studio teraz z PREFIKSU tytułu
# ("Studio - Scene") — sidebar `/networks/` listował WSZYSTKIE sieci, więc pierwszy match
# zawsze Brazzers (mis-attribution, dlatego nigdy nie były włączone). Niosą paysite studio
# content (TeamSkeet/Dad Crush/Brazzers/...) z title+performer+duration → composite fuzzy.
# Nawet bez canonical match: grywalny content z inferred tagami (mission: daily tagged ingest).
FullmoviesScraper,
HDPornGGScraper,
# EpornerApiScraper — dołączony 2026-06-03 (Faza 2b alternatywa). eporner detail to
# JS-heavy KVS bez SSR metadanych (jak porntrex/hqporner — odrzucone), ALE eporner ma
# publiczne JSON API (api/v2/video/search): 1 call = 100 filmów z title+length_sec+
# keywords+added+thumb. ~100k filmów, deep-crawl przez crawl_page() (API, bez detail-fetch).
EpornerApiScraper,
# XVideosBrowseScraper — dołączony 2026-06-03. SSR JSON-LD (duration/title/uploadDate)
# + page-parse /models/ (performerzy) + /tags/. Sample: median ~10.5min, 93% ≥3min.
# Mega-katalog ~13M → deep_crawl._PAGE_CAP["xvideoscom"]=1800 (~50k najnowszych), nie
# full-crawl. (youporn pominięty — JSON-LD bez actor/keywords, scene-perf/tagi = nav A-Z.)
XVideosBrowseScraper,
# HQFapScraper — PlayTube CMS (re-uploader pornhd.pet). Usunięty 2026-06-25 gdy CAŁA
# biblioteka CDN serwowała `/upload/videos/video_down.mp4` stub; PRZYWRÓCONY 2026-07-07
# bo strona wróciła na CDN vstor.top z realnymi plikami (portable cross-IP), user request.
HQFapScraper,
# HQPornerBrowseScraper — dołączony 2026-07-26. hqporner był TYLKO w
# ALL_DIRECT_SCRAPERS (search po performerze), więc świeże sceny wchodziły wyłącznie
# przy trafieniu na znanego performera → 0 nowych scen w 7 dni (user-report). Browse
# (listing / + ?p=N) daje ingest niezależny od stanu performerów. external_id ten sam
# co w search (`hqpornercom:<url>`), więc obie ścieżki trafiają w tę samą scenę.
HQPornerBrowseScraper,
# FourK69Scraper — USUNIĘTY 2026-06-25 (ten sam stub), NIE sprawdzany ponownie.
# JavflixScraper — JAV vertical (osobna sekcja). origin tube:javflix jest w
# JAV_ORIGINS → list_scenes wyklucza je z głównego feedu (tylko zakładka JAV).
JavflixScraper,
# VjavScraper — JAV vertical (osobna sekcja, origin tube:vjav w JAV_ORIGINS).
# TXXX network: sitemap id-walk (newest=max id) + JSON metadata API. Stream
# videofile.php → get_file HLS (portable, /proxy/hls passthrough). RE 2026-07-10.
VjavScraper,
# SupjavScraper — JAV vertical (osobna sekcja, origin tube:supjav w JAV_ORIGINS).
# CF-blokuje VPS → browse homepage przez Bright Data proxy. Embed-aggregator:
# data-link reverse-hex → lk1.supremejav.com → hoster (extractor supjav). RE 2026-07-10.
SupjavScraper,
# NepornScraper — dołączony 2026-06-10 (user request). KVS engine (jak freshporno/
# porn00), /latest-updates/N/. JSON-LD (title+desc+uploadDate+thumb) + video:duration
# meta + /models/ performerzy + /categories/ tagi. Brak studio (tytuł bywa
# "- HardX Update - ..." — fuzzy match po tytule). Resolve server-side _kvs,
# finalny remote_control.php portable cross-IP.
NepornScraper,
# SuperpornScraper — dołączony 2026-06-10 (user request). superporn blokuje VPS IP
# twardym CF 403 (każda impersonacja TLS), więc ingest HTML idzie przez Bright Data
# ISP proxy (BRIGHTDATA_PROXY_URL, ryczałt nie per-GB). Pierwszy scraper z proxy — `_proxy` w
# _browse_base. JSON-LD (title+desc+uploadDate+thumb+duration) + chipy pornstar/
# kategorie. Playback IP-bound → WebView (extractor superporncom → _vps_blocked_fallback).
# Bez proxy: scraper no-op (pusty iterator).
SuperpornScraper,
# porntrex/hqporner/youporn — NIE: KVS/JS bez SSR duration → niewidoczne orphany (2026-06-03).
# ShyfapScraper — wyłączony 2026-05-12 (pilot fail, 0% match — orphan factory).
]
__all__ = [
"BaseDirectTubeScraper",
"BaseBrowseScraper",
"ALL_DIRECT_SCRAPERS",
"ALL_BROWSE_SCRAPERS",
]