FlareSolverr-фолбэк для загрузки по ссылке; -ReleaseUrl в predict.ps1
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
bc9ff36e61
commit
dce219aeee
6 files changed
+150
-10
No files matched your search
+57
-7
@@ -11,10 +11,19 @@
|
||||
kinozal отдаёт описание только авторизованным пользователям, поэтому для него
|
||||
передавайте cookie (`cookie="uid=...; pass=..."`) либо скачивайте страницу сами
|
||||
(grabber уже умеет) и отдавайте сервису готовый `html`.
|
||||
|
||||
Cloudflare / защита от ботов: если задан FlareSolverr (ваш собственный
|
||||
экземпляр, как у Jackett/Prowlarr) — `flaresolverr="http://host:8191"` или
|
||||
переменная окружения DUB_FLARESOLVERR_URL, — то при ответе 403/503 или
|
||||
странице-заглушке страница запрашивается через него. Адрес FlareSolverr берётся
|
||||
только из настроек сервиса, но не из запроса клиента.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -68,24 +77,65 @@ def blocked_reason(html: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def fetch_html(url: str, *, timeout: float = 15.0,
|
||||
cookie: str | None = None) -> tuple[str, str]:
|
||||
"""Скачивает страницу релиза. Возвращает (html, tracker)."""
|
||||
tracker = detect_tracker(url)
|
||||
def _fetch_direct(url: str, timeout: float, cookie: str | None) -> str:
|
||||
headers = {"User-Agent": USER_AGENT, "Accept-Language": "ru,en;q=0.8"}
|
||||
if cookie:
|
||||
headers["Cookie"] = cookie
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
# редиректы на чужие хосты не нужны: открываем без follow-за-пределы allowlist
|
||||
# редиректы только внутри allowlist трекеров
|
||||
opener = urllib.request.build_opener(_AllowlistRedirect())
|
||||
with opener.open(req, timeout=timeout) as resp:
|
||||
data = resp.read(MAX_BYTES + 1)
|
||||
if len(data) > MAX_BYTES:
|
||||
raise ValueError("страница слишком большая")
|
||||
html = decode_bytes(data, resp.headers.get("Content-Type", ""))
|
||||
return decode_bytes(data, resp.headers.get("Content-Type", ""))
|
||||
|
||||
|
||||
def _fetch_flaresolverr(endpoint: str, url: str, timeout: float,
|
||||
cookie: str | None) -> str:
|
||||
"""Запрос через FlareSolverr (POST {endpoint}/v1, cmd=request.get)."""
|
||||
host = urlparse(url).hostname or ""
|
||||
body = {"cmd": "request.get", "url": url, "maxTimeout": int(timeout * 1000)}
|
||||
if cookie:
|
||||
body["cookies"] = [
|
||||
{"name": k.strip(), "value": v.strip(), "domain": host}
|
||||
for k, _, v in (c.partition("=") for c in cookie.split(";") if "=" in c)]
|
||||
req = urllib.request.Request(
|
||||
endpoint.rstrip("/") + "/v1", data=json.dumps(body).encode(),
|
||||
headers={"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=timeout + 15) as resp:
|
||||
data = json.loads(resp.read(MAX_BYTES * 2).decode("utf-8"))
|
||||
if data.get("status") != "ok":
|
||||
raise ValueError(f"FlareSolverr: {data.get('message', 'ошибка')}")
|
||||
return data["solution"]["response"]
|
||||
|
||||
|
||||
def fetch_html(url: str, *, timeout: float = 15.0, cookie: str | None = None,
|
||||
flaresolverr: str | None = None) -> tuple[str, str]:
|
||||
"""Скачивает страницу релиза. Возвращает (html, tracker).
|
||||
|
||||
Сначала прямой запрос; при 403/503 или странице-заглушке и заданном
|
||||
FlareSolverr — повтор через него.
|
||||
"""
|
||||
tracker = detect_tracker(url)
|
||||
flaresolverr = flaresolverr or os.environ.get("DUB_FLARESOLVERR_URL") or None
|
||||
html, direct_error = None, None
|
||||
try:
|
||||
html = _fetch_direct(url, timeout, cookie)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code not in (403, 429, 503) or not flaresolverr:
|
||||
raise
|
||||
direct_error = e
|
||||
if html is not None and blocked_reason(html) and flaresolverr:
|
||||
html = None
|
||||
if html is None:
|
||||
if not flaresolverr:
|
||||
raise ValueError("страница недоступна")
|
||||
html = _fetch_flaresolverr(flaresolverr, url, timeout, cookie)
|
||||
reason = blocked_reason(html)
|
||||
if reason:
|
||||
raise ValueError(f"страница недоступна: {reason}")
|
||||
hint = "" if flaresolverr else " (задайте FlareSolverr: DUB_FLARESOLVERR_URL)"
|
||||
raise ValueError(f"страница недоступна: {reason}{hint}")
|
||||
return html, tracker
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user