""" Генерация и 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, }, }