fix(movies): nie lacz filmow rozniacych sie numerem serii (The Doctor = 9 filmow)
Zgloszenie 32100b94: "w pierwszym linku jest granny porn". Pod filmem "The Doctor"
siedzialo 55 zrodel z co najmniej 9 roznych filmow pandamovies (Doctor Adventures 14 i
21, Granny's Doctor Games, Pervy Doctor 9, The Doctor Is In 2, We Need a Doctor...).
Przyczyna: tytul porownywany przez `token_set_ratio`, ktore mierzy czesc wspolna, wiec
tytul bedacy podzbiorem drugiego dostaje pelne 100 ("the doctor" vs "the doctor is in
2", "fantasies" vs "dark fantasies 3"). Numerowane serie wychodza na 90-96, tez ponad
progiem auto-merge 0.85. Gdy zrodlo nie ma roku ani studia, o wszystkim decyduje sam
tytul i cala seria zapada sie w jeden wpis: "Her way to use Tinder 61" ma 102 strony,
"Women Seeking Women 56" 87. W bazie 1878 filmow ma >=2 rozne strony z pandamovies.
Dwie zasady oparte na numerze serii (ostatnia liczba w tytule, z pominieciem lat):
* oba tytuly maja numer i sie rozni -> twarde odrzucenie,
* numer ma tylko jedna strona -> tytul najwyzej 0.6, wiec sam nie wystarczy do
auto-merge, ale ze zgodnym studiem i rokiem trafia do recznego przegladu.
Podzbior bez numerow zostaje bez kary celowo: "cece" vs "watch cece adult" to prawdziwe
lustro z przedrostkiem SEO, dla niego jest token_set.
Sprawdzone na 11 parach z bazy, w tym kontrolnych (lustro SEO bez kary, rok nie liczy
sie jako numer). Dziala tylko w przod; rozdzielenie juz polaczonych to osobna robota.
Osobno `scripts/browse_catchup.py`: jednorazowe nadrobienie N stron jednego browse-
scrapera z pliku. Powstal, bo `docker compose run -d ... python3 -` ze skryptem na
stdin nigdy nie startuje: przy -d stdin nie jest domkniety i python czeka w
nieskonczonosc. Tak "chodzilo" nadrabianie porndoe przez 2 dni bez jednego zapisu.
This commit is contained in:
parent
ad59baea09
commit
ca80ae44b6
2 changed files with 118 additions and 2 deletions
|
|
@ -58,6 +58,55 @@ AUTO_THRESHOLD = 0.85
|
||||||
REVIEW_THRESHOLD = 0.65
|
REVIEW_THRESHOLD = 0.65
|
||||||
|
|
||||||
|
|
||||||
|
def _series_number(title_normalized: str) -> int | None:
|
||||||
|
"""Ostatnia samodzielna liczba w tytule, z pominieciem lat (1900-2099).
|
||||||
|
|
||||||
|
"her way to use tinder 61" -> 61, "the doctor is in 2" -> 2, "the doctor" -> None,
|
||||||
|
"cece 2019" -> None (rok to nie numer odcinka).
|
||||||
|
"""
|
||||||
|
liczby = [
|
||||||
|
int(t)
|
||||||
|
for t in title_normalized.split()
|
||||||
|
if t.isdigit() and not 1900 <= int(t) <= 2099
|
||||||
|
]
|
||||||
|
return liczby[-1] if liczby else None
|
||||||
|
|
||||||
|
|
||||||
|
# Tyle najwyzej dostaje tytul, gdy numer serii ma tylko jedna strona. Ponizej progu
|
||||||
|
# auto-merge, zeby sam tytul nie decydowal, ale dosc, zeby ze zgodnym studiem i rokiem
|
||||||
|
# trafic do recznego przegladu zamiast od razu odpadac.
|
||||||
|
_ONE_SIDED_NUMBER_CAP = 0.6
|
||||||
|
|
||||||
|
|
||||||
|
def _title_signal(cand_title: str, inc_title: str) -> tuple[float, bool, dict]:
|
||||||
|
"""(ocena tytulu 0..1, twarde odrzucenie, powody).
|
||||||
|
|
||||||
|
`token_set_ratio` mierzy czesc wspolna, wiec tytul bedacy podzbiorem drugiego dostaje
|
||||||
|
pelne 100: "the doctor" vs "the doctor is in 2", "fantasies" vs "dark fantasies 3".
|
||||||
|
Numerowane serie wychodza na 90-96%, tez ponad progiem auto-merge. Gdy zrodlo nie ma
|
||||||
|
roku ani studia (lustra dooplay czesto nie maja), o wszystkim decyduje sam tytul i
|
||||||
|
cala seria zapada sie w jeden wpis. Zgloszenie 32100b94 (2026-08-30): pod "The Doctor"
|
||||||
|
siedzialo 9 roznych filmow z pandamovies, w tym granny porn; w bazie 1878 filmow ma
|
||||||
|
co najmniej dwie rozne strony z pandamovies, a jedna to norma.
|
||||||
|
|
||||||
|
Zasady, obie oparte na numerze serii (ostatnia liczba w tytule, bez lat):
|
||||||
|
* oba tytuly maja numer i sie rozni -> twarde odrzucenie (to inny odcinek),
|
||||||
|
* numer ma tylko jedna strona -> tytul najwyzej 0.6 (inne wydanie, nie lustro).
|
||||||
|
Sam podzbior bez numerow zostaje bez kary celowo: "cece" vs "watch cece adult" to
|
||||||
|
prawdziwe lustro z przedrostkiem SEO i to wlasnie dla niego jest token_set.
|
||||||
|
"""
|
||||||
|
ocena = fuzz.token_set_ratio(cand_title, inc_title) / 100.0
|
||||||
|
powody: dict = {}
|
||||||
|
n_c, n_i = _series_number(cand_title), _series_number(inc_title)
|
||||||
|
if n_c is not None and n_i is not None and n_c != n_i:
|
||||||
|
powody["series_number_mismatch_reject"] = [n_c, n_i]
|
||||||
|
return ocena, True, powody
|
||||||
|
if (n_c is None) != (n_i is None):
|
||||||
|
powody["series_number_one_sided_cap"] = n_c if n_c is not None else n_i
|
||||||
|
return min(ocena, _ONE_SIDED_NUMBER_CAP), False, powody
|
||||||
|
return ocena, False, powody
|
||||||
|
|
||||||
|
|
||||||
def score_movie_candidate(
|
def score_movie_candidate(
|
||||||
session: Session,
|
session: Session,
|
||||||
*,
|
*,
|
||||||
|
|
@ -71,10 +120,12 @@ def score_movie_candidate(
|
||||||
breakdown = MovieScoreBreakdown(reasons={})
|
breakdown = MovieScoreBreakdown(reasons={})
|
||||||
|
|
||||||
# Title (always)
|
# Title (always)
|
||||||
|
series_reject = False
|
||||||
if candidate.title_normalized and norm_title_normalized:
|
if candidate.title_normalized and norm_title_normalized:
|
||||||
breakdown.title = fuzz.token_set_ratio(
|
breakdown.title, series_reject, powody = _title_signal(
|
||||||
candidate.title_normalized, norm_title_normalized
|
candidate.title_normalized, norm_title_normalized
|
||||||
) / 100.0
|
)
|
||||||
|
breakdown.reasons.update(powody)
|
||||||
|
|
||||||
# Year, exact lub ±1 = wysoka, ±2 = średnia
|
# Year, exact lub ±1 = wysoka, ±2 = średnia
|
||||||
if candidate.release_year is not None and norm_release_year is not None:
|
if candidate.release_year is not None and norm_release_year is not None:
|
||||||
|
|
@ -141,6 +192,11 @@ def score_movie_candidate(
|
||||||
breakdown.reasons["studio_year_mismatch_reject"] = True
|
breakdown.reasons["studio_year_mismatch_reject"] = True
|
||||||
score = 0.0
|
score = 0.0
|
||||||
|
|
||||||
|
# Hard reject: rozne numery serii. Bez tego "Her way to use Tinder 61" wchlaniala
|
||||||
|
# "... Tinder 60" (token_set 96%), az pod jednym tytulem siedzialy 102 strony.
|
||||||
|
if series_reject:
|
||||||
|
score = 0.0
|
||||||
|
|
||||||
breakdown.composite = max(0.0, min(1.0, score))
|
breakdown.composite = max(0.0, min(1.0, score))
|
||||||
breakdown.reasons["weights"] = norm_w
|
breakdown.reasons["weights"] = norm_w
|
||||||
breakdown.reasons["sub_scores"] = available
|
breakdown.reasons["sub_scores"] = available
|
||||||
|
|
|
||||||
60
scripts/browse_catchup.py
Normal file
60
scripts/browse_catchup.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
"""Jednorazowe nadrobienie zaleglosci jednego browse-scrapera (wiecej stron niz w cyklu).
|
||||||
|
|
||||||
|
Po co: gdy tub byl przez jakis czas nieosiagalny (porndoe: 10 dni HTTP 403 na IP
|
||||||
|
serwerowni, 2026-08-21..31), regularny cykl browse-latest bierze tylko 5 stron i nie
|
||||||
|
dosiegnie tego, co sie nazbieralo. Ten skrypt robi jeden przebieg na N stron dla
|
||||||
|
JEDNEGO sitetagu, tym samym kodem co scheduler (`_ingest_iter_into_run`), wiec wynik
|
||||||
|
laduje w tych samych tabelach i liczy sie tak samo.
|
||||||
|
|
||||||
|
PULAPKA, przez ktora ten plik powstal: `docker compose run -d ... python3 -u -` ze
|
||||||
|
skryptem podanym przez heredoc na stdin NIE DZIALA. Przy `-d` stdin nie jest domkniety,
|
||||||
|
wiec python czeka na wejscie w nieskonczonosc: kontener "Up 2 days", zero logu, zero
|
||||||
|
zapisow, a z zewnatrz wyglada jak dlugie zadanie. Odpalaj skrypty z pliku, po sciezce.
|
||||||
|
|
||||||
|
Uzycie (osobny kontener, restart workera go nie ubija):
|
||||||
|
docker compose run -d --rm --name goon-catchup-porndoe worker \
|
||||||
|
python3 -u /srv/scripts/browse_catchup.py --sitetag porndoecom --pages 14
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from app.connectors.base import SourceKind
|
||||||
|
from app.connectors.direct_scrapers import ALL_BROWSE_SCRAPERS
|
||||||
|
from app.scheduler.browse_latest import SCRAPER_SOURCE_NAME, _ingest_iter_into_run
|
||||||
|
|
||||||
|
log = logging.getLogger("browse-catchup")
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
p = argparse.ArgumentParser()
|
||||||
|
p.add_argument("--sitetag", required=True, help="np. porndoecom")
|
||||||
|
p.add_argument("--pages", type=int, default=10)
|
||||||
|
a = p.parse_args(argv)
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", stream=sys.stdout
|
||||||
|
)
|
||||||
|
for noisy in ("httpx", "httpcore", "urllib3", "curl_cffi"):
|
||||||
|
logging.getLogger(noisy).setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
klasa = next((c for c in ALL_BROWSE_SCRAPERS if c.sitetag == a.sitetag), None)
|
||||||
|
if klasa is None:
|
||||||
|
log.error("nie ma browse-scrapera o sitetag=%s", a.sitetag)
|
||||||
|
return 2
|
||||||
|
scraper = klasa()
|
||||||
|
log.info("catchup %s: %d stron", a.sitetag, a.pages)
|
||||||
|
c = _ingest_iter_into_run(
|
||||||
|
source_kind=SourceKind.scraper,
|
||||||
|
source_name=SCRAPER_SOURCE_NAME,
|
||||||
|
run_label=f"browse-catchup:{a.sitetag}",
|
||||||
|
iterator_factory=lambda: scraper.latest_scenes(max_pages=a.pages),
|
||||||
|
)
|
||||||
|
log.info("KONIEC catchup %s: %s", a.sitetag, c)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loading…
Add table
Reference in a new issue