fix(siska): przeniesienie na siska.tv po wygasnieciu siska.video

siska.video od 2026-09-22 nie istnieje (NXDOMAIN), ten sam serwis dziala pod
siska.tv z tymi samymi videoID i sciezkami miniatur. Scraper bierze kafelki z obu
domen i z www, zapisuje jeden kanoniczny adres. Lista najnowszych jest pod
best_xvideos.php?page=N, bo /page/N/ na siska.tv zawsze oddaje strone 1.

Na produkcji adresy stron, miniatur i external_id przepisane na siska.tv
(16 007 zrodel, 16 265 powiazan scen); 103 duplikaty www.siska.video oznaczone
jako martwe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-09-22 13:18:22 +02:00
parent ed7c1cd3ed
commit cb9e4ab035
2 changed files with 22 additions and 9 deletions

View file

@ -110,7 +110,7 @@ class SourceListOut(BaseModel):
# tubey mają nietypowe TLDs / brakujące kropki w sitetag. # tubey mają nietypowe TLDs / brakujące kropki w sitetag.
_DISPLAY_OVERRIDES: dict[str, str] = { _DISPLAY_OVERRIDES: dict[str, str] = {
"fpoxxx": "fpo.xxx", "fpoxxx": "fpo.xxx",
"siskavideo": "siska.video", "siskavideo": "siska.tv",
"porn4dayspw": "porn4days.pw", "porn4dayspw": "porn4days.pw",
"porn00org": "porn00.org", "porn00org": "porn00.org",
"freshpornoorg": "freshporno.org", "freshpornoorg": "freshporno.org",

View file

@ -1,8 +1,8 @@
"""siska.video, latest-vids browse scraper. """siska.tv (do 2026-09-22 siska.video), latest-vids browse scraper.
Historia: dawniej performer-driven search scraper (`?s=<q>`), ale siska zepsuła Historia: dawniej performer-driven search scraper (`?s=<q>`), ale siska zepsuła
wyszukiwarkę (zwraca latest niezależnie od query). Przerobione na BROWSE (latest wyszukiwarkę (zwraca latest niezależnie od query). Przerobione na BROWSE (latest
chronologicznie z `/page/<n>/`), re-enabled 2026-06-20 (user fa4083a2). chronologicznie z listingu, patrz `_listing_url`), re-enabled 2026-06-20 (user fa4083a2).
Cały blok kafelka listingu ma komplet metadanych (zero detail-fetchy): Cały blok kafelka listingu ma komplet metadanych (zero detail-fetchy):
<a title='<Tytuł>' href='https://siska.video/video.php?videoID=<n>'> <a title='<Tytuł>' href='https://siska.video/video.php?videoID=<n>'>
@ -36,11 +36,18 @@ from app.extractors import browser_get
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
_BASE = "https://siska.video" # Domena siska.video wygasla 2026-09-22 (NXDOMAIN), ten sam serwis dziala dalej pod
# siska.tv: te same numery videoID i sciezki miniatur. Adresy w bazie przepisane na
# siska.tv tego samego dnia. Kafelki przyjmujemy z obu domen i z www, ale zapisujemy
# zawsze jeden kanoniczny adres (_canonical), bo external_id sceny i unikalny klucz
# zrodla to sam adres; www.siska.video i siska.video dawaly 103 zdublowane zrodla.
_BASE = "https://siska.tv"
_HOST_RE = re.compile(r"^https://(?:www\.)?siska\.(?:tv|video)/", re.IGNORECASE)
# Kafelek: <a title='..' href='..videoID=N'>. Reszta pól w oknie po tym matchu. # Kafelek: <a title='..' href='..videoID=N'>. Reszta pól w oknie po tym matchu.
_A_RE = re.compile( _A_RE = re.compile(
r"<a\s+title='(?P<title>[^']*)'\s+href='(?P<url>https://siska\.video/video\.php\?videoID=\d+)'", r"<a\s+title='(?P<title>[^']*)'\s+"
r"href='(?P<url>https://(?:www\.)?siska\.(?:tv|video)/video\.php\?videoID=\d+)'",
re.IGNORECASE, re.IGNORECASE,
) )
_DUR_RE = re.compile(r"th_video_duration'>\s*([\d]{1,2}(?:\s*:\s*[\d]{2}){1,2})\s*<") _DUR_RE = re.compile(r"th_video_duration'>\s*([\d]{1,2}(?:\s*:\s*[\d]{2}){1,2})\s*<")
@ -49,6 +56,10 @@ _ALT_RE = re.compile(r"alt='([^']*)'")
_CAT_RE = re.compile(r"/category/([^/]+)/", re.IGNORECASE) _CAT_RE = re.compile(r"/category/([^/]+)/", re.IGNORECASE)
def _canonical(url: str) -> str:
return _HOST_RE.sub(_BASE + "/", url)
def _parse_duration(text: str | None) -> int | None: def _parse_duration(text: str | None) -> int | None:
"""`40 : 27` → 2427 (MM:SS); `1 : 05 : 30` → H:MM:SS. None gdy brak.""" """`40 : 27` → 2427 (MM:SS); `1 : 05 : 30` → H:MM:SS. None gdy brak."""
if not text: if not text:
@ -73,11 +84,13 @@ class SiskaScraper(BaseBrowseScraper):
sitetag = "siskavideo" sitetag = "siskavideo"
def _listing_url(self, page: int) -> str: def _listing_url(self, page: int) -> str:
return f"{_BASE}/page/{page}/" # Na siska.tv `/page/<n>/` oddaje zawsze strone 1. Najnowsze po kolei (18 na
# strone, videoID malejaco) sa pod `best_xvideos.php`, mimo nazwy.
return f"{_BASE}/best_xvideos.php?page={page}"
# crawl_page nadpisany → poniższe abstrakcje nieużywane, ale wymagane do instancji. # crawl_page nadpisany → poniższe abstrakcje nieużywane, ale wymagane do instancji.
def _extract_scene_urls(self, listing_html: str) -> list[str]: def _extract_scene_urls(self, listing_html: str) -> list[str]:
return [m.group("url") for m in _A_RE.finditer(listing_html)] return [_canonical(m.group("url")) for m in _A_RE.finditer(listing_html)]
def _parse_detail(self, scene_url: str, detail_html: str) -> RawScene | None: def _parse_detail(self, scene_url: str, detail_html: str) -> RawScene | None:
return None return None
@ -94,7 +107,7 @@ class SiskaScraper(BaseBrowseScraper):
out: list[RawScene] = [] out: list[RawScene] = []
seen: set[str] = set() seen: set[str] = set()
for m in _A_RE.finditer(html): for m in _A_RE.finditer(html):
scene_url = m.group("url") scene_url = _canonical(m.group("url"))
if scene_url in seen: if scene_url in seen:
continue continue
seen.add(scene_url) seen.add(scene_url)
@ -106,7 +119,7 @@ class SiskaScraper(BaseBrowseScraper):
dm = _DUR_RE.search(window) dm = _DUR_RE.search(window)
duration_sec = _parse_duration(dm.group(1) if dm else None) duration_sec = _parse_duration(dm.group(1) if dm else None)
tm = _THUMB_RE.search(window) tm = _THUMB_RE.search(window)
thumbnail_url = tm.group(1) if tm else None thumbnail_url = _canonical(tm.group(1)) if tm else None
# alt='Performer - Tytuł - Studio' → performer (pierwszy), studio (ostatni). # alt='Performer - Tytuł - Studio' → performer (pierwszy), studio (ostatni).
performers: list[RawPerformer] = [] performers: list[RawPerformer] = []