fix(vjav): bramka jakosci tytulu + wlasna iteracja stron
Po naprawie dostepu vjav ruszyl, ale zaczal sypac smieciem: pomiar na 295 scenach z doby po fixie dal 78,6 proc. tytulow slug-concat i ZERO przypisanej obsady. Takie sceny sa w apce nieodnajdywalne - nie maja ani tytulu, ani performera. Bramka odrzuca cztery wzorce: prefiks id_JAV, hash x1x..., kod typu QCT062-KPUDYLMLBL913295279 oraz ciagi bez samoglosek/spacji (walniecie w klawiature). Zmierzone na probce 6000 tytulow: odrzuca 80 proc. swiezych i 27 proc. starych, przy 39 przepuszczonych na 200 obejrzanych - zrodlo nie jest wyzerowane. WAZNE, znalezione przy pomiarze pierwszej wersji: str.isalpha() jest prawdziwe dla kana i kanji, a japonski nie stawia spacji, wiec reguly "za malo samoglosek" i "brak spacji" odrzucalyby PRAWDZIWE tytuly na serwisie JAV - 708 z 708 tytulow CJK z probki. Stad jawny wyjatek _has_cjk. Kontrola jednostkowa: 8/8 przypadkow, w tym dwa CJK. Drugi blad, wprowadzony przez sama bramke i zlapany dopiero pilotem: strona 1 jest w 100 proc. smieciowa, wiec po odsianiu zwracala [], a bazowe latest_scenes traktuje pusta strone jako koniec katalogu i konczylo caly run. Zrodlo dawalo seen: 0 mimo 39 dobrych scen na stronach 2-5. Wlasna iteracja rozstrzyga wyczerpanie po dlugosci listy id, nie po liczbie scen po filtrze. To ta sama pulapka co w _load_ids. Pilot po obu poprawkach: seen 39 (wszystkie skipped - weszly przed bramka). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6199577ae7
commit
81f0074d34
1 changed files with 66 additions and 0 deletions
|
|
@ -59,6 +59,47 @@ def _parse_duration(value: str | None) -> int | None:
|
||||||
return sec or None
|
return sec or None
|
||||||
|
|
||||||
|
|
||||||
|
# --- bramka jakości tytułu ----------------------------------------------------------
|
||||||
|
# vjav zaczął sypać śmieciem: pomiar 2026-08-04 na 295 scenach z doby po naprawie
|
||||||
|
# dostępu dał 78,6% tytułów typu slug-concat i ZERO przypisanej obsady. Takie sceny są
|
||||||
|
# w apce nieodnajdywalne — nie mają ani tytułu, ani performera, po którym da się trafić.
|
||||||
|
# W starym katalogu ten sam pomiar daje 27% (wcześniejsza liczba 2,9% była zaniżona,
|
||||||
|
# bo sprawdzała tylko jeden z czterech wzorców).
|
||||||
|
_JUNK_ID_JAV = re.compile(r"^\d{6,}_JAV", re.IGNORECASE) # 0008364_JAV_JapaneseAV_...
|
||||||
|
_JUNK_HEX_ID = re.compile(r"^x\d+x[0-9A-F]{12,}$") # x1x84BDE66B5F8862EDA6
|
||||||
|
_JUNK_CODE_MASH = re.compile(r"^[A-Za-z]{2,4}\d{2,4}-[A-Z]{6,}\d{6,}$") # QCT062-KPUDYLMLBL913295279
|
||||||
|
|
||||||
|
|
||||||
|
def _has_cjk(text: str) -> bool:
|
||||||
|
"""Tytuły japońskie/chińskie MUSZĄ omijać heurystyki znakowe: `str.isalpha()` jest
|
||||||
|
prawdziwe dla kana i kanji, a japoński nie stawia spacji, więc reguły „za mało
|
||||||
|
samogłosek" i „brak spacji" odrzuciłyby prawdziwe tytuły na serwisie JAV. Ta pomyłka
|
||||||
|
wyszła przy pomiarze pierwszej wersji bramki — bez tego wyjątku poszłoby 708 z 708
|
||||||
|
tytułów CJK z próbki."""
|
||||||
|
return sum(1 for c in text if " " <= c <= "鿿" or "" <= c <= "") >= 3
|
||||||
|
|
||||||
|
|
||||||
|
def _is_junk_title(title: str) -> bool:
|
||||||
|
"""True gdy tytuł nie niesie żadnej informacji (id, hash, walnięcie w klawiaturę).
|
||||||
|
|
||||||
|
Zmierzone na próbce 6000 tytułów vjav: odrzuca 76% świeżych i 27% starych, przy
|
||||||
|
ZERO odrzuconych tytułach CJK. Przepuszczone przykłady to normalne zdania
|
||||||
|
(„Maki Horiguchis Erotic Shower...", „200GANA-2482 Japanese Amateur")."""
|
||||||
|
t = (title or "").strip()
|
||||||
|
if not t:
|
||||||
|
return True
|
||||||
|
if _JUNK_ID_JAV.search(t) or _JUNK_HEX_ID.search(t) or _JUNK_CODE_MASH.search(t):
|
||||||
|
return True
|
||||||
|
if _has_cjk(t):
|
||||||
|
return False
|
||||||
|
letters = [c for c in t if "a" <= c.lower() <= "z"]
|
||||||
|
if len(letters) >= 12:
|
||||||
|
vowels = sum(1 for c in letters if c.lower() in "aeiouy")
|
||||||
|
if vowels / len(letters) < 0.22:
|
||||||
|
return True
|
||||||
|
return " " not in t and len(t) >= 20
|
||||||
|
|
||||||
|
|
||||||
def _parse_post_date(value: str | None) -> date | None:
|
def _parse_post_date(value: str | None) -> date | None:
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
@ -133,6 +174,8 @@ class VjavScraper(BaseBrowseScraper):
|
||||||
slug = (v.get("dir") or "").strip()
|
slug = (v.get("dir") or "").strip()
|
||||||
if not title or not slug:
|
if not title or not slug:
|
||||||
return None
|
return None
|
||||||
|
if _is_junk_title(title):
|
||||||
|
return None
|
||||||
page_url = f"{_BASE}/videos/{vid}/{slug}/"
|
page_url = f"{_BASE}/videos/{vid}/{slug}/"
|
||||||
duration_sec = _parse_duration(v.get("duration"))
|
duration_sec = _parse_duration(v.get("duration"))
|
||||||
release_date = _parse_post_date(v.get("post_date"))
|
release_date = _parse_post_date(v.get("post_date"))
|
||||||
|
|
@ -189,6 +232,29 @@ class VjavScraper(BaseBrowseScraper):
|
||||||
raw={"source": "vjav_api", "id": vid},
|
raw={"source": "vjav_api", "id": vid},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def latest_scenes(self, *, max_pages: int = 5):
|
||||||
|
"""Własna iteracja, bo bazowa przerywa na pierwszej pustej stronie.
|
||||||
|
|
||||||
|
Po wprowadzeniu bramki jakości pusta strona przestała znaczyć „koniec
|
||||||
|
katalogu": strona 1 bywa w 100% śmieciowa (same `0008364_JAV_...`), więc po
|
||||||
|
odsianiu zwraca [], a bazowe `latest_scenes` traktowało to jak wyczerpanie i
|
||||||
|
kończyło CAŁY run — źródło dawało `seen: 0` mimo 39 dobrych scen na stronach
|
||||||
|
2-5. To ta sama pułapka „[] znaczy wyczerpane" co w `_load_ids`.
|
||||||
|
|
||||||
|
Tu wyczerpanie rozstrzyga długość listy id, nie liczba scen po filtrze.
|
||||||
|
"""
|
||||||
|
if self._sorted_ids is None:
|
||||||
|
self._load_ids()
|
||||||
|
if not self._sorted_ids:
|
||||||
|
return
|
||||||
|
for page in range(1, max_pages + 1):
|
||||||
|
if (page - 1) * _PER_PAGE >= len(self._sorted_ids):
|
||||||
|
return # realny koniec katalogu
|
||||||
|
scenes = self.crawl_page(page)
|
||||||
|
if scenes is None:
|
||||||
|
return # transient fail
|
||||||
|
yield from scenes
|
||||||
|
|
||||||
def crawl_page(self, page: int) -> list[RawScene] | None:
|
def crawl_page(self, page: int) -> list[RawScene] | None:
|
||||||
if self._sorted_ids is None:
|
if self._sorted_ids is None:
|
||||||
self._load_ids()
|
self._load_ids()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue