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
|
||||
|
||||
|
||||
# --- 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:
|
||||
if not value:
|
||||
return None
|
||||
|
|
@ -133,6 +174,8 @@ class VjavScraper(BaseBrowseScraper):
|
|||
slug = (v.get("dir") or "").strip()
|
||||
if not title or not slug:
|
||||
return None
|
||||
if _is_junk_title(title):
|
||||
return None
|
||||
page_url = f"{_BASE}/videos/{vid}/{slug}/"
|
||||
duration_sec = _parse_duration(v.get("duration"))
|
||||
release_date = _parse_post_date(v.get("post_date"))
|
||||
|
|
@ -189,6 +232,29 @@ class VjavScraper(BaseBrowseScraper):
|
|||
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:
|
||||
if self._sorted_ids is None:
|
||||
self._load_ids()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue