feature!: перенимаем реализацию прохождения cf_turnstile у scrapling
https://github.com/D4Vinci/Scrapling BREAKING_CHANGES: клик отрабатывает, но прохода не случается
This commit is contained in:
1 parent
f2d4d441f4
commit
8f7645b2b9
11 files changed
+191
-28
No files matched your search
+1
-1
@@ -44,8 +44,8 @@ mozversion==2.4.0
|
|||||||
multidict==6.7.1
|
multidict==6.7.1
|
||||||
numpy==2.5.3
|
numpy==2.5.3
|
||||||
orjson==3.12.0
|
orjson==3.12.0
|
||||||
|
patchright==1.63.0
|
||||||
platformdirs==4.11.7
|
platformdirs==4.11.7
|
||||||
playwright==1.62.0
|
|
||||||
propcache==0.5.2
|
propcache==0.5.2
|
||||||
psutil==7.2.2
|
psutil==7.2.2
|
||||||
pydantic==2.13.5
|
pydantic==2.13.5
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@ from dataclasses import dataclass
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
from playwright.async_api import TimeoutError as PWTimeoutError
|
from patchright.async_api import TimeoutError as PWTimeoutError
|
||||||
|
|
||||||
|
|
||||||
async def click_and_race(page, element, challenge_locator, timeout=30_000):
|
async def click_and_race(page, element, challenge_locator, timeout=30_000):
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import random
|
|||||||
import string
|
import string
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from playwright.async_api import TimeoutError
|
from patchright.async_api import TimeoutError
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
|
|
||||||
# последняя известная позиция курсора на страницу — Playwright не даёт её прочитать из браузера,
|
# последняя известная позиция курсора на страницу — Playwright не даёт её прочитать из браузера,
|
||||||
# поэтому считаем сами, чтобы следующее движение начиналось не из (0, 0), а оттуда, где реально
|
# поэтому считаем сами, чтобы следующее движение начиналось не из (0, 0), а оттуда, где реально
|
||||||
@@ -103,11 +103,14 @@ async def click_human(
|
|||||||
"""move_mouse_human + клик с человеческой задержкой между наведением и down/up, вместо
|
"""move_mouse_human + клик с человеческой задержкой между наведением и down/up, вместо
|
||||||
мгновенного page.mouse.click(x, y)."""
|
мгновенного page.mouse.click(x, y)."""
|
||||||
await move_mouse_human(page, x, y, **move_kwargs)
|
await move_mouse_human(page, x, y, **move_kwargs)
|
||||||
|
print('wait for timeout')
|
||||||
await page.wait_for_timeout(random.uniform(50, 180))
|
await page.wait_for_timeout(random.uniform(50, 180))
|
||||||
|
print('key down')
|
||||||
await page.mouse.down(button=button)
|
await page.mouse.down(button=button)
|
||||||
|
print('sleep')
|
||||||
await page.wait_for_timeout(random.uniform(40, 99))
|
await page.wait_for_timeout(random.uniform(40, 99))
|
||||||
|
print('key up')
|
||||||
await page.mouse.up(button=button)
|
await page.mouse.up(button=button)
|
||||||
await move_mouse_human(page, x-50, y+50, **move_kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_networkidle(page: Page, timeout: Optional[int] = None):
|
async def wait_for_networkidle(page: Page, timeout: Optional[int] = None):
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import re
|
|||||||
import time
|
import time
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
|
|
||||||
from ..base import Detector, ChallengeInfo, AntiBotProvider, Solver
|
from ..base import Detector, ChallengeInfo, AntiBotProvider, Solver
|
||||||
from ..base import Stage
|
from ..base import Stage
|
||||||
@@ -140,9 +140,10 @@ __CF_PATTERN__ = re.compile(r"^https://challenges\.cloudflare\.com/cdn-cgi/chall
|
|||||||
class CloudflareSolver(Solver):
|
class CloudflareSolver(Solver):
|
||||||
|
|
||||||
async def solve(self, page: Page, info: ChallengeInfo):
|
async def solve(self, page: Page, info: ChallengeInfo):
|
||||||
|
print("start solving")
|
||||||
iframe = page.frame(url=__CF_PATTERN__)
|
iframe = page.frame(url=__CF_PATTERN__)
|
||||||
if iframe:
|
if iframe:
|
||||||
await page.wait_for_timeout(randint(4000, 7000)) # дать чекбоксу домонтироваться в closed shadow root
|
await page.wait_for_timeout(randint(500, 1000))
|
||||||
frame_el = await iframe.frame_element()
|
frame_el = await iframe.frame_element()
|
||||||
box = await frame_el.bounding_box()
|
box = await frame_el.bounding_box()
|
||||||
else:
|
else:
|
||||||
@@ -152,8 +153,6 @@ class CloudflareSolver(Solver):
|
|||||||
# # Calculate the Captcha coordinates for any viewport
|
# # Calculate the Captcha coordinates for any viewport
|
||||||
captcha_x, captcha_y = box["x"] + randint(26, 28), box["y"] + randint(25, 27)
|
captcha_x, captcha_y = box["x"] + randint(26, 28), box["y"] + randint(25, 27)
|
||||||
|
|
||||||
# await page.mouse.click(captcha_x, captcha_y, delay=randint(100, 200), button="left")
|
|
||||||
|
|
||||||
await click_human(page, captcha_x, captcha_y)
|
await click_human(page, captcha_x, captcha_y)
|
||||||
|
|
||||||
await wait_for_networkidle(page)
|
await wait_for_networkidle(page)
|
||||||
@@ -186,6 +185,7 @@ class CloudflareSolver(Solver):
|
|||||||
return
|
return
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
|
||||||
captcha_solver = self._opt.get("captcha_solver")
|
captcha_solver = self._opt.get("captcha_solver")
|
||||||
if not captcha_solver:
|
if not captcha_solver:
|
||||||
raise RuntimeError("Нужен captcha_solver (2captcha) чтобы решить Turnstile")
|
raise RuntimeError("Нужен captcha_solver (2captcha) чтобы решить Turnstile")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
|
|
||||||
from captcha.base import Captcha
|
from captcha.base import Captcha
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
реестр детекторов, порядок проверки
|
реестр детекторов, порядок проверки
|
||||||
"""
|
"""
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
|
|
||||||
from .base import AntiBotProvider
|
from .base import AntiBotProvider
|
||||||
from .exceptions import ProviderNotDetected
|
from .exceptions import ProviderNotDetected
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import hashlib
|
|||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
from twocaptcha import TwoCaptcha
|
from twocaptcha import TwoCaptcha
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from playwright.async_api import Page
|
from patchright.async_api import Page
|
||||||
|
|
||||||
from antibot.base import click_and_race
|
from antibot.base import click_and_race
|
||||||
from antibot.orchestrator import pass_challenges
|
from antibot.orchestrator import pass_challenges
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
"""
|
||||||
|
Launch/context-флаги для Chromium, снижающие детект автоматизации на уровне ниже
|
||||||
|
JS-фингерпринта (который уже покрывает browserforge, см. engine/fingerprint.py).
|
||||||
|
Портировано из scrapling.engines.constants / _browsers._base.BaseSessionMixin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
HARMFUL_ARGS = (
|
||||||
|
# Playwright сам добавляет эти флаги по умолчанию — они одни из самых дешёвых
|
||||||
|
# и надёжных сигналов автоматизации, поэтому их нужно гасить через ignore_default_args
|
||||||
|
"--enable-automation",
|
||||||
|
"--disable-popup-blocking",
|
||||||
|
"--disable-component-update",
|
||||||
|
"--disable-default-apps",
|
||||||
|
"--disable-extensions",
|
||||||
|
)
|
||||||
|
|
||||||
|
DEFAULT_ARGS = (
|
||||||
|
"--no-pings",
|
||||||
|
"--no-first-run",
|
||||||
|
"--disable-infobars",
|
||||||
|
"--disable-breakpad",
|
||||||
|
"--no-service-autorun",
|
||||||
|
"--homepage=about:blank",
|
||||||
|
"--password-store=basic",
|
||||||
|
"--disable-hang-monitor",
|
||||||
|
"--no-default-browser-check",
|
||||||
|
"--disable-session-crashed-bubble",
|
||||||
|
"--disable-search-engine-choice-screen",
|
||||||
|
)
|
||||||
|
|
||||||
|
STEALTH_ARGS = (
|
||||||
|
"--test-type",
|
||||||
|
"--mute-audio",
|
||||||
|
"--disable-sync",
|
||||||
|
"--hide-scrollbars",
|
||||||
|
"--disable-logging",
|
||||||
|
"--start-maximized", # обход headless-детекта по размеру окна
|
||||||
|
"--enable-async-dns",
|
||||||
|
"--use-mock-keychain",
|
||||||
|
"--disable-translate",
|
||||||
|
"--disable-voice-input",
|
||||||
|
"--window-position=0,0",
|
||||||
|
"--disable-wake-on-wifi",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--enable-tcp-fast-open",
|
||||||
|
"--enable-web-bluetooth",
|
||||||
|
"--disable-cloud-import",
|
||||||
|
"--disable-print-preview",
|
||||||
|
"--disable-dev-shm-usage",
|
||||||
|
"--metrics-recording-only",
|
||||||
|
"--disable-crash-reporter",
|
||||||
|
"--disable-partial-raster",
|
||||||
|
"--disable-gesture-typing",
|
||||||
|
"--disable-checker-imaging",
|
||||||
|
"--disable-prompt-on-repost",
|
||||||
|
"--force-color-profile=srgb",
|
||||||
|
"--font-render-hinting=none",
|
||||||
|
"--aggressive-cache-discard",
|
||||||
|
"--disable-cookie-encryption",
|
||||||
|
"--disable-domain-reliability",
|
||||||
|
"--disable-threaded-animation",
|
||||||
|
"--disable-threaded-scrolling",
|
||||||
|
"--enable-simple-cache-backend",
|
||||||
|
"--disable-background-networking",
|
||||||
|
"--enable-surface-synchronization",
|
||||||
|
"--disable-image-animation-resync",
|
||||||
|
"--disable-renderer-backgrounding",
|
||||||
|
"--disable-ipc-flooding-protection",
|
||||||
|
"--prerender-from-omnibox=disabled",
|
||||||
|
"--safebrowsing-disable-auto-update",
|
||||||
|
"--disable-offer-upload-credit-cards",
|
||||||
|
"--disable-background-timer-throttling",
|
||||||
|
"--disable-new-content-rendering-timeout",
|
||||||
|
"--run-all-compositor-stages-before-draw",
|
||||||
|
"--disable-client-side-phishing-detection",
|
||||||
|
"--disable-backgrounding-occluded-windows",
|
||||||
|
"--disable-layer-tree-host-memory-pressure",
|
||||||
|
"--autoplay-policy=user-gesture-required",
|
||||||
|
"--disable-offer-store-unmasked-wallet-cards",
|
||||||
|
"--disable-blink-features=AutomationControlled",
|
||||||
|
"--disable-component-extensions-with-background-pages",
|
||||||
|
"--enable-features=NetworkService,NetworkServiceInProcess,TrustTokens,TrustTokensAlwaysAllowIssuance",
|
||||||
|
"--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4",
|
||||||
|
"--disable-features=AudioServiceOutOfProcess,TranslateUI,BlinkGenPropertyTrees",
|
||||||
|
)
|
||||||
|
|
||||||
|
_WEBRTC_ARGS = (
|
||||||
|
"--webrtc-ip-handling-policy=disable_non_proxied_udp",
|
||||||
|
"--force-webrtc-ip-handling-policy",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def launch_args(locale: str | None = None, block_webrtc: bool = True) -> list[str]:
|
||||||
|
"""Флаги запуска Chromium: скорость + анти-детект + (опционально) защита от WebRTC-утечки
|
||||||
|
реального IP мимо прокси + (если передана locale) выставление языка браузера на уровне
|
||||||
|
лаунча, а не только через JS-контекст — иначе Web Workers/Intl внутри браузера остаются
|
||||||
|
на языке хоста, даже когда browserforge уже подменил navigator.language на странице."""
|
||||||
|
args = list(DEFAULT_ARGS) + list(STEALTH_ARGS)
|
||||||
|
|
||||||
|
if block_webrtc:
|
||||||
|
args += list(_WEBRTC_ARGS)
|
||||||
|
|
||||||
|
if locale:
|
||||||
|
base_lang = locale.split("-")[0].lower()
|
||||||
|
accept_lang = f"{locale},{base_lang}" if base_lang != locale.lower() else locale
|
||||||
|
args += [f"--lang={locale}", f"--accept-lang={accept_lang}"]
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def stealth_context_options() -> dict:
|
||||||
|
"""Context-опции, не пересекающиеся с тем, что уже выставляет browserforge
|
||||||
|
(user_agent/viewport/device_scale_factor) — их сюда специально не кладём, чтобы
|
||||||
|
fingerprint оставался единственным источником истины для этих полей."""
|
||||||
|
return {
|
||||||
|
"color_scheme": "dark", # обходит проверку prefersLightColor в creepjs
|
||||||
|
"is_mobile": False,
|
||||||
|
"has_touch": False,
|
||||||
|
"ignore_https_errors": True,
|
||||||
|
}
|
||||||
+31
-8
@@ -18,13 +18,32 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from playwright.async_api import ProxySettings
|
from patchright.async_api import ProxySettings
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
IPINFO_URL = "https://ipinfo.io/json"
|
IPINFO_URL = "https://ipinfo.io/json"
|
||||||
DEFAULT_CACHE_FILE = Path(__file__).resolve().parent.parent.parent / "extra" / "geoip_cache.json"
|
DEFAULT_CACHE_FILE = Path(__file__).resolve().parent.parent.parent / "extra" / "geoip_cache.json"
|
||||||
DEFAULT_TTL = 24 * 3600 # прокси может сменить exit-IP — не кэшируем навечно
|
DEFAULT_TTL = 24 * 3600 # прокси может сменить exit-IP — не кэшируем навечно
|
||||||
|
DEFAULT_LOCALE = "en-US"
|
||||||
|
|
||||||
|
# Страна exit-IP -> основная локаль. Раньше это (как и timezone/geolocation) под капотом
|
||||||
|
# делал Camoufox(geoip=True); теперь резолвим сами и прокидываем в browserforge (locale
|
||||||
|
# согласованно выставляет navigator.language/languages и Accept-Language header) и в
|
||||||
|
# launch-флаги Chromium --lang=/--accept-lang= (engine/browser_launch.py). Не более чем
|
||||||
|
# точечная таблица под реально используемые прокси-гео — не претендует на полноту ISO-3166.
|
||||||
|
_COUNTRY_LOCALE: dict[str, str] = {
|
||||||
|
"RU": "ru-RU", "BY": "ru-BY", "KZ": "ru-KZ", "UA": "uk-UA",
|
||||||
|
"US": "en-US", "GB": "en-GB", "CA": "en-CA", "AU": "en-AU", "IE": "en-IE", "IN": "en-IN",
|
||||||
|
"DE": "de-DE", "AT": "de-AT", "CH": "de-CH",
|
||||||
|
"FR": "fr-FR", "BE": "fr-BE",
|
||||||
|
"ES": "es-ES", "MX": "es-MX", "AR": "es-AR",
|
||||||
|
"IT": "it-IT", "PT": "pt-PT", "BR": "pt-BR", "NL": "nl-NL",
|
||||||
|
"PL": "pl-PL", "CZ": "cs-CZ", "SK": "sk-SK", "RO": "ro-RO", "HU": "hu-HU",
|
||||||
|
"SE": "sv-SE", "NO": "nb-NO", "FI": "fi-FI", "DK": "da-DK",
|
||||||
|
"TR": "tr-TR", "GR": "el-GR",
|
||||||
|
"CN": "zh-CN", "TW": "zh-TW", "JP": "ja-JP", "KR": "ko-KR",
|
||||||
|
}
|
||||||
|
|
||||||
_cache_lock = asyncio.Lock()
|
_cache_lock = asyncio.Lock()
|
||||||
|
|
||||||
@@ -59,14 +78,16 @@ async def resolve_geo(
|
|||||||
cache_file: Path = DEFAULT_CACHE_FILE,
|
cache_file: Path = DEFAULT_CACHE_FILE,
|
||||||
ttl: float = DEFAULT_TTL,
|
ttl: float = DEFAULT_TTL,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Возвращает {"timezone_id", "geolocation": {"latitude", "longitude"}, "country", "ip"}
|
"""Возвращает {"timezone_id", "geolocation": {"latitude", "longitude"}, "country", "ip", "locale"}
|
||||||
по IP, который виден снаружи через прокси, или None, если резолв не удался и в кэше
|
по IP, который виден снаружи через прокси, или None, если резолв не удался и в кэше
|
||||||
тоже ничего нет — в этом случае вызывающий код просто не переопределяет timezone/geolocation.
|
тоже ничего нет — в этом случае вызывающий код просто не переопределяет timezone/geolocation/locale.
|
||||||
|
|
||||||
Язык (locale) сюда намеренно не подмешиваем: Accept-Language — это предпочтение пользователя,
|
"locale" — основная локаль страны exit-IP (см. _COUNTRY_LOCALE), с фолбэком DEFAULT_LOCALE
|
||||||
а не производная от геолокации IP, и его уже согласованно генерирует fingerprint (см.
|
для неизвестных/отсутствующих кодов страны. Реальный человек с российским прокси не сидит
|
||||||
engine/fingerprint.py). Подменять его на "en-<COUNTRY>" было бы отдельным, никак не
|
с английской раскладкой браузера, поэтому locale — такой же производный от IP сигнал, как
|
||||||
связанным с реальным поведением допущением."""
|
timezone/geolocation, а не независимое "предпочтение пользователя". Прокидывается вызывающим
|
||||||
|
кодом в browserforge (согласованно выставляет navigator.language/languages и Accept-Language,
|
||||||
|
см. engine/fingerprint.py) и в launch-флаги Chromium (engine/browser_launch.py)."""
|
||||||
key = proxy_settings_to_url(proxy)
|
key = proxy_settings_to_url(proxy)
|
||||||
|
|
||||||
async with _cache_lock:
|
async with _cache_lock:
|
||||||
@@ -98,11 +119,13 @@ async def resolve_geo(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
geolocation = None
|
geolocation = None
|
||||||
|
|
||||||
|
country = data.get("country") # "RU", "US", ...
|
||||||
result = {
|
result = {
|
||||||
"ip": data.get("ip"),
|
"ip": data.get("ip"),
|
||||||
"country": data.get("country"), # "RU", "US", ...
|
"country": country,
|
||||||
"timezone_id": data.get("timezone"),
|
"timezone_id": data.get("timezone"),
|
||||||
"geolocation": geolocation,
|
"geolocation": geolocation,
|
||||||
|
"locale": _COUNTRY_LOCALE.get(country, DEFAULT_LOCALE),
|
||||||
}
|
}
|
||||||
|
|
||||||
async with _cache_lock:
|
async with _cache_lock:
|
||||||
|
|||||||
+23
-6
@@ -11,10 +11,11 @@ from fastapi import Cookie
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi import Response
|
from fastapi import Response
|
||||||
from playwright.async_api import ProxySettings
|
from patchright.async_api import ProxySettings
|
||||||
from playwright.async_api import async_playwright
|
from patchright.async_api import async_playwright
|
||||||
|
|
||||||
from engine import actions
|
from engine import actions
|
||||||
|
from engine.browser_launch import HARMFUL_ARGS, launch_args, stealth_context_options
|
||||||
from engine.fingerprint import apply_fingerprint, context_options_for, load_or_create_fingerprint
|
from engine.fingerprint import apply_fingerprint, context_options_for, load_or_create_fingerprint
|
||||||
from engine.geoip import resolve_geo
|
from engine.geoip import resolve_geo
|
||||||
from engine.schemas import SolveRequest
|
from engine.schemas import SolveRequest
|
||||||
@@ -77,9 +78,16 @@ async def solve(
|
|||||||
if not user_data_dir.exists():
|
if not user_data_dir.exists():
|
||||||
user_data_dir.mkdir(parents=True)
|
user_data_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
# geo — до создания fingerprint'а, чтобы производная от прокси locale (см. engine/geoip.py)
|
||||||
|
# попала в сам fingerprint, а не подменяла его задним числом
|
||||||
|
geo = await resolve_geo(proxy)
|
||||||
|
locale = geo.get("locale") if geo else None
|
||||||
|
|
||||||
fp_kwargs = {}
|
fp_kwargs = {}
|
||||||
if payload.user_agent:
|
if payload.user_agent:
|
||||||
fp_kwargs["user_agent"] = payload.user_agent
|
fp_kwargs["user_agent"] = payload.user_agent
|
||||||
|
if locale:
|
||||||
|
fp_kwargs["locale"] = locale
|
||||||
fingerprint = load_or_create_fingerprint(
|
fingerprint = load_or_create_fingerprint(
|
||||||
user_data_dir,
|
user_data_dir,
|
||||||
# только нижняя граница: реалистичный монитор должен быть не меньше запрошенного окна,
|
# только нижняя граница: реалистичный монитор должен быть не меньше запрошенного окна,
|
||||||
@@ -89,24 +97,33 @@ async def solve(
|
|||||||
**fp_kwargs,
|
**fp_kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx_kwargs = context_options_for(
|
ctx_kwargs = {
|
||||||
|
# базовые анти-детект опции ниже — fingerprint накладывается поверх и выигрывает
|
||||||
|
# при конфликте по user_agent/viewport/device_scale_factor
|
||||||
|
**stealth_context_options(),
|
||||||
|
**context_options_for(
|
||||||
fingerprint,
|
fingerprint,
|
||||||
viewport={"width": scr_size[0], "height": scr_size[1]},
|
viewport={"width": scr_size[0], "height": scr_size[1]},
|
||||||
)
|
),
|
||||||
|
}
|
||||||
|
|
||||||
geo = await resolve_geo(proxy)
|
permissions = ["notifications"]
|
||||||
if geo:
|
if geo:
|
||||||
if geo.get("timezone_id"):
|
if geo.get("timezone_id"):
|
||||||
ctx_kwargs["timezone_id"] = geo["timezone_id"]
|
ctx_kwargs["timezone_id"] = geo["timezone_id"]
|
||||||
if geo.get("geolocation"):
|
if geo.get("geolocation"):
|
||||||
ctx_kwargs["geolocation"] = geo["geolocation"]
|
ctx_kwargs["geolocation"] = geo["geolocation"]
|
||||||
ctx_kwargs["permissions"] = ["geolocation"]
|
permissions.append("geolocation")
|
||||||
|
ctx_kwargs["permissions"] = permissions
|
||||||
|
|
||||||
async with async_playwright() as p:
|
async with async_playwright() as p:
|
||||||
context = await p.chromium.launch_persistent_context(
|
context = await p.chromium.launch_persistent_context(
|
||||||
user_data_dir=str(user_data_dir),
|
user_data_dir=str(user_data_dir),
|
||||||
proxy=proxy,
|
proxy=proxy,
|
||||||
headless=False,
|
headless=False,
|
||||||
|
channel="chrome",
|
||||||
|
args=launch_args(locale=locale),
|
||||||
|
ignore_default_args=HARMFUL_ARGS,
|
||||||
**ctx_kwargs,
|
**ctx_kwargs,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in new issue
Block a user