chore: перетащил обертку над браузером из scrapling

This commit is contained in:
protokey committed 2026-09-23 15:54:48 +04:00
1 parent 8f8bc62ae7
commit 257310b7d5
19 files changed
+4967 -283

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
"""
Генерация и 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,
},
}