Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-03 21:11:05 +02:00
Commit 08ed938105
239 geänderte Dateien mit 21554 neuen und 0 gelöschten Zeilen

0
browser/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,721 @@
# browser/fingerprint_protection.py
"""
Schutz vor Browser-Fingerprinting - Erweiterte Methoden zum Schutz vor verschiedenen Fingerprinting-Techniken
"""
import random
import logging
import json
from typing import Dict, List, Any, Optional, Tuple
logger = logging.getLogger("fingerprint_protection")
class FingerprintProtection:
"""
Bietet erweiterte Schutzmaßnahmen gegen verschiedene Browser-Fingerprinting-Techniken.
Kann mit dem PlaywrightManager integriert werden, um die Anonymität zu verbessern.
"""
def __init__(self, context=None, stealth_config=None):
"""
Initialisiert den Fingerprint-Schutz.
Args:
context: Der Browser-Kontext, auf den die Schutzmaßnahmen angewendet werden sollen
stealth_config: Konfigurationseinstellungen für das Stealth-Verhalten
"""
self.context = context
self.stealth_config = stealth_config or {}
self.scripts = []
self.noise_level = self.stealth_config.get("noise_level", 0.5) # 0.0-1.0
# Standardwerte für Fingerprinting-Schutz
self.defaults = {
"webgl_vendor": "Google Inc. (Intel)",
"webgl_renderer": "Intel Iris OpenGL Engine",
"canvas_noise": True,
"audio_noise": True,
"webgl_noise": True,
"hardware_concurrency": 8,
"device_memory": 8,
"timezone_id": "Europe/Berlin"
}
# Einstellungen mit benutzerdefinierten Werten überschreiben
for key, value in self.stealth_config.items():
if key in self.defaults:
self.defaults[key] = value
# Schutzmaßnahmen initialisieren
self._init_protections()
def set_context(self, context):
"""Setzt den Browser-Kontext nach der Initialisierung."""
self.context = context
def _init_protections(self):
"""Initialisiert alle Fingerprint-Schutzmaßnahmen."""
self._init_canvas_protection()
self._init_webgl_protection()
self._init_audio_protection()
self._init_navigator_protection()
self._init_misc_protections()
def _init_canvas_protection(self):
"""
Initialisiert den Schutz gegen Canvas-Fingerprinting.
Dies modifiziert das Canvas-Element, um leicht abweichende Werte zurückzugeben.
"""
script = """
() => {
// Originalmethoden speichern
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
// Funktion zum Hinzufügen von Rauschen zu Bilddaten
const addNoise = (data, noise) => {
const noiseFactor = noise || 0.03; // Standardwert für das Rauschlevel
// Nur auf Canvas über Mindestgröße anwenden, um normale Canvas nicht zu stören
if (data.width > 16 && data.height > 16) {
const pixelCount = data.width * data.height * 4; // RGBA-Kanäle
// Präzise, aber wenig wahrnehmbare Änderungen
const minPixels = Math.max(10, Math.floor(pixelCount * 0.005)); // Mindestens 10 Pixel
const maxPixels = Math.min(100, Math.floor(pixelCount * 0.01)); // Höchstens 100 Pixel
// Zufällige Anzahl an Pixeln auswählen
const pixelsToModify = Math.floor(Math.random() * (maxPixels - minPixels)) + minPixels;
// Pixel modifizieren
for (let i = 0; i < pixelsToModify; i++) {
// Zufälligen Pixel auswählen
const offset = Math.floor(Math.random() * pixelCount / 4) * 4;
// Nur RGB modifizieren, Alpha-Kanal unverändert lassen
for (let j = 0; j < 3; j++) {
// Subtile Änderungen hinzufügen (±1-2)
const mod = Math.floor(Math.random() * 3) - 1;
data.data[offset + j] = Math.max(0, Math.min(255, data.data[offset + j] + mod));
}
}
}
return data;
};
// getImageData überschreiben
CanvasRenderingContext2D.prototype.getImageData = function() {
const imageData = originalGetImageData.apply(this, arguments);
return addNoise(imageData, NOISE_LEVEL);
};
// toDataURL überschreiben
HTMLCanvasElement.prototype.toDataURL = function() {
// Temporäre Modifikation für getImageData
const tempGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = originalGetImageData;
// toDataURL aufrufen
let dataURL = originalToDataURL.apply(this, arguments);
// Wenn das Canvas groß genug ist und nicht von kritischen Anwendungen verwendet wird
if (this.width > 16 && this.height > 16 && !this.hasAttribute('data-fingerprint-protect-ignore')) {
// Rauschen in binären Teil des DataURL einfügen
const parts = dataURL.split(',');
if (parts.length === 2) {
// Binäre Daten dekodieren
const binary = atob(parts[1]);
// Geringfügige Änderungen bei etwa 0,1% der Bytes
let modifiedBinary = '';
for (let i = 0; i < binary.length; i++) {
if (Math.random() < 0.001 * NOISE_LEVEL) {
// Byte leicht ändern
const byte = binary.charCodeAt(i);
const mod = Math.floor(Math.random() * 3) - 1;
modifiedBinary += String.fromCharCode(Math.max(0, Math.min(255, byte + mod)));
} else {
modifiedBinary += binary[i];
}
}
// Binäre Daten wieder kodieren
dataURL = parts[0] + ',' + btoa(modifiedBinary);
}
}
// getImageData wiederherstellen
CanvasRenderingContext2D.prototype.getImageData = tempGetImageData;
return dataURL;
};
// toBlob überschreiben
HTMLCanvasElement.prototype.toBlob = function(callback) {
// Original toBlob aufrufen
originalToBlob.apply(this, [function(blob) {
// Wenn das Canvas groß genug ist und nicht von kritischen Anwendungen verwendet wird
if (this.width > 16 && this.height > 16 && !this.hasAttribute('data-fingerprint-protect-ignore')) {
// Blob zu ArrayBuffer konvertieren
const reader = new FileReader();
reader.onload = function() {
const arrayBuffer = reader.result;
const array = new Uint8Array(arrayBuffer);
// Geringfügige Änderungen bei etwa 0,1% der Bytes
for (let i = 0; i < array.length; i++) {
if (Math.random() < 0.001 * NOISE_LEVEL) {
// Byte leicht ändern
const mod = Math.floor(Math.random() * 3) - 1;
array[i] = Math.max(0, Math.min(255, array[i] + mod));
}
}
// Neuen Blob erstellen
const modifiedBlob = new Blob([array], {type: blob.type});
callback(modifiedBlob);
};
reader.readAsArrayBuffer(blob);
} else {
// Unveränderten Blob zurückgeben
callback(blob);
}
}.bind(this)]);
};
}
""".replace("NOISE_LEVEL", str(self.noise_level))
self.scripts.append(script)
def _init_webgl_protection(self):
"""
Initialisiert den Schutz gegen WebGL-Fingerprinting.
Dies modifiziert WebGL-spezifische Werte, die für Fingerprinting verwendet werden.
"""
webgl_vendor = self.defaults["webgl_vendor"]
webgl_renderer = self.defaults["webgl_renderer"]
script = f"""
() => {{
// WebGL Vendor und Renderer spoofen
const getParameterProxies = [
WebGLRenderingContext.prototype,
WebGL2RenderingContext.prototype
];
getParameterProxies.forEach(contextPrototype => {{
if (!contextPrototype) return;
const originalGetParameter = contextPrototype.getParameter;
contextPrototype.getParameter = function(parameter) {{
// WebGL Vendor (VENDOR)
if (parameter === 0x1F00) {{
return "{webgl_vendor}";
}}
// WebGL Renderer (RENDERER)
if (parameter === 0x1F01) {{
return "{webgl_renderer}";
}}
// Unshaded Language Version (SHADING_LANGUAGE_VERSION)
if (parameter === 0x8B8C) {{
const originalValue = originalGetParameter.call(this, parameter);
// Subtile Änderungen an der Version
const versionMatch = originalValue.match(/^WebGL GLSL ES ([0-9]\\.[0-9][0-9])/);
if (versionMatch) {{
return originalValue.replace(versionMatch[1],
(parseFloat(versionMatch[1]) + (Math.random() * 0.01 - 0.005)).toFixed(2));
}}
}}
// VERSION
if (parameter === 0x1F02) {{
const originalValue = originalGetParameter.call(this, parameter);
// Subtile Änderungen an der Version
const versionMatch = originalValue.match(/^WebGL ([0-9]\\.[0-9])/);
if (versionMatch) {{
return originalValue.replace(versionMatch[1],
(parseFloat(versionMatch[1]) + (Math.random() * 0.01 - 0.005)).toFixed(1));
}}
}}
return originalGetParameter.apply(this, arguments);
}};
}});
// WebGL Vertex und Fragment Shader spoofen
const shaderSourceProxies = [
WebGLRenderingContext.prototype,
WebGL2RenderingContext.prototype
];
shaderSourceProxies.forEach(contextPrototype => {{
if (!contextPrototype) return;
const originalShaderSource = contextPrototype.shaderSource;
contextPrototype.shaderSource = function(shader, source) {{
// Füge geringfügige Unterschiede in Kommentaren ein, ohne die Funktionalität zu beeinträchtigen
if (source.indexOf('//') !== -1) {{
// Zufälligen Kommentar leicht modifizieren
source = source.replace(/\\/\\/(.*?)\\n/g, (match, comment) => {{
if (Math.random() < 0.1) {{
// Füge ein Leerzeichen oder einen Bindestrich hinzu oder entferne eines
const modifications = [
' ', '-', '', ' '
];
const mod = modifications[Math.floor(Math.random() * modifications.length)];
return `//` + comment + mod + `\\n`;
}}
return match;
}});
}}
// Ersetze bestimmte Whitespace-Muster
source = source.replace(/\\s{2,}/g, match => {{
if (Math.random() < 0.05) {{
return ' '.repeat(match.length + (Math.random() < 0.5 ? 1 : -1));
}}
return match;
}});
return originalShaderSource.call(this, shader, source);
}};
}});
// Canvas-Kontext spoofen
const getContextProxies = [
HTMLCanvasElement.prototype
];
getContextProxies.forEach(canvasPrototype => {{
const originalGetContext = canvasPrototype.getContext;
canvasPrototype.getContext = function(contextType, contextAttributes) {{
const context = originalGetContext.apply(this, arguments);
if (context && (contextType === 'webgl' || contextType === 'experimental-webgl' || contextType === 'webgl2')) {{
// Zufällige Werte für verschiedene Parameter einführen
if ({str(self.defaults["webgl_noise"]).lower()}) {{
// Zufällige Modifikation für MAX_VERTEX_UNIFORM_VECTORS
const MAX_VERTEX_UNIFORM_VECTORS = context.getParameter(context.MAX_VERTEX_UNIFORM_VECTORS);
Object.defineProperty(context, 'MAX_VERTEX_UNIFORM_VECTORS', {{
get: () => MAX_VERTEX_UNIFORM_VECTORS + Math.floor(Math.random() * 3) - 1
}});
// Zufällige Modifikation für MAX_FRAGMENT_UNIFORM_VECTORS
const MAX_FRAGMENT_UNIFORM_VECTORS = context.getParameter(context.MAX_FRAGMENT_UNIFORM_VECTORS);
Object.defineProperty(context, 'MAX_FRAGMENT_UNIFORM_VECTORS', {{
get: () => MAX_FRAGMENT_UNIFORM_VECTORS + Math.floor(Math.random() * 3) - 1
}});
}}
}}
return context;
}};
}});
}}
"""
self.scripts.append(script)
def _init_audio_protection(self):
"""
Initialisiert den Schutz gegen Audio-Fingerprinting.
Dies modifiziert die Audio-API-Funktionen, die für Fingerprinting verwendet werden.
"""
script = f"""
() => {{
// Audio-Kontext spoofen
if (window.AudioContext || window.webkitAudioContext) {{
const AudioContextProxy = window.AudioContext || window.webkitAudioContext;
const originalAudioContext = AudioContextProxy;
// AudioContext überschreiben
window.AudioContext = window.webkitAudioContext = function() {{
const context = new originalAudioContext();
// createOscillator überschreiben
const originalCreateOscillator = context.createOscillator;
context.createOscillator = function() {{
const oscillator = originalCreateOscillator.apply(this, arguments);
// Frequenz leicht modifizieren
const originalFrequency = oscillator.frequency;
Object.defineProperty(oscillator, 'frequency', {{
get: function() {{
return originalFrequency;
}},
set: function(value) {{
if (typeof value === 'number') {{
// Leichte Änderung hinzufügen
const noise = (Math.random() * 0.02 - 0.01) * value;
originalFrequency.value = value + noise;
}} else {{
originalFrequency.value = value;
}}
}}
}});
return oscillator;
}};
// getChannelData überschreiben
const originalGetChannelData = context.createBuffer.prototype?.getChannelData || OfflineAudioContext.prototype?.getChannelData;
if (originalGetChannelData) {{
context.createBuffer.prototype.getChannelData = function(channel) {{
const array = originalGetChannelData.call(this, channel);
if ({str(self.defaults["audio_noise"]).lower()} && this.length > 20) {{
// Sehr subtiles Rauschen hinzufügen (bei etwa 0,1% der Samples)
const noise = {self.noise_level} * 0.0001;
// Effiziente Implementierung
const samples = Math.min(200, Math.floor(array.length * 0.001));
for (let i = 0; i < samples; i++) {{
const idx = Math.floor(Math.random() * array.length);
array[idx] += (Math.random() * 2 - 1) * noise;
}}
}}
return array;
}};
}}
// AnalyserNode.getFloatFrequencyData überschreiben
if (context.createAnalyser) {{
const originalCreateAnalyser = context.createAnalyser;
context.createAnalyser = function() {{
const analyser = originalCreateAnalyser.apply(this, arguments);
const originalGetFloatFrequencyData = analyser.getFloatFrequencyData;
analyser.getFloatFrequencyData = function(array) {{
originalGetFloatFrequencyData.call(this, array);
if ({str(self.defaults["audio_noise"]).lower()} && array.length > 20) {{
// Sehr subtiles Rauschen hinzufügen
const noise = {self.noise_level} * 0.001;
// Effiziente Implementierung für große Arrays
const samples = Math.min(20, Math.floor(array.length * 0.01));
for (let i = 0; i < samples; i++) {{
const idx = Math.floor(Math.random() * array.length);
array[idx] += (Math.random() * 2 - 1) * noise;
}}
}}
return array;
}};
return analyser;
}};
}}
return context;
}};
}}
}}
"""
self.scripts.append(script)
def _init_navigator_protection(self):
"""
Initialisiert den Schutz gegen Navigator-Objekt-Fingerprinting.
Dies modifiziert verschiedene Navigator-Eigenschaften, die für Fingerprinting verwendet werden.
"""
hardware_concurrency = self.defaults["hardware_concurrency"]
device_memory = self.defaults["device_memory"]
script = f"""
() => {{
// Navigator-Eigenschaften überschreiben
// hardwareConcurrency (CPU-Kerne)
if (navigator.hardwareConcurrency) {{
Object.defineProperty(navigator, 'hardwareConcurrency', {{
get: () => {hardware_concurrency}
}});
}}
// deviceMemory (RAM)
if (navigator.deviceMemory) {{
Object.defineProperty(navigator, 'deviceMemory', {{
get: () => {device_memory}
}});
}}
// language und languages
if (navigator.language) {{
const originalLanguage = navigator.language;
const originalLanguages = navigator.languages;
Object.defineProperty(navigator, 'language', {{
get: () => originalLanguage
}});
Object.defineProperty(navigator, 'languages', {{
get: () => originalLanguages
}});
}}
// userAgent-Konsistenz sicherstellen
if (navigator.userAgent) {{
const userAgent = navigator.userAgent;
// Wenn der userAgent bereits überschrieben wurde, stellen wir
// sicher, dass die appVersion und platform konsistent sind
const browserInfo = {{
chrome: /Chrome\\/(\\d+)/.exec(userAgent),
firefox: /Firefox\\/(\\d+)/.exec(userAgent),
safari: /Safari\\/(\\d+)/.exec(userAgent),
edge: /Edg(e|)\\/(\\d+)/.exec(userAgent)
}};
// Platform basierend auf userAgent bestimmen
let platform = '{self.defaults.get("platform", "Win32")}';
if (/Windows/.test(userAgent)) platform = 'Win32';
else if (/Macintosh/.test(userAgent)) platform = 'MacIntel';
else if (/Linux/.test(userAgent)) platform = 'Linux x86_64';
else if (/Android/.test(userAgent)) platform = 'Linux armv8l';
else if (/iPhone|iPad/.test(userAgent)) platform = 'iPhone';
Object.defineProperty(navigator, 'platform', {{
get: () => platform
}});
// appVersion konsistent machen
if (navigator.appVersion) {{
Object.defineProperty(navigator, 'appVersion', {{
get: () => userAgent.substring(8)
}});
}}
// vendor basierend auf Browser setzen
let vendor = '{self.defaults.get("vendor", "Google Inc.")}';
if (browserInfo.safari) vendor = 'Apple Computer, Inc.';
else if (browserInfo.firefox) vendor = '';
Object.defineProperty(navigator, 'vendor', {{
get: () => vendor
}});
}}
"""
self.scripts.append(script)
def _init_misc_protections(self):
"""
Initialisiert verschiedene weitere Schutzmaßnahmen gegen Fingerprinting.
"""
timezone_id = self.defaults["timezone_id"]
script = f"""
() => {{
// Date.prototype.getTimezoneOffset überschreiben
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
Date.prototype.getTimezoneOffset = function() {{
// Zeitzonendaten für '{timezone_id}'
// Mitteleuropäische Zeit (CET/CEST): UTC+1 / UTC+2 (Sommerzeit)
const date = new Date(this);
// Prüfen, ob Sommerzeit
const jan = new Date(date.getFullYear(), 0, 1);
const jul = new Date(date.getFullYear(), 6, 1);
const standardOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
// Sommerzeit in Europa: Ende März bis Ende Oktober
const isDST = date.getMonth() > 2 && date.getMonth() < 10;
// Offset in Minuten: CET = -60, CEST = -120
return isDST ? -120 : -60;
}};
// Plugins und MimeTypes Schutz
if (navigator.plugins) {{
// Leere oder gefälschte Plugins
Object.defineProperty(navigator, 'plugins', {{
get: () => {{
// Plugins-Array-Eigenschaften simulieren
const plugins = {{
length: 0,
item: () => null,
namedItem: () => null,
refresh: () => {{}},
[Symbol.iterator]: function* () {{}}
}};
return plugins;
}}
}});
// MimeTypes ebenfalls leeren
if (navigator.mimeTypes) {{
Object.defineProperty(navigator, 'mimeTypes', {{
get: () => {{
// MimeTypes-Array-Eigenschaften simulieren
const mimeTypes = {{
length: 0,
item: () => null,
namedItem: () => null,
[Symbol.iterator]: function* () {{}}
}};
return mimeTypes;
}}
}});
}}
}}
// Performance.now() und Date.now() - Schutz gegen Timing-Angriffe
if (window.performance && performance.now) {{
const originalNow = performance.now;
performance.now = function() {{
const value = originalNow.call(performance);
// Subtile Abweichung hinzufügen
return value + (Math.random() * 0.01);
}};
}}
// Date.now() ebenfalls mit subtiler Abweichung
const originalDateNow = Date.now;
Date.now = function() {{
const value = originalDateNow.call(Date);
// Subtile Abweichung hinzufügen (±1ms)
return value + (Math.random() < 0.5 ? 1 : 0);
}};
// screen-Eigenschaften konsistent machen
if (window.screen) {{
const originalWidth = screen.width;
const originalHeight = screen.height;
const originalColorDepth = screen.colorDepth;
const originalPixelDepth = screen.pixelDepth;
// Abweichungen verhindern - konsistente Werte liefern
Object.defineProperties(screen, {{
'width': {{ get: () => originalWidth }},
'height': {{ get: () => originalHeight }},
'availWidth': {{ get: () => originalWidth }},
'availHeight': {{ get: () => originalHeight - 40 }}, // Taskleiste simulieren
'colorDepth': {{ get: () => originalColorDepth }},
'pixelDepth': {{ get: () => originalPixelDepth }}
}});
}}
}}
"""
self.scripts.append(script)
def apply_to_context(self, context=None):
"""
Wendet alle Skripte auf den Browser-Kontext an.
Args:
context: Der Browser-Kontext, falls er noch nicht gesetzt wurde
"""
if context:
self.context = context
if not self.context:
logger.warning("Kein Browser-Kontext zum Anwenden der Fingerprint-Schutzmaßnahmen")
return
for script in self.scripts:
self.context.add_init_script(script)
logger.info(f"Fingerprint-Schutzmaßnahmen auf Browser-Kontext angewendet ({len(self.scripts)} Skripte)")
def get_fingerprint_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status der Fingerprint-Schutzmaßnahmen zurück.
Returns:
Dict[str, Any]: Status der Fingerprint-Schutzmaßnahmen
"""
status = {
"active": self.context is not None,
"script_count": len(self.scripts),
"protections": {
"canvas": self.defaults["canvas_noise"],
"webgl": self.defaults["webgl_noise"],
"audio": self.defaults["audio_noise"],
"navigator": True,
"battery": "getBattery" in self.scripts[-1],
"timing": "performance.now" in self.scripts[-1]
},
"noise_level": self.noise_level,
"custom_values": {
"webgl_vendor": self.defaults["webgl_vendor"],
"webgl_renderer": self.defaults["webgl_renderer"],
"hardware_concurrency": self.defaults["hardware_concurrency"],
"device_memory": self.defaults["device_memory"],
"timezone_id": self.defaults["timezone_id"]
}
}
return status
def rotate_fingerprint(self, noise_level: Optional[float] = None):
"""
Rotiert den Fingerprint durch Neugenerierung der Schutzmaßnahmen.
Args:
noise_level: Optionales neues Rauschniveau (0.0-1.0)
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if noise_level is not None:
self.noise_level = max(0.0, min(1.0, noise_level))
try:
# Skripte zurücksetzen
self.scripts = []
# Neues WebGL-Vendor/Renderer-Paar generieren
webgl_vendors = [
"Google Inc.",
"Google Inc. (Intel)",
"Google Inc. (NVIDIA)",
"Google Inc. (AMD)",
"Intel Inc.",
"NVIDIA Corporation",
"AMD"
]
webgl_renderers = [
"ANGLE (Intel, Intel(R) HD Graphics 620 Direct3D11 vs_5_0 ps_5_0)",
"ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0)",
"ANGLE (AMD, AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0)",
"Intel Iris OpenGL Engine",
"NVIDIA GeForce GTX 980 OpenGL Engine",
"AMD Radeon Pro 560 OpenGL Engine",
"Mesa DRI Intel(R) UHD Graphics 620 (KBL GT2)",
"Mesa DRI NVIDIA GeForce GTX 1650"
]
# Zufällige Werte wählen
self.defaults["webgl_vendor"] = random.choice(webgl_vendors)
self.defaults["webgl_renderer"] = random.choice(webgl_renderers)
# Hardware-Concurrency und Device-Memory variieren
self.defaults["hardware_concurrency"] = random.choice([2, 4, 6, 8, 12, 16])
self.defaults["device_memory"] = random.choice([2, 4, 8, 16])
# Schutzmaßnahmen neu initialisieren
self._init_protections()
# Auf Kontext anwenden, falls vorhanden
if self.context:
self.apply_to_context()
logger.info(f"Fingerprint erfolgreich rotiert (Noise-Level: {self.noise_level:.2f})")
return True
except Exception as e:
logger.error(f"Fehler bei der Rotation des Fingerprints: {e}")
return False

Datei anzeigen

@ -0,0 +1,127 @@
# browser/playwright_extensions.py
"""
Erweiterungen für den PlaywrightManager - Fügt zusätzliche Funktionalität hinzu
"""
import logging
from typing import Dict, Any, Optional
from browser.fingerprint_protection import FingerprintProtection
logger = logging.getLogger("playwright_extensions")
class PlaywrightExtensions:
"""
Erweiterungsklasse für den PlaywrightManager.
Bietet zusätzliche Funktionalität, ohne die Hauptklasse zu verändern.
"""
def __init__(self, playwright_manager):
"""
Initialisiert die Erweiterungsklasse.
Args:
playwright_manager: Eine Instanz des PlaywrightManager
"""
self.playwright_manager = playwright_manager
self.fingerprint_protection = None
self.enhanced_stealth_enabled = False
def enable_enhanced_fingerprint_protection(self, config: Optional[Dict[str, Any]] = None) -> bool:
"""
Aktiviert den erweiterten Fingerprint-Schutz.
Args:
config: Optionale Konfiguration für den Fingerprint-Schutz
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Sicherstellen, dass der Browser gestartet wurde
if not hasattr(self.playwright_manager, 'context') or self.playwright_manager.context is None:
logger.warning("Browser muss zuerst gestartet werden, bevor der Fingerprint-Schutz aktiviert werden kann")
return False
# Basis-Stealth-Konfiguration aus dem PlaywrightManager verwenden
stealth_config = getattr(self.playwright_manager, 'stealth_config', {})
# Mit der benutzerdefinierten Konfiguration erweitern, falls vorhanden
if config:
stealth_config.update(config)
# Fingerprint-Schutz initialisieren
self.fingerprint_protection = FingerprintProtection(
context=self.playwright_manager.context,
stealth_config=stealth_config
)
# Schutzmaßnahmen auf den Kontext anwenden
self.fingerprint_protection.apply_to_context()
# Status aktualisieren
self.enhanced_stealth_enabled = True
logger.info("Erweiterter Fingerprint-Schutz aktiviert")
return True
except Exception as e:
logger.error(f"Fehler beim Aktivieren des erweiterten Fingerprint-Schutzes: {e}")
return False
def rotate_fingerprint(self, noise_level: Optional[float] = None) -> bool:
"""
Rotiert den Browser-Fingerprint.
Args:
noise_level: Optionales neues Rauschniveau (0.0-1.0)
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self.enhanced_stealth_enabled or self.fingerprint_protection is None:
logger.warning("Erweiterter Fingerprint-Schutz ist nicht aktiviert")
return False
return self.fingerprint_protection.rotate_fingerprint(noise_level)
def get_fingerprint_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status des Fingerprint-Schutzes zurück.
Returns:
Dict[str, Any]: Status des Fingerprint-Schutzes
"""
if not self.enhanced_stealth_enabled or self.fingerprint_protection is None:
return {"active": False, "message": "Erweiterter Fingerprint-Schutz ist nicht aktiviert"}
return self.fingerprint_protection.get_fingerprint_status()
def hook_into_playwright_manager(self) -> None:
"""
Hängt die Erweiterungsmethoden an den PlaywrightManager.
"""
if not self.playwright_manager:
logger.error("Kein PlaywrightManager zum Anhängen der Erweiterungen")
return
# Originalstart-Methode speichern
original_start = self.playwright_manager.start
# Die start-Methode überschreiben, um den Fingerprint-Schutz automatisch zu aktivieren
def enhanced_start(*args, **kwargs):
result = original_start(*args, **kwargs)
# Wenn start erfolgreich war und erweiterter Schutz aktiviert ist,
# wenden wir den Fingerprint-Schutz auf den neuen Kontext an
if result and self.enhanced_stealth_enabled and self.fingerprint_protection:
self.fingerprint_protection.set_context(self.playwright_manager.context)
self.fingerprint_protection.apply_to_context()
return result
# Methoden dynamisch zum PlaywrightManager hinzufügen
self.playwright_manager.enable_enhanced_fingerprint_protection = self.enable_enhanced_fingerprint_protection
self.playwright_manager.rotate_fingerprint = self.rotate_fingerprint
self.playwright_manager.get_fingerprint_status = self.get_fingerprint_status
self.playwright_manager.start = enhanced_start

517
browser/playwright_manager.py Normale Datei
Datei anzeigen

@ -0,0 +1,517 @@
"""
Playwright Manager - Hauptklasse für die Browser-Steuerung mit Anti-Bot-Erkennung
"""
import os
import json
import logging
import random
import time
from pathlib import Path
from typing import Dict, Optional, List, Any, Tuple
from playwright.sync_api import sync_playwright, Browser, Page, BrowserContext, ElementHandle
# Konfiguriere Logger
logger = logging.getLogger("playwright_manager")
class PlaywrightManager:
"""
Verwaltet Browser-Sitzungen mit Playwright, einschließlich Stealth-Modus und Proxy-Einstellungen.
"""
def __init__(self,
headless: bool = False,
proxy: Optional[Dict[str, str]] = None,
browser_type: str = "chromium",
user_agent: Optional[str] = None,
screenshots_dir: str = "screenshots",
slowmo: int = 0):
"""
Initialisiert den PlaywrightManager.
Args:
headless: Ob der Browser im Headless-Modus ausgeführt werden soll
proxy: Proxy-Konfiguration (z.B. {'server': 'http://myproxy.com:3128', 'username': 'user', 'password': 'pass'})
browser_type: Welcher Browser-Typ verwendet werden soll ("chromium", "firefox", oder "webkit")
user_agent: Benutzerdefinierter User-Agent
screenshots_dir: Verzeichnis für Screenshots
slowmo: Verzögerung zwischen Aktionen in Millisekunden (nützlich für Debugging)
"""
self.headless = headless
self.proxy = proxy
self.browser_type = browser_type
self.user_agent = user_agent
self.screenshots_dir = screenshots_dir
self.slowmo = slowmo
# Stelle sicher, dass das Screenshots-Verzeichnis existiert
os.makedirs(self.screenshots_dir, exist_ok=True)
# Playwright-Instanzen
self.playwright = None
self.browser = None
self.context = None
self.page = None
# Zähler für Wiederhholungsversuche
self.retry_counter = {}
# Lade Stealth-Konfigurationen
self.stealth_config = self._load_stealth_config()
def _load_stealth_config(self) -> Dict[str, Any]:
"""Lädt die Stealth-Konfigurationen aus der Datei oder verwendet Standardwerte."""
try:
config_dir = Path(__file__).parent.parent / "config"
stealth_config_path = config_dir / "stealth_config.json"
if stealth_config_path.exists():
with open(stealth_config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.warning(f"Konnte Stealth-Konfiguration nicht laden: {e}")
# Verwende Standardwerte, wenn das Laden fehlschlägt
return {
"vendor": "Google Inc.",
"platform": "Win32",
"webdriver": False,
"accept_language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
"timezone_id": "Europe/Berlin",
"fingerprint_noise": True,
"device_scale_factor": 1.0,
}
def start(self) -> Page:
"""
Startet die Playwright-Sitzung und gibt die Browser-Seite zurück.
Returns:
Page: Die Browser-Seite
"""
if self.page is not None:
return self.page
try:
self.playwright = sync_playwright().start()
# Wähle den Browser-Typ
if self.browser_type == "firefox":
browser_instance = self.playwright.firefox
elif self.browser_type == "webkit":
browser_instance = self.playwright.webkit
else:
browser_instance = self.playwright.chromium
# Browser-Startoptionen
browser_args = []
if self.browser_type == "chromium":
# Chrome-spezifische Argumente für Anti-Bot-Erkennung
browser_args.extend([
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins,site-per-process',
'--disable-site-isolation-trials',
])
# Browser starten
self.browser = browser_instance.launch(
headless=self.headless,
args=browser_args,
slow_mo=self.slowmo
)
# Kontext-Optionen für Stealth-Modus
context_options = {
"viewport": {"width": 1920, "height": 1080},
"device_scale_factor": self.stealth_config.get("device_scale_factor", 1.0),
"locale": "de-DE",
"timezone_id": self.stealth_config.get("timezone_id", "Europe/Berlin"),
"accept_downloads": True,
}
# User-Agent setzen
if self.user_agent:
context_options["user_agent"] = self.user_agent
# Proxy-Einstellungen, falls vorhanden
if self.proxy:
context_options["proxy"] = self.proxy
# Browserkontext erstellen
self.context = self.browser.new_context(**context_options)
# JavaScript-Fingerprinting-Schutz
self._apply_stealth_scripts()
# Neue Seite erstellen
self.page = self.context.new_page()
# Event-Listener für Konsolen-Logs
self.page.on("console", lambda msg: logger.debug(f"BROWSER CONSOLE: {msg.text}"))
return self.page
except Exception as e:
logger.error(f"Fehler beim Starten des Browsers: {e}")
self.close()
raise
def _apply_stealth_scripts(self):
"""Wendet JavaScript-Skripte an, um Browser-Fingerprinting zu umgehen."""
# Diese Skripte überschreiben Eigenschaften, die für Bot-Erkennung verwendet werden
scripts = [
# WebDriver-Eigenschaft überschreiben
"""
() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
}
""",
# Navigator-Eigenschaften überschreiben
f"""
() => {{
const newProto = navigator.__proto__;
delete newProto.webdriver;
navigator.__proto__ = newProto;
Object.defineProperty(navigator, 'platform', {{
get: () => '{self.stealth_config.get("platform", "Win32")}'
}});
Object.defineProperty(navigator, 'languages', {{
get: () => ['de-DE', 'de', 'en-US', 'en']
}});
Object.defineProperty(navigator, 'vendor', {{
get: () => '{self.stealth_config.get("vendor", "Google Inc.")}'
}});
}}
""",
# Chrome-Objekte hinzufügen, die in normalen Browsern vorhanden sind
"""
() => {
// Fügt chrome.runtime hinzu, falls nicht vorhanden
if (!window.chrome) {
window.chrome = {};
}
if (!window.chrome.runtime) {
window.chrome.runtime = {};
window.chrome.runtime.sendMessage = function() {};
}
}
""",
# Plugin-Fingerprinting
"""
() => {
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
}
"""
]
# Wenn Fingerprint-Noise aktiviert ist, füge zufällige Variationen hinzu
if self.stealth_config.get("fingerprint_noise", True):
scripts.append("""
() => {
// Canvas-Fingerprinting leicht verändern
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {
const result = originalToDataURL.apply(this, arguments);
if (this.width > 16 && this.height > 16) {
// Kleines Rauschen in Pixels einfügen
const context = this.getContext('2d');
const imageData = context.getImageData(0, 0, 2, 2);
const pixelArray = imageData.data;
// Ändere einen zufälligen Pixel leicht
const randomPixel = Math.floor(Math.random() * pixelArray.length / 4) * 4;
pixelArray[randomPixel] = (pixelArray[randomPixel] + Math.floor(Math.random() * 10)) % 256;
context.putImageData(imageData, 0, 0);
}
return result;
};
}
""")
# Skripte auf den Browser-Kontext anwenden
for script in scripts:
self.context.add_init_script(script)
def navigate_to(self, url: str, wait_until: str = "networkidle", timeout: int = 30000) -> bool:
"""
Navigiert zu einer bestimmten URL und wartet, bis die Seite geladen ist.
Args:
url: Die Ziel-URL
wait_until: Wann die Navigation als abgeschlossen gilt ("load", "domcontentloaded", "networkidle")
timeout: Timeout in Millisekunden
Returns:
bool: True bei erfolgreicher Navigation, False sonst
"""
if self.page is None:
self.start()
try:
logger.info(f"Navigiere zu: {url}")
self.page.goto(url, wait_until=wait_until, timeout=timeout)
return True
except Exception as e:
logger.error(f"Fehler bei der Navigation zu {url}: {e}")
self.take_screenshot(f"navigation_error_{int(time.time())}")
return False
def wait_for_selector(self, selector: str, timeout: int = 30000) -> Optional[ElementHandle]:
"""
Wartet auf ein Element mit dem angegebenen Selektor.
Args:
selector: CSS- oder XPath-Selektor
timeout: Timeout in Millisekunden
Returns:
Optional[ElementHandle]: Das Element oder None, wenn nicht gefunden
"""
if self.page is None:
raise ValueError("Browser nicht gestartet. Rufe zuerst start() auf.")
try:
element = self.page.wait_for_selector(selector, timeout=timeout)
return element
except Exception as e:
logger.warning(f"Element nicht gefunden: {selector} - {e}")
return None
def fill_form_field(self, selector: str, value: str, timeout: int = 5000) -> bool:
"""
Füllt ein Formularfeld aus.
Args:
selector: Selektor für das Feld
value: Einzugebender Wert
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Auf Element warten
element = self.wait_for_selector(selector, timeout)
if not element:
return False
# Element fokussieren
element.focus()
time.sleep(random.uniform(0.1, 0.3))
# Vorhandenen Text löschen (optional)
current_value = element.evaluate("el => el.value")
if current_value:
element.fill("")
time.sleep(random.uniform(0.1, 0.2))
# Text menschenähnlich eingeben
for char in value:
element.type(char, delay=random.uniform(20, 100))
time.sleep(random.uniform(0.01, 0.05))
logger.info(f"Feld {selector} gefüllt mit: {value}")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen von {selector}: {e}")
key = f"fill_{selector}"
return self._retry_action(key, lambda: self.fill_form_field(selector, value, timeout))
def click_element(self, selector: str, force: bool = False, timeout: int = 5000) -> bool:
"""
Klickt auf ein Element.
Args:
selector: Selektor für das Element
force: Force-Click verwenden
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Auf Element warten
element = self.wait_for_selector(selector, timeout)
if not element:
return False
# Scroll zum Element
self.page.evaluate("element => element.scrollIntoView({ behavior: 'smooth', block: 'center' })", element)
time.sleep(random.uniform(0.3, 0.7))
# Menschenähnliches Verhalten - leichte Verzögerung vor dem Klick
time.sleep(random.uniform(0.2, 0.5))
# Element klicken
element.click(force=force, delay=random.uniform(20, 100))
logger.info(f"Element geklickt: {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim Klicken auf {selector}: {e}")
key = f"click_{selector}"
return self._retry_action(key, lambda: self.click_element(selector, force, timeout))
def select_option(self, selector: str, value: str, timeout: int = 5000) -> bool:
"""
Wählt eine Option aus einem Dropdown-Menü.
Args:
selector: Selektor für das Dropdown
value: Wert oder sichtbarer Text der Option
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Auf Element warten
element = self.wait_for_selector(selector, timeout)
if not element:
return False
# Option auswählen
self.page.select_option(selector, value=value)
logger.info(f"Option '{value}' ausgewählt in {selector}")
return True
except Exception as e:
logger.error(f"Fehler bei der Auswahl von '{value}' in {selector}: {e}")
key = f"select_{selector}"
return self._retry_action(key, lambda: self.select_option(selector, value, timeout))
def is_element_visible(self, selector: str, timeout: int = 5000) -> bool:
"""
Prüft, ob ein Element sichtbar ist.
Args:
selector: Selektor für das Element
timeout: Timeout in Millisekunden
Returns:
bool: True wenn sichtbar, False sonst
"""
try:
element = self.page.wait_for_selector(selector, timeout=timeout, state="visible")
return element is not None
except:
return False
def take_screenshot(self, name: str = None) -> str:
"""
Erstellt einen Screenshot der aktuellen Seite.
Args:
name: Name für den Screenshot (ohne Dateierweiterung)
Returns:
str: Pfad zum erstellten Screenshot
"""
if self.page is None:
raise ValueError("Browser nicht gestartet. Rufe zuerst start() auf.")
timestamp = int(time.time())
filename = f"{name}_{timestamp}.png" if name else f"screenshot_{timestamp}.png"
path = os.path.join(self.screenshots_dir, filename)
self.page.screenshot(path=path, full_page=True)
logger.info(f"Screenshot erstellt: {path}")
return path
def _retry_action(self, key: str, action_func, max_retries: int = 3) -> bool:
"""
Wiederholt eine Aktion bei Fehler.
Args:
key: Eindeutiger Schlüssel für die Aktion
action_func: Funktion, die ausgeführt werden soll
max_retries: Maximale Anzahl der Wiederholungen
Returns:
bool: Ergebnis der Aktion
"""
if key not in self.retry_counter:
self.retry_counter[key] = 0
self.retry_counter[key] += 1
if self.retry_counter[key] <= max_retries:
logger.info(f"Wiederhole Aktion {key} (Versuch {self.retry_counter[key]} von {max_retries})")
time.sleep(random.uniform(0.5, 1.0))
return action_func()
else:
logger.warning(f"Maximale Anzahl von Wiederholungen für {key} erreicht")
self.retry_counter[key] = 0
return False
def close(self):
"""Schließt den Browser und gibt Ressourcen frei."""
try:
if self.page:
self.page.close()
self.page = None
if self.context:
self.context.close()
self.context = None
if self.browser:
self.browser.close()
self.browser = None
if self.playwright:
self.playwright.stop()
self.playwright = None
logger.info("Browser-Sitzung geschlossen")
except Exception as e:
logger.error(f"Fehler beim Schließen des Browsers: {e}")
def __enter__(self):
"""Kontext-Manager-Eintritt."""
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Kontext-Manager-Austritt."""
self.close()
# Beispielnutzung, wenn direkt ausgeführt
if __name__ == "__main__":
# Konfiguriere Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Beispiel für einen Proxy (ohne Anmeldedaten)
proxy_config = {
"server": "http://example-proxy.com:8080"
}
# Browser starten und zu einer Seite navigieren
with PlaywrightManager(headless=False) as manager:
manager.navigate_to("https://www.instagram.com")
time.sleep(5) # Kurze Pause zum Anzeigen der Seite

216
browser/stealth_config.py Normale Datei
Datei anzeigen

@ -0,0 +1,216 @@
"""
Stealth-Konfiguration für Playwright - Anti-Bot-Erkennung
"""
import json
import logging
import os
import random
import platform
from pathlib import Path
from typing import Dict, Any, List
# Konfiguriere Logger
logger = logging.getLogger("stealth_config")
class StealthConfig:
"""
Konfiguriert Anti-Bot-Erkennungs-Einstellungen für Playwright.
Generiert und verwaltet verschiedene Fingerprint-Einstellungen.
"""
# Standardwerte für User-Agents
CHROME_DESKTOP_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
]
MOBILE_AGENTS = [
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/135.0.0.0 Mobile/15E148 Safari/604.1"
]
# Plattformen
PLATFORMS = {
"windows": "Win32",
"macos": "MacIntel",
"linux": "Linux x86_64",
"android": "Linux armv8l",
"ios": "iPhone"
}
# Browser-Sprachen
LANGUAGES = [
"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
"de-DE,de;q=0.9,en;q=0.8",
"de;q=0.9,en-US;q=0.8,en;q=0.7",
"en-US,en;q=0.9,de;q=0.8"
]
# Zeitzone für Deutschland
TIMEZONE_ID = "Europe/Berlin"
def __init__(self, config_dir: str = None):
"""
Initialisiert die Stealth-Konfiguration.
Args:
config_dir: Verzeichnis für Konfigurationsdateien
"""
self.config_dir = config_dir or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config")
os.makedirs(self.config_dir, exist_ok=True)
self.config_path = os.path.join(self.config_dir, "stealth_config.json")
# Lade benutzerdefinierte User-Agents, falls vorhanden
self.user_agents = self._load_user_agents()
# Lade gespeicherte Konfiguration oder erstelle eine neue
self.config = self._load_or_create_config()
def _load_user_agents(self) -> Dict[str, List[str]]:
"""Lädt benutzerdefinierte User-Agents aus der Konfigurationsdatei."""
user_agents_path = os.path.join(self.config_dir, "user_agents.json")
if os.path.exists(user_agents_path):
try:
with open(user_agents_path, 'r', encoding='utf-8') as f:
agents = json.load(f)
if isinstance(agents, dict) and "desktop" in agents and "mobile" in agents:
return agents
except Exception as e:
logger.warning(f"Fehler beim Laden von user_agents.json: {e}")
# Standardwerte zurückgeben
return {
"desktop": self.CHROME_DESKTOP_AGENTS,
"mobile": self.MOBILE_AGENTS
}
def _load_or_create_config(self) -> Dict[str, Any]:
"""Lädt die Konfiguration oder erstellt eine neue, falls keine existiert."""
if os.path.exists(self.config_path):
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
logger.info("Stealth-Konfiguration geladen")
return config
except Exception as e:
logger.warning(f"Konnte Stealth-Konfiguration nicht laden: {e}")
# Erstelle eine neue Konfiguration
config = self.generate_config()
self.save_config(config)
return config
def generate_config(self, device_type: str = "desktop") -> Dict[str, Any]:
"""
Generiert eine neue Stealth-Konfiguration.
Args:
device_type: "desktop" oder "mobile"
Returns:
Dict[str, Any]: Die generierte Konfiguration
"""
# Wähle Plattform und entsprechenden User-Agent
if device_type == "mobile":
platform_name = random.choice(["android", "ios"])
user_agent = random.choice(self.user_agents["mobile"])
else:
# Wähle eine Plattform, die zum System passt
system = platform.system().lower()
if system == "darwin":
platform_name = "macos"
elif system == "windows":
platform_name = "windows"
else:
platform_name = "linux"
user_agent = random.choice(self.user_agents["desktop"])
platform_value = self.PLATFORMS.get(platform_name, "Win32")
# Wähle weitere Konfigurationen
config = {
"user_agent": user_agent,
"platform": platform_value,
"vendor": "Google Inc." if "Chrome" in user_agent else "Apple Computer, Inc.",
"accept_language": random.choice(self.LANGUAGES),
"timezone_id": self.TIMEZONE_ID,
"device_scale_factor": random.choice([1.0, 1.25, 1.5, 2.0]) if random.random() < 0.3 else 1.0,
"color_depth": random.choice([24, 30, 48]),
"hardware_concurrency": random.choice([2, 4, 8, 12, 16]),
"device_memory": random.choice([2, 4, 8, 16]),
"webdriver": False,
"fingerprint_noise": True,
"device_type": device_type
}
return config
def save_config(self, config: Dict[str, Any]) -> None:
"""
Speichert die Konfiguration in einer Datei.
Args:
config: Die zu speichernde Konfiguration
"""
try:
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2)
logger.info(f"Stealth-Konfiguration gespeichert in: {self.config_path}")
except Exception as e:
logger.error(f"Fehler beim Speichern der Stealth-Konfiguration: {e}")
def get_config(self) -> Dict[str, Any]:
"""Gibt die aktuelle Konfiguration zurück."""
return self.config
def rotate_config(self, device_type: str = None) -> Dict[str, Any]:
"""
Generiert eine neue Konfiguration und speichert sie.
Args:
device_type: "desktop" oder "mobile", oder None für bestehenden Typ
Returns:
Dict[str, Any]: Die neue Konfiguration
"""
if device_type is None:
device_type = self.config.get("device_type", "desktop")
self.config = self.generate_config(device_type)
self.save_config(self.config)
return self.config
def get_user_agent(self) -> str:
"""Gibt den aktuellen User-Agent aus der Konfiguration zurück."""
return self.config.get("user_agent", self.CHROME_DESKTOP_AGENTS[0])
# Beispielnutzung, wenn direkt ausgeführt
if __name__ == "__main__":
# Konfiguriere Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Beispiel für Konfigurationserstellung
stealth = StealthConfig()
print("Aktuelle Konfiguration:")
print(json.dumps(stealth.get_config(), indent=2))
print("\nNeue Desktop-Konfiguration:")
desktop_config = stealth.rotate_config("desktop")
print(json.dumps(desktop_config, indent=2))
print("\nNeue Mobile-Konfiguration:")
mobile_config = stealth.rotate_config("mobile")
print(json.dumps(mobile_config, indent=2))