fix(ingest,proxy): pusta strona to nie koniec katalogu + miniaturki blokowane po IP
naughtymachinima (zgloszenie 83eb300d, "tylko 148 scen"): deep-crawl uznal katalog za wyczerpany na stronie 8, bo trafil na pusta strone. Listing ma 1770 stron po 30 filmow, a ponowne wejscie na strone 9 oddalo 13 scen i ZERO bledow pobrania, czyli tamto zero bylo chwilowym dlawieniem. Pusta strona bywa tez calkiem legalna, bo bramka odrzuca klipy < 180 s, a to 2/3 tego katalogu. Konczymy wiec dopiero po trzech pustych stronach pod rzad. Kursor odblokowany, kontrolny run przeszedl strony 9-14: 49 scen, 7 nowych, bez oznaczenia exhausted. fullvideosporn (zgloszenie f4995691, "brak miniaturek"): adresy w bazie sa poprawne, ale tn.sextu.com odrzuca nasz adres serwerowni: 429 przy kazdej probie, takze po przerwie, a przez residential proxy 200 i 26 kB. Proxy obrazkow po odmowie (401/403/429) ponawia teraz raz przez to proxy. Obrazek ma kilkadziesiat kB i klient cachuje go dobe, wiec to nie jest proxowanie wideo. Sprawdzone: 6/6 miniaturek laduje sie (15-116 kB), wczesniej 0/6. Uwaga na przyszlosc: helper wladowal sie przy pierwszym podejsciu MIEDZY dekorator trasy a proxy_image, wiec @router.get udekorowal jego, a /proxy/img/ oddawalo URL proxy z loginem i haslem jako JSON. Naprawione, w docstringu ostrzezenie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fa7fb668b6
commit
e26c78df24
2 changed files with 63 additions and 3 deletions
|
|
@ -315,6 +315,19 @@ def sign_url(
|
||||||
return {"token": make_token(url, referer), "expires_in": TOKEN_TTL_SEC}
|
return {"token": make_token(url, referer), "expires_in": TOKEN_TTL_SEC}
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=1)
|
||||||
|
def _fallback_proxy_url() -> str | None:
|
||||||
|
"""Residential proxy, uzywany TYLKO gdy CDN odrzucil nasz adres IP.
|
||||||
|
|
||||||
|
NIE wstawiaj niczego miedzy dekorator trasy a `proxy_image`: przy pierwszym podejsciu
|
||||||
|
ta funkcja wladowala sie wlasnie tam i `@router.get` udekorowal JA, wiec /proxy/img/
|
||||||
|
oddawalo URL proxy razem z loginem i haslem jako JSON.
|
||||||
|
"""
|
||||||
|
from app.config import get_settings
|
||||||
|
|
||||||
|
return getattr(get_settings(), "brightdata_proxy_url", None) or None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/img/{token}/{_basename:path}")
|
@router.get("/img/{token}/{_basename:path}")
|
||||||
async def proxy_image(
|
async def proxy_image(
|
||||||
token: str,
|
token: str,
|
||||||
|
|
@ -365,6 +378,26 @@ async def proxy_image(
|
||||||
r = r2
|
r = r2
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.info("img proxy retry z wlasnym originem padl %s: %s", target, e)
|
log.info("img proxy retry z wlasnym originem padl %s: %s", target, e)
|
||||||
|
if r.status_code in (401, 403, 429):
|
||||||
|
# Ostatnia deska ratunku: CDN blokuje NASZ adres IP, nie zapytanie. Miniaturki
|
||||||
|
# fullvideosporn (tn.sextu.com) oddaja z serwerowni 429 przy kazdej probie
|
||||||
|
# (3/3, tez po przerwie), a przez residential proxy 200 i 26 kB. Stad puste
|
||||||
|
# kafelki mimo poprawnych adresow w bazie (zgloszenie f4995691). Obrazek ma
|
||||||
|
# kilkadziesiat kB i klient cachuje go na dobe, wiec to nie jest ten sam
|
||||||
|
# przypadek co proxowanie wideo. Sciagamy tylko wtedy, gdy host nas odrzucil.
|
||||||
|
proxy_url = _fallback_proxy_url()
|
||||||
|
if proxy_url:
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(
|
||||||
|
timeout=timeout, follow_redirects=True, proxy=proxy_url
|
||||||
|
) as pclient:
|
||||||
|
r3 = await pclient.get(target, headers=headers)
|
||||||
|
if r3.status_code < 400:
|
||||||
|
r = r3
|
||||||
|
else:
|
||||||
|
log.info("img proxy: proxy tez oddal %d dla %s", r3.status_code, target)
|
||||||
|
except Exception as e:
|
||||||
|
log.info("img proxy: proba przez proxy padla %s: %s", target, e)
|
||||||
if r.status_code >= 400:
|
if r.status_code >= 400:
|
||||||
# Upstream 4xx/5xx dla thumba, degraded zamiast raise (placeholder w mobile).
|
# Upstream 4xx/5xx dla thumba, degraded zamiast raise (placeholder w mobile).
|
||||||
# GOON-5 (Cloudflare 523) i GOON-D, bezsensowny noise w Sentry, lepiej
|
# GOON-5 (Cloudflare 523) i GOON-D, bezsensowny noise w Sentry, lepiej
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,12 @@ def _pick_target(state: dict, targets: list[str]) -> str | None:
|
||||||
return live[0]
|
return live[0]
|
||||||
|
|
||||||
|
|
||||||
|
#: Ile pustych stron POD RZAD uznajemy za koniec katalogu. Jedna nie wystarcza, patrz
|
||||||
|
#: komentarz w petli: serwis moze chwilowo dlawic, a strona moze byc w calosci odrzucona
|
||||||
|
#: przez bramki jakosci.
|
||||||
|
_EMPTY_PAGES_BEFORE_END = 3
|
||||||
|
|
||||||
|
|
||||||
def run_deep_crawl(*, pages_per_run: int = 60, sitetags: list[str] | None = None) -> dict:
|
def run_deep_crawl(*, pages_per_run: int = 60, sitetags: list[str] | None = None) -> dict:
|
||||||
"""Jeden run: wybierz tube, crawl kolejne `pages_per_run` stron od kursora, ingest.
|
"""Jeden run: wybierz tube, crawl kolejne `pages_per_run` stron od kursora, ingest.
|
||||||
Zwraca podsumowanie (sitetag, zakres stron, counters, exhausted)."""
|
Zwraca podsumowanie (sitetag, zakres stron, counters, exhausted)."""
|
||||||
|
|
@ -142,6 +148,7 @@ def run_deep_crawl(*, pages_per_run: int = 60, sitetags: list[str] | None = None
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
last_done = start - 1
|
last_done = start - 1
|
||||||
exhausted = False
|
exhausted = False
|
||||||
|
empty_streak = 0
|
||||||
budget_hit = False
|
budget_hit = False
|
||||||
|
|
||||||
st = state.setdefault(sitetag, {})
|
st = state.setdefault(sitetag, {})
|
||||||
|
|
@ -168,10 +175,30 @@ def run_deep_crawl(*, pages_per_run: int = 60, sitetags: list[str] | None = None
|
||||||
# transient fetch-fail listingu, NIE awansuj kursora, następny run powtórzy
|
# transient fetch-fail listingu, NIE awansuj kursora, następny run powtórzy
|
||||||
break
|
break
|
||||||
if not scenes:
|
if not scenes:
|
||||||
log.info("deep-crawl %s: empty page %d → catalog end (exhausted)", sitetag, page)
|
# Pusta strona to NIE dowod konca katalogu. Dwa powody, oba realne:
|
||||||
exhausted = True
|
# 1. Serwis chwilowo dlawi ruch, wiec wszystkie fetche szczegolow z tej
|
||||||
|
# strony padaja i zostaje pusta lista.
|
||||||
|
# 2. Na stronie faktycznie nic nie przeszlo naszych bramek (np.
|
||||||
|
# naughtymachinima odrzuca klipy < 180 s, a to 2/3 jego katalogu).
|
||||||
|
# Naughtymachinima zostalo tak oznaczone jako `exhausted` na stronie 8,
|
||||||
|
# mimo ze listing ma 1770 stron po 30 filmow, a ponowne wejscie na
|
||||||
|
# strone 9 oddalo 13 scen i zero bledow pobrania. Zostalo 158 scen
|
||||||
|
# zamiast tysiecy (zgloszenie 83eb300d). Dlatego konczymy dopiero po
|
||||||
|
# kilku pustych stronach POD RZAD.
|
||||||
|
empty_streak += 1
|
||||||
last_done = page
|
last_done = page
|
||||||
break
|
_persist()
|
||||||
|
if empty_streak >= _EMPTY_PAGES_BEFORE_END:
|
||||||
|
log.info(
|
||||||
|
"deep-crawl %s: %d pustych stron pod rzad (do %d) → koniec katalogu",
|
||||||
|
sitetag, empty_streak, page,
|
||||||
|
)
|
||||||
|
exhausted = True
|
||||||
|
break
|
||||||
|
log.info("deep-crawl %s: pusta strona %d (%d/%d), lece dalej",
|
||||||
|
sitetag, page, empty_streak, _EMPTY_PAGES_BEFORE_END)
|
||||||
|
continue
|
||||||
|
empty_streak = 0
|
||||||
page_is_backfill = (page > _LATEST_PAGE_THRESHOLD) and not swept_once
|
page_is_backfill = (page > _LATEST_PAGE_THRESHOLD) and not swept_once
|
||||||
for raw in scenes:
|
for raw in scenes:
|
||||||
counters["seen"] += 1
|
counters["seen"] += 1
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue