84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
"""Stealth browser factory — bypasses Cloudflare/ArvanCloud bot detection."""
|
|
from __future__ import annotations
|
|
|
|
import random
|
|
import time
|
|
from contextlib import contextmanager
|
|
from typing import Generator
|
|
|
|
import undetected_chromedriver as uc
|
|
from selenium.webdriver.chrome.options import Options
|
|
from selenium_stealth import stealth
|
|
|
|
from config import config
|
|
|
|
_USER_AGENTS = [
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
|
]
|
|
|
|
|
|
def _build_options(user_agent: str, headless: bool) -> Options:
|
|
opts = Options()
|
|
if headless:
|
|
opts.add_argument("--headless=new")
|
|
opts.add_argument(f"--user-agent={user_agent}")
|
|
opts.add_argument("--no-sandbox")
|
|
opts.add_argument("--disable-dev-shm-usage")
|
|
opts.add_argument("--disable-blink-features=AutomationControlled")
|
|
opts.add_argument("--disable-infobars")
|
|
opts.add_argument("--window-size=1920,1080")
|
|
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
|
|
opts.add_experimental_option("useAutomationExtension", False)
|
|
if config.chrome_binary:
|
|
opts.binary_location = config.chrome_binary
|
|
return opts
|
|
|
|
|
|
def make_driver(headless: bool | None = None) -> uc.Chrome:
|
|
"""Return a stealthed undetected Chrome instance."""
|
|
use_headless = config.headless if headless is None else headless
|
|
ua = random.choice(_USER_AGENTS)
|
|
opts = _build_options(ua, use_headless)
|
|
|
|
driver = uc.Chrome(options=opts, use_subprocess=True)
|
|
|
|
stealth(
|
|
driver,
|
|
languages=["en-US", "en"],
|
|
vendor="Google Inc.",
|
|
platform="Win32",
|
|
webgl_vendor="Intel Inc.",
|
|
renderer="Intel Iris OpenGL Engine",
|
|
fix_hairline=True,
|
|
)
|
|
|
|
# Mask navigator.webdriver via CDP
|
|
driver.execute_cdp_cmd(
|
|
"Page.addScriptToEvaluateOnNewDocument",
|
|
{
|
|
"source": """
|
|
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
|
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
|
|
Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
|
|
"""
|
|
},
|
|
)
|
|
return driver
|
|
|
|
|
|
@contextmanager
|
|
def driver_session(headless: bool | None = None) -> Generator[uc.Chrome, None, None]:
|
|
"""Context manager that guarantees driver.quit() on exit."""
|
|
driver = make_driver(headless)
|
|
try:
|
|
yield driver
|
|
finally:
|
|
driver.quit()
|
|
|
|
|
|
def human_delay(lo: float = 0.8, hi: float = 2.5) -> None:
|
|
"""Sleep for a random human-like interval."""
|
|
time.sleep(random.uniform(lo, hi))
|