fix(vjav): 429 parsowany jako XML, zrodlo stalo 12 dni bez zadnego bledu

Audyt po fixie streamporn.vip. vjav (77 tys. zrodel) nie dodal nic od 2026-07-22 i
NIE zglaszal bledu - crawl_page oddawal 0 scen, licznik pokazywal errors: 0.

Przyczyna: vjav odcial IP VPS-a. Sprawdzone 2026-08-03 - sitemap, strona glowna i
/latest-updates/ daja 429 przy trzech probach z rzedu, a przez Bright Data 200 od
pierwszej. Kod tego nie widzial, bo browser_get NIE rzuca na 4xx, wiec 343-bajtowa
tresc bledu byla parsowana jako XML: zero sitemap, zero id.

Drugi blad, ktory to ukryl: przy niepowodzeniu _load_ids ustawialo _sorted_ids = [],
a crawl_page tlumaczy [] na "koniec katalogu" (poprawny stan), nie na awarie. Teraz
przy bledzie zostaje None, ktore crawl_page zwraca jako transient-fail.

Fix: proxy Bright Data na sitemap i API metadanych (ten sam host, ten sam 429), jawne
sprawdzanie statusu, [] tylko dla realnie pustego katalogu.

Po fixie crawl_page oddaje 120 scen zamiast 0. Nowej podazy to jeszcze nie dalo
(wszystkie 120 to pozycje sprzed blokady, skipped na niezmienionym hashu) - sitemap
vjav nie przesunal sie od czasu odciecia. Do obserwacji. Uwaga na jakosc: w tej
partii 58 proc. tytulow to slug-concat typu 0008363_JAV_..., przy 2,9 proc. w calym
katalogu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-08-03 14:36:25 +02:00
parent 3f8f38202a
commit 532c7e2bb4

View file

@ -31,6 +31,7 @@ from app.connectors.base import (
RawStudio,
RawTag,
)
from app.config import get_settings
from app.connectors.direct_scrapers._browse_base import BaseBrowseScraper
from app.extractors import browser_get
from app.normalize.text import slugify
@ -74,21 +75,45 @@ class VjavScraper(BaseBrowseScraper):
super().__init__(*args, **kwargs)
# Cache id-ów wideo (posortowane malejąco = newest first), per instancja/run.
self._sorted_ids: list[int] | None = None
# vjav odciął IP VPS-a: 2026-08-03 KAŻDA ścieżka (sitemap, strona główna,
# /latest-updates/) oddawała 429 przy trzech próbach z rzędu, a przez proxy
# 200 od pierwszej. Źródło stało 12 dni. Bright Data jest tu więc warunkiem
# działania, nie optymalizacją — bez klucza scraper się wyłącza.
self._proxy = get_settings().brightdata_proxy_url
if not self._proxy:
log.warning("vjav: BRAK BRIGHTDATA_PROXY_URL — vjav blokuje IP VPS-a (429)")
# ---- browse: sitemap id-walk ------------------------------------------------
def _load_ids(self) -> None:
"""`self._sorted_ids = []` znaczy „katalog pusty", a to woła `crawl_page` do
zwrócenia [], czyli koniec katalogu". Dlatego błąd pobrania NIE MOŻE tu
ustawiać []: przy 429 scraper raportował 0 scen i 0 błędów, więc przez 12 dni
wyglądał na zdrowy. Zostawiamy None = nie wiadomo", co crawl_page tłumaczy na
transient-fail. Status sprawdzamy jawnie, bo `browser_get` nie rzuca na 4xx
i to właśnie przez to treść błędu 429 była parsowana jako XML."""
try:
res = browser_get(_SITEMAP_INDEX, timeout=self._timeout)
res = browser_get(_SITEMAP_INDEX, timeout=self._timeout, proxy=self._proxy)
if getattr(res, "status_code", 200) >= 400:
log.warning("vjav: sitemap index HTTP %s — przerywam", res.status_code)
self._sorted_ids = None
return
idx = res.text if hasattr(res, "text") else res
except Exception as e:
log.warning("vjav: sitemap index fetch fail: %s", e)
self._sorted_ids = []
self._sorted_ids = None
return
maps = list(dict.fromkeys(_SITEMAP_RE.findall(idx)))
if not maps:
log.warning("vjav: sitemap index bez sitemap wideo (%d B) — przerywam", len(idx))
self._sorted_ids = None
return
ids: set[int] = set()
for sm in maps:
try:
res = browser_get(sm, timeout=self._timeout)
res = browser_get(sm, timeout=self._timeout, proxy=self._proxy)
if getattr(res, "status_code", 200) >= 400:
log.info("vjav: sitemap %s HTTP %s", sm, res.status_code)
continue
body = res.text if hasattr(res, "text") else res
except Exception as e:
log.info("vjav: sitemap %s fetch fail: %s", sm, e)
@ -177,7 +202,14 @@ class VjavScraper(BaseBrowseScraper):
out: list[RawScene] = []
for vid in chunk:
try:
res = browser_get(self._meta_url(vid), timeout=self._timeout, headers={"Referer": _BASE + "/"})
# Proxy także tutaj — API metadanych siedzi na tym samym hoście, więc
# obejmuje je ten sam 429 co sitemap.
res = browser_get(
self._meta_url(vid),
timeout=self._timeout,
headers={"Referer": _BASE + "/"},
proxy=self._proxy,
)
body = res.text if hasattr(res, "text") else res
v = json.loads(body).get("video")
except Exception as e: