fix(movies): obsada i studio z motywu "vid", pandamovies z powrotem
Some checks are pending
Backend tests / test (push) Waiting to run
Some checks are pending
Backend tests / test (push) Waiting to run
Mangoporn, streamporn.li i pandamovies przeszly w pazdzierniku na nowy motyw: obsada tylko w JSON-LD, studio, rok i gatunki w chipach bez rel="tag" (czasem bez koncowego ukosnika). Parser po cichu przestal czytac obsade i studio, a mangoporn dostawal jedynie tag "Porn Movies". Pandamovies dziala znowu pod pandamovies.org, wiec wraca do ingestu. Skrypty: pelne zaciagniecie zrodla po przerwie i uzupelnienie metadanych filmow z pazdziernika. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bf45339c97
commit
37f8eb1762
4 changed files with 156 additions and 14 deletions
|
|
@ -35,6 +35,7 @@ def get_movie_connectors() -> list[tuple[str, type]]:
|
|||
"""
|
||||
from app.connectors.dooplay import (
|
||||
MangopornConnector,
|
||||
PandamoviesConnector,
|
||||
StreampornVipConnector,
|
||||
)
|
||||
from app.connectors.freeomovie import FreeoMovieConnector
|
||||
|
|
@ -55,17 +56,11 @@ def get_movie_connectors() -> list[tuple[str, type]]:
|
|||
("paradisehill", ParadisehillConnector),
|
||||
("mangoporn", MangopornConnector),
|
||||
("streampornvip", StreampornVipConnector),
|
||||
# pandamovies WYLACZONY 2026-08-20: serwis padl, zostal im tylko zapamietany
|
||||
# fronton. Strona glowna oddaje 291 kB i wypisuje 56 filmow, ale KAZDA inna
|
||||
# sciezka to 404: listing /movies/ (ten, ktory czytal konektor), /movies/page/2/
|
||||
# linkowana przez ich wlasny szablon, /genre/* i /release-year/* z ich menu,
|
||||
# sitemapa, a przede wszystkim same strony filmow (0 na 6 dzialajacych).
|
||||
# To nie blokada na nasze IP: przez residential proxy wychodzi identyczne 404
|
||||
# (te same 796 bajtow). Konektor konczyl runy jako success z zerem rekordow,
|
||||
# co falszowalo obraz w monitoringu. Playback tego, co juz mamy, dziala dalej,
|
||||
# bo zrodla wskazuja na hostery (doply, luluvid, seekplayer), nie na ten serwis.
|
||||
# Klasa zostaje w dooplay.py dla backrefu origins `pandamovies:*`.
|
||||
# ("pandamovies", PandamoviesConnector),
|
||||
# pandamovies: wylaczony 2026-08-20 (serwis padl, kazda sciezka poza strona
|
||||
# glowna dawala 404), wlaczony z powrotem 2026-10-16 po przeprowadzce na
|
||||
# pandamovies.org: listing /movies/ 27 filmow na strone, 7 hosterow na film,
|
||||
# dziala wprost z VPS bez proxy. Ten sam motyw "vid" co mangoporn i streamporn.li.
|
||||
("pandamovies", PandamoviesConnector),
|
||||
# freeomovie.to (bestia theme, nie dooplay), mirror: świeże filmy DVD,
|
||||
# title-trigram attach do canonical + playback z TABS. Ocena 2026-07-02.
|
||||
("freeomovie", FreeoMovieConnector),
|
||||
|
|
|
|||
|
|
@ -191,9 +191,22 @@ _ST_DURATION_RE = re.compile(
|
|||
_ST_DATE_RE = re.compile(
|
||||
r'class="st st--date"[^>]*>.*?<time\s+datetime="(\d{4}-\d{2}-\d{2})', re.IGNORECASE | re.DOTALL
|
||||
)
|
||||
_YEAR_LOOSE_RE = re.compile(r'/(?:year|release-year)/(\d{4})/"', re.IGNORECASE)
|
||||
# pandamovies.org 2026-10: chip `/release-year/2026` bez koncowego ukosnika.
|
||||
_YEAR_LOOSE_RE = re.compile(r'/(?:year|release-year)/(\d{4})/?"', re.IGNORECASE)
|
||||
# Motyw "vid" (mangoporn, pandamovies, streamporn.li, 2026-10): studio w chipie bez
|
||||
# rel="tag", pod /studios/, /movies/studios/ albo /director/, czasem bez ukosnika.
|
||||
_STUDIO_CHIP_RE = re.compile(
|
||||
r'<a\s+class="chip"\s+href="https?://[^/]+/(?:movies/)?(?:studios?|director)/([a-z0-9-]+)/?"[^>]*>([^<]+)</a>',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# Ten sam motyw nie ma juz sekcji obsady z rel="tag"; obsada filmu jest w JSON-LD
|
||||
# (`"actor":[{"name":..., "url":".../actor/<slug>"}]`). Linki do aktorow w HTML bywaja
|
||||
# tez w widzetach bocznych, wiec bierzemy JSON-LD, ktory opisuje tylko ten film.
|
||||
_JSONLD_ACTORS_RE = re.compile(r'"actor"\s*:\s*(\[[^\]]*\])')
|
||||
# mangoporn ma gatunki pod /movies/genre/, pandamovies bez koncowego ukosnika, a chip
|
||||
# kategorii /genres/porn-movies/ to nie gatunek (byl jedynym tagiem filmow mangoporn).
|
||||
_GENRE_CHIP_RE = re.compile(
|
||||
r'<a\s+class="chip"\s+href="https?://[^/]+/genres?/([a-z0-9-]+)/"[^>]*>([^<]+)</a>',
|
||||
r'<a\s+class="chip"\s+href="https?://[^/]+/(?:movies/)?genres?/(?!porn-movies/?")([a-z0-9-]+)/?"[^>]*>([^<]+)</a>',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_DATA_SERVERS_RE = re.compile(r'data-servers="([^"]*)"', re.IGNORECASE)
|
||||
|
|
@ -348,6 +361,30 @@ class DooplayConnector(BaseMovieConnector):
|
|||
)
|
||||
|
||||
|
||||
def _jsonld_performers(html: str, source_name: str) -> list[RawPerformer]:
|
||||
"""Obsada z JSON-LD filmu (motyw "vid" 2026-10). Id jak dawniej: slug z adresu aktora,
|
||||
wiec te same osoby nie dubluja sie wzgledem filmow sprzed zmiany motywu."""
|
||||
m = _JSONLD_ACTORS_RE.search(html)
|
||||
if not m:
|
||||
return []
|
||||
try:
|
||||
people = json.loads(m.group(1))
|
||||
except ValueError:
|
||||
return []
|
||||
out: list[RawPerformer] = []
|
||||
seen: set[str] = set()
|
||||
for p in people:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
name = _decode_html((p.get("name") or "").strip())
|
||||
slug = (p.get("url") or "").rstrip("/").rsplit("/", 1)[-1].lower()
|
||||
if not name or not slug or slug in seen:
|
||||
continue
|
||||
seen.add(slug)
|
||||
out.append(RawPerformer(external_id=f"{source_name}:{slug}", name=name))
|
||||
return out
|
||||
|
||||
|
||||
def _parse_dooplay_detail(
|
||||
*, slug: str, html: str, source_name: str, base_url: str, page_url: str | None = None
|
||||
) -> RawMovie | None:
|
||||
|
|
@ -381,7 +418,7 @@ def _parse_dooplay_detail(
|
|||
release_year = int(m_year.group(1)) if m_year else vid_year
|
||||
|
||||
studio: RawStudio | None = None
|
||||
m_studio = _STUDIO_RE.search(html)
|
||||
m_studio = _STUDIO_RE.search(html) or _STUDIO_CHIP_RE.search(html)
|
||||
if m_studio:
|
||||
studio_slug = m_studio.group(1)
|
||||
studio_name = _decode_html(m_studio.group(2).strip())
|
||||
|
|
@ -468,6 +505,8 @@ def _parse_dooplay_detail(
|
|||
)
|
||||
for m in _PORNSTAR_RE.finditer(html)
|
||||
]
|
||||
if not performers:
|
||||
performers = _jsonld_performers(html, source_name)
|
||||
|
||||
tags = [
|
||||
RawTag(
|
||||
|
|
|
|||
88
scripts/backfill_dooplay_metadata.py
Normal file
88
scripts/backfill_dooplay_metadata.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"""Uzupelnia obsade, studio i tagi filmow z serwisow dooplay po zmianie motywu (2026-10).
|
||||
|
||||
Mangoporn, streamporn.li i pandamovies przeszly na motyw "vid": obsada tylko w JSON-LD,
|
||||
studio i rok w chipach bez rel="tag". Parser tego nie czytal, wiec filmy dodane od ok.
|
||||
5.10 przyszly bez obsady i czesto bez studia (cichy fail parsera, ingest konczyl sie
|
||||
"errors: 0"). Skrypt pobiera ich strony na nowo i puszcza przez resolve_movie, ktory przy
|
||||
znanym refie tylko uzupelnia puste pola i dopina obsade/tagi. Strony przez proxy, bo
|
||||
mangoporn z VPS bywa niedostepny (timeout, blad TLS).
|
||||
|
||||
python3 /srv/scripts/backfill_dooplay_metadata.py # dry-run: tylko liczy
|
||||
python3 /srv/scripts/backfill_dooplay_metadata.py --apply [--since 2026-10-01]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import logging
|
||||
|
||||
import app.connectors.dooplay as dp
|
||||
from app.config import get_settings
|
||||
|
||||
dp.browser_get = functools.partial(dp.browser_get, proxy=get_settings().brightdata_proxy_url)
|
||||
|
||||
from sqlalchemy import text # noqa: E402
|
||||
|
||||
from app.db import session_scope # noqa: E402
|
||||
from app.normalize.movies import normalize_movie # noqa: E402
|
||||
from app.resolve.movie_resolver import resolve_movie # noqa: E402
|
||||
|
||||
_CONNECTORS = {
|
||||
"mangoporn": dp.MangopornConnector,
|
||||
"streampornvip": dp.StreampornVipConnector,
|
||||
"pandamovies": dp.PandamoviesConnector,
|
||||
}
|
||||
|
||||
_REFS_SQL = """
|
||||
SELECT r.movie_id, r.external_id, r.url, so.id AS source_id, so.name
|
||||
FROM movie_external_refs r
|
||||
JOIN sources so ON so.id = r.source_id
|
||||
JOIN movies m ON m.id = r.movie_id
|
||||
WHERE so.name = ANY(:names) AND m.created_at >= :since AND r.url IS NOT NULL
|
||||
AND (m.studio_id IS NULL
|
||||
OR NOT EXISTS (SELECT 1 FROM movie_performers p WHERE p.movie_id = m.id))
|
||||
ORDER BY m.created_at DESC
|
||||
"""
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--apply", action="store_true")
|
||||
ap.add_argument("--since", default="2026-10-01")
|
||||
args = ap.parse_args()
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
with session_scope() as s:
|
||||
refs = s.execute(text(_REFS_SQL), {"names": list(_CONNECTORS), "since": args.since}).all()
|
||||
print(f"refow do uzupelnienia: {len(refs)}", flush=True)
|
||||
if not args.apply:
|
||||
return
|
||||
|
||||
conns = {name: cls() for name, cls in _CONNECTORS.items()}
|
||||
done = failed = with_cast = 0
|
||||
for n, ref in enumerate(refs, 1):
|
||||
try:
|
||||
raw = conns[ref.name]._fetch_detail(ref.url)
|
||||
except Exception as e: # noqa: BLE001
|
||||
failed += 1
|
||||
print(f" blad {ref.name} {ref.url}: {str(e)[:100]}", flush=True)
|
||||
continue
|
||||
if raw is None:
|
||||
failed += 1
|
||||
continue
|
||||
norm = normalize_movie(raw)
|
||||
if norm.external_id != ref.external_id:
|
||||
failed += 1
|
||||
print(f" inny id {ref.external_id} -> {norm.external_id}, pomijam", flush=True)
|
||||
continue
|
||||
with session_scope() as s:
|
||||
resolve_movie(s, norm=norm, source_id=ref.source_id)
|
||||
done += 1
|
||||
with_cast += bool(raw.performers)
|
||||
if n % 25 == 0:
|
||||
print(f"... {n}/{len(refs)}, uzupelnione {done}, z obsada {with_cast}, bledy {failed}", flush=True)
|
||||
print(f"uzupelnione: {done}, z obsada: {with_cast}, bledy: {failed}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
scripts/ingest_movies_backfill.py
Normal file
20
scripts/ingest_movies_backfill.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""Pelne zaciagniecie filmow z jednego zrodla (bez delty), np. po przerwie w ingescie.
|
||||
|
||||
Codzienny _job_movie_ingest idzie w trybie przyrostowym (dooplay: 3 strony listy), wiec
|
||||
po kilku tygodniach przestoju zrodla zaleglosci same sie nie nadrobia. Pelny tryb dooplay
|
||||
czyta do 50 stron. Uzycie (z pliku, w osobnym kontenerze):
|
||||
docker compose run -d --name X worker python3 /srv/scripts/ingest_movies_backfill.py pandamovies [limit]
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from app.connectors import get_movie_connectors
|
||||
from app.ingest import ingest_movies_from_connector
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
name = sys.argv[1]
|
||||
limit = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
||||
connectors = dict(get_movie_connectors())
|
||||
if name not in connectors:
|
||||
sys.exit(f"nie ma konektora filmow {name!r}, sa: {sorted(connectors)}")
|
||||
print(ingest_movies_from_connector(connectors[name](), limit=limit, use_delta=False))
|
||||
Loading…
Add table
Reference in a new issue