413 Zeilen
14 KiB
Python
413 Zeilen
14 KiB
Python
# Path: p:/Chimaira/Code-Playwright/utils/proxy_rotator.py
|
|
|
|
"""
|
|
Proxy-Rotations- und Verwaltungsfunktionalität.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import random
|
|
import logging
|
|
import requests
|
|
import time
|
|
from typing import Dict, List, Any, Optional, Tuple, Union
|
|
|
|
logger = logging.getLogger("proxy_rotator")
|
|
|
|
class ProxyRotator:
|
|
"""Klasse zur Verwaltung und Rotation von Proxies."""
|
|
|
|
CONFIG_FILE = os.path.join("config", "proxies.json")
|
|
|
|
def __init__(self):
|
|
"""Initialisiert den ProxyRotator und lädt die Konfiguration."""
|
|
self.config = self.load_config()
|
|
self.current_proxy = None
|
|
self.last_rotation_time = 0
|
|
|
|
# Stelle sicher, dass das Konfigurationsverzeichnis existiert
|
|
os.makedirs(os.path.dirname(self.CONFIG_FILE), exist_ok=True)
|
|
|
|
def load_config(self) -> Dict[str, Any]:
|
|
"""Lädt die Proxy-Konfiguration aus der Konfigurationsdatei."""
|
|
if not os.path.exists(self.CONFIG_FILE):
|
|
return {
|
|
"ipv4": [],
|
|
"ipv6": [],
|
|
"mobile": [],
|
|
"mobile_api": {
|
|
"marsproxies": "",
|
|
"iproyal": ""
|
|
},
|
|
"rotation_interval": 300 # 5 Minuten
|
|
}
|
|
|
|
try:
|
|
with open(self.CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
config = json.load(f)
|
|
|
|
logger.info(f"Proxy-Konfiguration geladen: {len(config.get('ipv4', []))} IPv4, "
|
|
f"{len(config.get('ipv6', []))} IPv6, {len(config.get('mobile', []))} Mobile")
|
|
|
|
return config
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Laden der Proxy-Konfiguration: {e}")
|
|
return {
|
|
"ipv4": [],
|
|
"ipv6": [],
|
|
"mobile": [],
|
|
"mobile_api": {
|
|
"marsproxies": "",
|
|
"iproyal": ""
|
|
},
|
|
"rotation_interval": 300
|
|
}
|
|
|
|
def save_config(self) -> bool:
|
|
"""Speichert die Proxy-Konfiguration in die Konfigurationsdatei."""
|
|
try:
|
|
with open(self.CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(self.config, f, indent=2)
|
|
|
|
logger.info("Proxy-Konfiguration gespeichert")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Speichern der Proxy-Konfiguration: {e}")
|
|
return False
|
|
|
|
def get_config(self) -> Dict[str, Any]:
|
|
"""Gibt die aktuelle Konfiguration zurück."""
|
|
return self.config
|
|
|
|
def update_config(self, new_config: Dict[str, Any]) -> bool:
|
|
"""Aktualisiert die Konfiguration mit den neuen Werten."""
|
|
try:
|
|
# Aktualisiere nur die bereitgestellten Schlüssel
|
|
for key, value in new_config.items():
|
|
self.config[key] = value
|
|
|
|
# Konfiguration speichern
|
|
return self.save_config()
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Aktualisieren der Proxy-Konfiguration: {e}")
|
|
return False
|
|
|
|
def get_proxies_by_type(self, proxy_type: str) -> List[str]:
|
|
"""Gibt eine Liste von Proxies des angegebenen Typs zurück."""
|
|
if proxy_type.lower() not in ["ipv4", "ipv6", "mobile"]:
|
|
logger.warning(f"Ungültiger Proxy-Typ: {proxy_type}")
|
|
return []
|
|
|
|
return self.config.get(proxy_type.lower(), [])
|
|
|
|
def get_random_proxy(self, proxy_type: str) -> Optional[str]:
|
|
"""Gibt einen zufälligen Proxy des angegebenen Typs zurück."""
|
|
proxies = self.get_proxies_by_type(proxy_type)
|
|
|
|
if not proxies:
|
|
logger.warning(f"Keine Proxies vom Typ '{proxy_type}' verfügbar")
|
|
return None
|
|
|
|
return random.choice(proxies)
|
|
|
|
def get_proxy(self, proxy_type=None):
|
|
"""
|
|
Gibt eine Proxy-Konfiguration für den angegebenen Typ zurück.
|
|
|
|
Args:
|
|
proxy_type: Proxy-Typ ("ipv4", "ipv6", "mobile") oder None für zufälligen Typ
|
|
|
|
Returns:
|
|
Dict mit Proxy-Konfiguration oder None, wenn kein Proxy verfügbar ist
|
|
"""
|
|
try:
|
|
# Wenn kein Proxy-Typ angegeben ist, einen zufälligen verwenden
|
|
if proxy_type is None:
|
|
available_types = []
|
|
if self.config.get("ipv4"):
|
|
available_types.append("ipv4")
|
|
if self.config.get("ipv6"):
|
|
available_types.append("ipv6")
|
|
if self.config.get("mobile"):
|
|
available_types.append("mobile")
|
|
|
|
if not available_types:
|
|
logger.warning("Keine Proxies verfügbar")
|
|
return None
|
|
|
|
proxy_type = random.choice(available_types)
|
|
|
|
# Proxy vom angegebenen Typ holen
|
|
proxy_list = self.get_proxies_by_type(proxy_type)
|
|
|
|
if not proxy_list:
|
|
logger.warning(f"Keine Proxies vom Typ '{proxy_type}' verfügbar")
|
|
return None
|
|
|
|
# Zufälligen Proxy aus der Liste auswählen
|
|
proxy = random.choice(proxy_list)
|
|
|
|
# Proxy-URL parsen
|
|
parts = proxy.split(":")
|
|
|
|
if len(parts) >= 4:
|
|
# Format: host:port:username:password
|
|
host, port, username, password = parts[:4]
|
|
|
|
return {
|
|
"server": f"http://{host}:{port}",
|
|
"username": username,
|
|
"password": password
|
|
}
|
|
elif len(parts) >= 2:
|
|
# Format: host:port
|
|
host, port = parts[:2]
|
|
|
|
return {
|
|
"server": f"http://{host}:{port}"
|
|
}
|
|
else:
|
|
logger.warning(f"Ungültiges Proxy-Format: {self.mask_proxy_credentials(proxy)}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Abrufen des Proxys: {e}")
|
|
return None
|
|
|
|
def get_next_proxy(self, proxy_type: str, force_rotation: bool = False) -> Optional[str]:
|
|
"""
|
|
Gibt den nächsten zu verwendenden Proxy zurück, unter Berücksichtigung des Rotationsintervalls.
|
|
|
|
Args:
|
|
proxy_type: Typ des Proxys (ipv4, ipv6, mobile)
|
|
force_rotation: Erzwingt eine Rotation, unabhängig vom Zeitintervall
|
|
|
|
Returns:
|
|
Proxy-String oder None, wenn kein Proxy verfügbar ist
|
|
"""
|
|
current_time = time.time()
|
|
interval = self.config.get("rotation_interval", 300) # Standardintervall: 5 Minuten
|
|
|
|
# Rotation durchführen, wenn das Intervall abgelaufen ist oder erzwungen wird
|
|
if force_rotation or self.current_proxy is None or (current_time - self.last_rotation_time) > interval:
|
|
self.current_proxy = self.get_random_proxy(proxy_type)
|
|
self.last_rotation_time = current_time
|
|
|
|
if self.current_proxy:
|
|
logger.info(f"Proxy rotiert zu: {self.mask_proxy_credentials(self.current_proxy)}")
|
|
|
|
return self.current_proxy
|
|
|
|
def test_proxy(self, proxy_type: str) -> Dict[str, Any]:
|
|
"""
|
|
Testet einen Proxy des angegebenen Typs.
|
|
|
|
Args:
|
|
proxy_type: Typ des zu testenden Proxys
|
|
|
|
Returns:
|
|
Dictionary mit Testergebnissen
|
|
"""
|
|
proxy = self.get_random_proxy(proxy_type)
|
|
|
|
if not proxy:
|
|
return {
|
|
"success": False,
|
|
"error": f"Keine Proxies vom Typ '{proxy_type}' verfügbar"
|
|
}
|
|
|
|
try:
|
|
# Proxy-URL parsen
|
|
parts = proxy.split(":")
|
|
|
|
if len(parts) >= 4:
|
|
# Format: host:port:username:password
|
|
host, port, username, password = parts[:4]
|
|
proxy_url = f"http://{username}:{password}@{host}:{port}"
|
|
elif len(parts) >= 2:
|
|
# Format: host:port
|
|
host, port = parts[:2]
|
|
proxy_url = f"http://{host}:{port}"
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"error": f"Ungültiges Proxy-Format: {self.mask_proxy_credentials(proxy)}"
|
|
}
|
|
|
|
# Proxy-Konfiguration für requests
|
|
proxies = {
|
|
"http": proxy_url,
|
|
"https": proxy_url
|
|
}
|
|
|
|
# Startzeit für Antwortzeit-Messung
|
|
start_time = time.time()
|
|
|
|
# Test-Anfrage über den Proxy
|
|
response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=10)
|
|
|
|
# Antwortzeit berechnen
|
|
response_time = time.time() - start_time
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
ip = data.get("ip", "Unbekannt")
|
|
|
|
# Länderinformationen abrufen (optional)
|
|
country = self.get_country_for_ip(ip)
|
|
|
|
return {
|
|
"success": True,
|
|
"ip": ip,
|
|
"country": country,
|
|
"response_time": response_time,
|
|
"proxy_type": proxy_type
|
|
}
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"error": f"Ungültige Antwort: HTTP {response.status_code}"
|
|
}
|
|
|
|
except requests.exceptions.Timeout:
|
|
return {
|
|
"success": False,
|
|
"error": "Zeitüberschreitung bei der Verbindung"
|
|
}
|
|
except requests.exceptions.ProxyError:
|
|
return {
|
|
"success": False,
|
|
"error": "Proxy-Fehler: Verbindung abgelehnt oder fehlgeschlagen"
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Testen des Proxys: {e}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
def get_country_for_ip(self, ip: str) -> Optional[str]:
|
|
"""
|
|
Ermittelt das Land für eine IP-Adresse.
|
|
|
|
Args:
|
|
ip: IP-Adresse
|
|
|
|
Returns:
|
|
Ländername oder None im Fehlerfall
|
|
"""
|
|
try:
|
|
response = requests.get(f"https://ipapi.co/{ip}/json/", timeout=5)
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
return data.get("country_name")
|
|
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
def mask_proxy_credentials(self, proxy: str) -> str:
|
|
"""
|
|
Maskiert die Anmeldeinformationen in einem Proxy-String für die Protokollierung.
|
|
|
|
Args:
|
|
proxy: Original-Proxy-String
|
|
|
|
Returns:
|
|
Maskierter Proxy-String
|
|
"""
|
|
parts = proxy.split(":")
|
|
|
|
if len(parts) >= 4:
|
|
# Format: host:port:username:password
|
|
host, port = parts[0], parts[1]
|
|
return f"{host}:{port}:***:***"
|
|
|
|
return proxy
|
|
|
|
def add_proxy(self, proxy: str, proxy_type: str) -> bool:
|
|
"""
|
|
Fügt einen neuen Proxy zur Konfiguration hinzu.
|
|
|
|
Args:
|
|
proxy: Proxy-String im Format host:port:username:password
|
|
proxy_type: Typ des Proxys (ipv4, ipv6, mobile)
|
|
|
|
Returns:
|
|
True bei Erfolg, False bei Fehler
|
|
"""
|
|
if proxy_type.lower() not in ["ipv4", "ipv6", "mobile"]:
|
|
logger.warning(f"Ungültiger Proxy-Typ: {proxy_type}")
|
|
return False
|
|
|
|
proxy_list = self.config.get(proxy_type.lower(), [])
|
|
|
|
if proxy not in proxy_list:
|
|
proxy_list.append(proxy)
|
|
self.config[proxy_type.lower()] = proxy_list
|
|
self.save_config()
|
|
|
|
logger.info(f"Proxy hinzugefügt: {self.mask_proxy_credentials(proxy)} (Typ: {proxy_type})")
|
|
return True
|
|
|
|
return False
|
|
|
|
def remove_proxy(self, proxy: str, proxy_type: str) -> bool:
|
|
"""
|
|
Entfernt einen Proxy aus der Konfiguration.
|
|
|
|
Args:
|
|
proxy: Proxy-String
|
|
proxy_type: Typ des Proxys (ipv4, ipv6, mobile)
|
|
|
|
Returns:
|
|
True bei Erfolg, False bei Fehler
|
|
"""
|
|
if proxy_type.lower() not in ["ipv4", "ipv6", "mobile"]:
|
|
logger.warning(f"Ungültiger Proxy-Typ: {proxy_type}")
|
|
return False
|
|
|
|
proxy_list = self.config.get(proxy_type.lower(), [])
|
|
|
|
if proxy in proxy_list:
|
|
proxy_list.remove(proxy)
|
|
self.config[proxy_type.lower()] = proxy_list
|
|
self.save_config()
|
|
|
|
logger.info(f"Proxy entfernt: {self.mask_proxy_credentials(proxy)} (Typ: {proxy_type})")
|
|
return True
|
|
|
|
return False
|
|
|
|
def format_proxy_for_playwright(self, proxy: str) -> Dict[str, str]:
|
|
"""
|
|
Formatiert einen Proxy-String für die Verwendung mit Playwright.
|
|
|
|
Args:
|
|
proxy: Proxy-String im Format host:port:username:password
|
|
|
|
Returns:
|
|
Dictionary mit Playwright-Proxy-Konfiguration
|
|
"""
|
|
parts = proxy.split(":")
|
|
|
|
if len(parts) >= 4:
|
|
# Format: host:port:username:password
|
|
host, port, username, password = parts[:4]
|
|
|
|
return {
|
|
"server": f"{host}:{port}",
|
|
"username": username,
|
|
"password": password
|
|
}
|
|
elif len(parts) >= 2:
|
|
# Format: host:port
|
|
host, port = parts[:2]
|
|
|
|
return {
|
|
"server": f"{host}:{port}"
|
|
}
|
|
else:
|
|
logger.warning(f"Ungültiges Proxy-Format: {self.mask_proxy_credentials(proxy)}")
|
|
return {} |