browser_manager.py•18.4 kB
"""
Módulo para gestionar instancias de WebDriver con configuraciones avanzadas.
Basado en el ejemplo de DriverFactory.py del usuario.
"""
import os
import platform
import random
import tempfile
import shutil
import time
from typing import Optional, Dict, Any
from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from selenium.webdriver.remote.webdriver import WebDriver
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
try:
import undetected_chromedriver as uc
UNDETECTED_CHROME_AVAILABLE = True
except ImportError:
UNDETECTED_CHROME_AVAILABLE = False
from config import BrowserOptions, ProxyConfig, DetectionEvasionConfig
class WebDriverManager:
"""Gestor de instancias de WebDriver con configuraciones avanzadas."""
def __init__(self):
self.system = platform.system().lower()
self.user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
]
# Rutas específicas de navegadores por SO
self.browser_paths = {
"windows": {
"chrome": [
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
],
"firefox": [
r"C:\Program Files\Mozilla Firefox\firefox.exe",
r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe"
]
},
"linux": {
"chrome": [
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
"/usr/bin/chromium",
"/usr/bin/chromium-browser"
],
"firefox": [
"/usr/bin/firefox",
"/usr/bin/firefox-esr"
]
},
"darwin": { # macOS
"chrome": [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
],
"firefox": [
"/Applications/Firefox.app/Contents/MacOS/firefox"
]
}
}
def _get_browser_binary_path(self, browser_name: str) -> Optional[str]:
"""Detecta la ruta del binario del navegador según el SO."""
if self.system not in self.browser_paths:
return None
if browser_name not in self.browser_paths[self.system]:
return None
for path in self.browser_paths[self.system][browser_name]:
if os.path.exists(path):
return path
return None
def create_chrome_driver(
self,
browser_options: BrowserOptions,
proxy_config: Optional[ProxyConfig] = None,
detection_evasion: Optional[DetectionEvasionConfig] = None
) -> Dict[str, Any]:
"""Crea una instancia de Chrome WebDriver con configuraciones stealth."""
if detection_evasion and detection_evasion.use_undetected_chrome and UNDETECTED_CHROME_AVAILABLE:
return self._create_undetected_chrome(browser_options, proxy_config, detection_evasion)
else:
return self._create_standard_chrome(browser_options, proxy_config, detection_evasion)
def _create_undetected_chrome(
self,
browser_options: BrowserOptions,
proxy_config: Optional[ProxyConfig],
detection_evasion: DetectionEvasionConfig
) -> Dict[str, Any]:
"""Crea una instancia de Chrome usando undetected-chromedriver."""
try:
# Crear driver con undetected-chromedriver (similar al ejemplo del usuario)
driver = uc.Chrome(
headless=browser_options.headless,
version_main=None # Detectar automáticamente
)
# Aplicar configuraciones stealth similares al ejemplo del usuario
self._apply_stealth_configurations(driver, browser_options, detection_evasion)
# Crear directorio temporal para user data
user_data_dir = tempfile.mkdtemp()
return {
"driver": driver,
"user_data_dir": user_data_dir
}
except Exception as e:
raise Exception(f"Error al crear undetected Chrome driver: {e}")
def _create_standard_chrome(
self,
browser_options: BrowserOptions,
proxy_config: Optional[ProxyConfig],
detection_evasion: Optional[DetectionEvasionConfig]
) -> Dict[str, Any]:
"""Crea una instancia de Chrome estándar con configuraciones stealth."""
try:
options = ChromeOptions()
# Detectar ruta del binario de Chrome
chrome_binary = self._get_browser_binary_path("chrome")
if chrome_binary:
options.binary_location = chrome_binary
# Crear directorio temporal único para user data con timestamp
import time
timestamp = str(int(time.time() * 1000000)) # microsegundos para mayor unicidad
user_data_dir = tempfile.mkdtemp(prefix=f"chrome_profile_{timestamp}_")
# Configuraciones básicas (sin user-data-dir para evitar conflictos)
# options.add_argument(f"--user-data-dir={user_data_dir}") # Comentado temporalmente
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--disable-blink-features=AutomationControlled")
# Modo headless
if browser_options.headless:
options.add_argument("--headless=new")
# Modo incógnito
if browser_options.incognito:
options.add_argument("--incognito")
# User agent aleatorio
user_agent = browser_options.user_agent
if detection_evasion and detection_evasion.randomize_user_agent:
user_agent = random.choice(self.user_agents)
if user_agent:
options.add_argument(f"--user-agent={user_agent}")
# Configuraciones de contenido
prefs = {
"profile.default_content_setting_values.notifications": 2,
"profile.default_content_settings.popups": 0
}
if browser_options.disable_images:
prefs["profile.managed_default_content_settings.images"] = 2
if browser_options.disable_javascript:
prefs["profile.managed_default_content_settings.javascript"] = 2
options.add_experimental_option("prefs", prefs)
# Configuraciones de evasión de detección
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# Argumentos personalizados
if browser_options.custom_args:
for arg in browser_options.custom_args:
options.add_argument(arg)
# Configuración de proxy
if proxy_config:
if proxy_config.http:
options.add_argument(f"--proxy-server={proxy_config.http}")
elif proxy_config.https:
options.add_argument(f"--proxy-server={proxy_config.https}")
elif proxy_config.socks:
options.add_argument(f"--proxy-server={proxy_config.socks}")
# Configurar servicio
service = ChromeService(ChromeDriverManager().install())
# Crear driver
driver = webdriver.Chrome(service=service, options=options)
# Aplicar configuraciones stealth post-creación
self._apply_stealth_configurations(driver, browser_options, detection_evasion)
return {
"driver": driver,
"user_data_dir": user_data_dir
}
except Exception as e:
# Limpiar directorio temporal en caso de error
if 'user_data_dir' in locals() and os.path.exists(user_data_dir):
try:
shutil.rmtree(user_data_dir)
except:
pass
raise Exception(f"Error al crear Chrome driver estándar: {e}")
def _apply_stealth_configurations(
self,
driver: WebDriver,
browser_options: BrowserOptions,
detection_evasion: Optional[DetectionEvasionConfig]
):
"""Aplica configuraciones stealth al driver (basado en el ejemplo del usuario)."""
try:
# Modificar navigator.webdriver flag (del ejemplo del usuario)
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
# Configurar viewport aleatorio pero realista
viewport_width = browser_options.window_width or random.randint(1024, 1920)
viewport_height = browser_options.window_height or random.randint(768, 1080)
if detection_evasion and detection_evasion.randomize_viewport:
viewport_width += random.randint(-100, 100)
viewport_height += random.randint(-50, 50)
driver.set_window_size(viewport_width, viewport_height)
# Configurar user agent con CDP (del ejemplo del usuario)
if detection_evasion and detection_evasion.randomize_user_agent:
user_agent = random.choice(self.user_agents)
try:
driver.execute_cdp_cmd('Network.setUserAgentOverride', {
"userAgent": user_agent
})
except:
pass # Fallback si CDP no está disponible
# Configurar headers realistas (del ejemplo del usuario)
try:
driver.execute_cdp_cmd('Network.setExtraHTTPHeaders', {
'headers': {
'Accept-Language': 'es-ES,es;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120"',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1'
}
})
except:
pass # Fallback si CDP no está disponible
# Emular características del dispositivo (del ejemplo del usuario)
try:
driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', {
'mobile': False,
'width': viewport_width,
'height': viewport_height,
'deviceScaleFactor': random.choice([1, 1.25, 1.5, 2]),
'screenWidth': viewport_width,
'screenHeight': viewport_height,
})
except:
pass # Fallback si CDP no está disponible
except Exception as e:
print(f"Advertencia: No se pudieron aplicar todas las configuraciones stealth: {e}")
def create_firefox_driver(
self,
browser_options: BrowserOptions,
proxy_config: Optional[ProxyConfig] = None,
detection_evasion: Optional[DetectionEvasionConfig] = None
) -> Dict[str, Any]:
"""Crea una instancia de Firefox WebDriver."""
try:
options = FirefoxOptions()
# Detectar ruta del binario de Firefox
firefox_binary = self._get_browser_binary_path("firefox")
if firefox_binary:
options.binary_location = firefox_binary
# Modo headless
if browser_options.headless:
options.add_argument("--headless")
# User agent
user_agent = browser_options.user_agent
if detection_evasion and detection_evasion.randomize_user_agent:
user_agent = random.choice(self.user_agents)
if user_agent:
options.set_preference("general.useragent.override", user_agent)
# Configuraciones de contenido
if browser_options.disable_images:
options.set_preference("permissions.default.image", 2)
if browser_options.disable_javascript:
options.set_preference("javascript.enabled", False)
# Configuraciones de privacidad
if browser_options.incognito:
options.add_argument("--private")
# Configuración de proxy
if proxy_config:
if proxy_config.http:
proxy_parts = proxy_config.http.replace("http://", "").split(":")
if len(proxy_parts) == 2:
options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.http", proxy_parts[0])
options.set_preference("network.proxy.http_port", int(proxy_parts[1]))
options.set_preference("network.proxy.ssl", proxy_parts[0])
options.set_preference("network.proxy.ssl_port", int(proxy_parts[1]))
# Configurar servicio
service = FirefoxService(GeckoDriverManager().install())
# Crear driver
driver = webdriver.Firefox(service=service, options=options)
# Configurar tamaño de ventana
if not browser_options.headless:
width = browser_options.window_width or 1280
height = browser_options.window_height or 720
if detection_evasion and detection_evasion.randomize_viewport:
width += random.randint(-100, 100)
height += random.randint(-50, 50)
driver.set_window_size(width, height)
return {
"driver": driver,
"user_data_dir": None
}
except Exception as e:
raise Exception(f"Error al crear Firefox driver: {e}")
def create_driver(
self,
browser_type: str,
browser_options: BrowserOptions,
proxy_config: Optional[ProxyConfig] = None,
detection_evasion: Optional[DetectionEvasionConfig] = None
) -> Dict[str, Any]:
"""Crea una instancia de WebDriver según el tipo de navegador especificado."""
browser_type = browser_type.lower()
if browser_type == "chrome":
return self.create_chrome_driver(browser_options, proxy_config, detection_evasion)
elif browser_type == "firefox":
return self.create_firefox_driver(browser_options, proxy_config, detection_evasion)
else:
raise ValueError(f"Tipo de navegador no soportado: {browser_type}")
def login_with_retry(self, driver: WebDriver, url: str, max_retries: int = 3) -> bool:
"""Función de navegación con reintentos y comportamiento humano (del ejemplo del usuario)."""
for attempt in range(max_retries):
try:
# Simular movimiento de mouse antes de cargar la página
driver.execute_script("""
let event = new MouseEvent('mousemove', {
'view': window,
'bubbles': true,
'cancelable': true,
'clientX': Math.random() * window.innerWidth,
'clientY': Math.random() * window.innerHeight
});
document.dispatchEvent(event);
""")
# Agregar delay aleatorio antes de cargar la página
time.sleep(random.uniform(2, 5))
driver.get(url)
# Simular scroll aleatorio
scroll_amount = random.randint(100, 300)
driver.execute_script(f"window.scrollBy(0, {scroll_amount})")
time.sleep(random.uniform(1, 3))
return True
except Exception as e:
print(f"Intento {attempt + 1} fallido: {str(e)}")
if attempt < max_retries - 1:
# Espera exponencial entre reintentos
time.sleep((attempt + 1) * random.uniform(5, 10))
continue
return False
return False