fix: из-за этого не дает пройти cf_turnstile

This commit is contained in:
protokey committed 2026-09-23 16:51:05 +04:00
1 parent eefcf7a0a9
commit 51a801f612
2 files changed
-103

No files matched your search

-80
View File
@@ -1,80 +0,0 @@
"""
Генерация и persist согласованного browser fingerprint на профиль (user_data_dir).
Используем browserforge — ту же библиотеку, которую под капотом использовал Camoufox — но
поверх голого Playwright Chromium: генерируем Fingerprint (navigator/screen/WebGL/canvas/шрифты/
headers, всё согласовано друг с другом и с выбранным UA) и инжектим его через add_init_script.
Важно: генерируем именно под browser=['chrome'] — раз движок у нас реальный Chromium, подделывать
navigator под Firefox/Safari было бы хуже, чем не подделывать вообще (несоответствие между
заявленным UA и реальными JS-фичами движка — сильный сигнал для антибота).
"""
import json
from pathlib import Path
from browserforge.fingerprints import Fingerprint, FingerprintGenerator, Screen
from browserforge.fingerprints.generator import NavigatorFingerprint, ScreenFingerprint, VideoCard
from browserforge.injectors.utils import InjectFunction, only_injectable_headers
FINGERPRINT_FILE = "fingerprint.json"
def _fingerprint_from_dict(data: dict) -> Fingerprint:
data = dict(data)
data["screen"] = ScreenFingerprint(**data["screen"])
data["navigator"] = NavigatorFingerprint(**data["navigator"])
if data.get("videoCard"):
data["videoCard"] = VideoCard(**data["videoCard"])
return Fingerprint(**data)
def load_or_create_fingerprint(user_data_dir: Path, **generator_kwargs) -> Fingerprint:
"""Держит fingerprint стабильным для профиля: генерируется один раз при первом запросе
этой сессии (user_data_dir) и переиспользуется во всех последующих — так же, как реальный
человек не меняет видеокарту/шрифты/UA между визитами на сайт."""
fp_file = user_data_dir / FINGERPRINT_FILE
if fp_file.exists():
return _fingerprint_from_dict(json.loads(fp_file.read_text()))
generator_kwargs.setdefault("browser", ["chrome"])
# strict=True: без него browserforge при отсутствии точного совпадения в датасете
# тихо ослабляет даже нижнюю границу screen (min_width/min_height) — на практике
# так может прилететь мобильный экран под десктопный viewport (screen.availWidth
# меньше самого окна браузера — физически невозможно и легко детектится), а в
# части случаев цикл релаксации виснет на неопределённое время вместо быстрого
# ответа. Со strict=True generate() либо отдаёт честно подходящий сэмпл, либо
# сразу кидает ValueError — и то и другое быстро (~0.1с)
generator_kwargs.setdefault("strict", True)
fingerprint = FingerprintGenerator().generate(**generator_kwargs)
fp_file.write_text(fingerprint.dumps())
return fingerprint
def browser_kwargs_for(fingerprint: Fingerprint, browser_name: str = "chromium") -> dict:
"""kwargs для `AsyncStealthySession(**kwargs)`, полностью выставляющие браузер под fingerprint —
UA, заголовки, init-script (подделка navigator/screen/WebGL/codecs/battery) и размер окна
(viewport/screen/device_scale_factor).
Всё уходит обычными kwargs сессии — тем же путём, что и остальная конфигурация
(proxy/cookies/locale и т.д.), как это делает сама scrapling — а не отдельным вызовом после
запуска контекста (add_init_script/set_extra_http_headers руками требуют context, который
есть только после входа в `async with AsyncStealthySession(...)`, то есть после того, как
решение о конфигурации уже "должно было" быть принято до запуска браузера).
:param fingerprint: Сгенерированный/загруженный fingerprint (см. load_or_create_fingerprint).
:param browser_name: Имя браузера для фильтрации заголовков (only_injectable_headers) —
"chromium", раз движок у нас реальный Chromium (см. docstring модуля).
:return: dict с ключами `useragent`, `extra_headers`, `init_script_content`, `additional_args`
(viewport/screen/device_scale_factor) — распаковывается в kwargs AsyncStealthySession.
"""
screen = {"width": fingerprint.screen.width, "height": fingerprint.screen.height}
return {
"useragent": fingerprint.navigator.userAgent,
"extra_headers": only_injectable_headers(fingerprint.headers, browser_name),
"init_script_content": InjectFunction(fingerprint),
"additional_args": {
"viewport": dict(screen),
"screen": dict(screen),
"device_scale_factor": fingerprint.screen.devicePixelRatio,
},
}
-23
View File
@@ -6,7 +6,6 @@ from pathlib import Path
from typing import Annotated from typing import Annotated
from urllib.parse import urlsplit from urllib.parse import urlsplit
from browserforge.fingerprints import Screen
from fastapi import Cookie from fastapi import Cookie
from fastapi import FastAPI from fastapi import FastAPI
from fastapi import Request from fastapi import Request
@@ -14,7 +13,6 @@ from fastapi import Response
from patchright.async_api import Page, ProxySettings from patchright.async_api import Page, ProxySettings
from api import actions from api import actions
from api.fingerprint import browser_kwargs_for, load_or_create_fingerprint
from api.geoip import resolve_geo from api.geoip import resolve_geo
from api.schemas import SolveRequest from api.schemas import SolveRequest
from engine import AsyncStealthySession from engine import AsyncStealthySession
@@ -115,27 +113,6 @@ async def solve(
if locale: if locale:
fp_kwargs["locale"] = locale fp_kwargs["locale"] = locale
scr_size = (1920, 1080)
fingerprint = load_or_create_fingerprint(
user_data_dir,
# только нижняя граница: реальный монитор физически не может быть
# меньше открытого на нём окна браузера (viewport ниже берём из
# fingerprint'а, не отсюда)
screen=Screen(min_width=scr_size[0], min_height=scr_size[1]),
**fp_kwargs,
)
# UA/заголовки/init-script (подделка navigator/screen/WebGL/...) /viewport/screen/
# device_scale_factor под fingerprint — единым куском kwargs (см.
# api/fingerprint.py:browser_kwargs_for), тем же путём, что и остальная конфигурация
# сессии (proxy/cookies/locale), а не отдельным вызовом после запуска контекста.
fp_browser_kwargs = browser_kwargs_for(fingerprint)
permissions = ["notifications"]
if geo and geo.get("geolocation"):
fp_browser_kwargs["additional_args"]["geolocation"] = geo["geolocation"]
permissions.append("geolocation")
fp_browser_kwargs["additional_args"]["permissions"] = permissions
nav_timeout_ms = payload.timeout * 1000 if payload.timeout else 60_000 nav_timeout_ms = payload.timeout * 1000 if payload.timeout else 60_000
blocked_domains = set(payload.blocked_domains) if payload.blocked_domains else None blocked_domains = set(payload.blocked_domains) if payload.blocked_domains else None