Update changes
Dieser Commit ist enthalten in:
@ -1,11 +1,14 @@
|
||||
"""
|
||||
Cookie Consent Handler für Browser-Sessions
|
||||
|
||||
Behandelt Cookie-Consent-Seiten bei der Session-Wiederherstellung
|
||||
Behandelt Cookie-Consent-Seiten bei der Session-Wiederherstellung.
|
||||
Enthält Anti-Detection-Maßnahmen wie Lese-Pausen.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
import time
|
||||
import random
|
||||
from typing import Optional, Any
|
||||
from playwright.sync_api import Page
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -15,20 +18,25 @@ class CookieConsentHandler:
|
||||
"""Behandelt Cookie-Consent-Dialoge verschiedener Plattformen"""
|
||||
|
||||
@staticmethod
|
||||
def handle_instagram_consent(page: Page) -> bool:
|
||||
def handle_instagram_consent(page: Page, human_behavior: Any = None) -> bool:
|
||||
"""
|
||||
Behandelt Instagram's Cookie-Consent-Seite
|
||||
|
||||
Behandelt Instagram's Cookie-Consent-Seite mit realistischer Lese-Zeit.
|
||||
|
||||
Diese Methode simuliert menschliches Verhalten durch eine Pause
|
||||
bevor der Cookie-Dialog geklickt wird. Echte Menschen lesen den
|
||||
Cookie-Text bevor sie auf einen Button klicken.
|
||||
|
||||
Args:
|
||||
page: Playwright Page-Objekt
|
||||
|
||||
human_behavior: Optional HumanBehavior-Instanz für realistische Delays
|
||||
|
||||
Returns:
|
||||
bool: True wenn Consent behandelt wurde, False sonst
|
||||
"""
|
||||
try:
|
||||
# Warte kurz auf Seitenladung
|
||||
page.wait_for_load_state('networkidle', timeout=5000)
|
||||
|
||||
|
||||
# Prüfe ob wir auf der Cookie-Consent-Seite sind
|
||||
consent_indicators = [
|
||||
# Deutsche Texte
|
||||
@ -57,7 +65,28 @@ class CookieConsentHandler:
|
||||
button = page.locator(button_selector).first
|
||||
if button.is_visible():
|
||||
logger.info(f"Found consent decline button: {button_selector}")
|
||||
|
||||
|
||||
# ANTI-DETECTION: Realistische Lese-Pause bevor Cookie-Dialog geklickt wird
|
||||
# Simuliert das Lesen der Cookie-Informationen (3-8 Sekunden)
|
||||
if human_behavior and hasattr(human_behavior, 'anti_detection_delay'):
|
||||
logger.debug("Cookie-Banner erkannt - simuliere Lesen...")
|
||||
human_behavior.anti_detection_delay("cookie_reading")
|
||||
else:
|
||||
# Fallback ohne HumanBehavior
|
||||
read_delay = random.uniform(3.0, 8.0)
|
||||
logger.debug(f"Cookie-Banner Lese-Pause: {read_delay:.1f}s")
|
||||
time.sleep(read_delay)
|
||||
|
||||
# Gelegentlich etwas scrollen um "mehr zu lesen" (30% Chance)
|
||||
if random.random() < 0.3:
|
||||
try:
|
||||
page.evaluate("window.scrollBy(0, 50)")
|
||||
time.sleep(random.uniform(0.8, 1.5))
|
||||
page.evaluate("window.scrollBy(0, -50)")
|
||||
time.sleep(random.uniform(0.3, 0.6))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Verwende robuste Click-Methoden für Cookie-Consent
|
||||
success = False
|
||||
try:
|
||||
@ -233,19 +262,21 @@ class CookieConsentHandler:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def check_and_handle_consent(page: Page, platform: str = "instagram") -> bool:
|
||||
def check_and_handle_consent(page: Page, platform: str = "instagram",
|
||||
human_behavior: Any = None) -> bool:
|
||||
"""
|
||||
Prüft und behandelt Cookie-Consent für die angegebene Plattform
|
||||
|
||||
Prüft und behandelt Cookie-Consent für die angegebene Plattform.
|
||||
|
||||
Args:
|
||||
page: Playwright Page-Objekt
|
||||
platform: Plattform-Name (default: "instagram")
|
||||
|
||||
human_behavior: Optional HumanBehavior-Instanz für realistische Delays
|
||||
|
||||
Returns:
|
||||
bool: True wenn Consent behandelt wurde, False sonst
|
||||
"""
|
||||
if platform.lower() == "instagram":
|
||||
return CookieConsentHandler.handle_instagram_consent(page)
|
||||
return CookieConsentHandler.handle_instagram_consent(page, human_behavior)
|
||||
else:
|
||||
logger.warning(f"No consent handler implemented for platform: {platform}")
|
||||
return False
|
||||
@ -1,521 +0,0 @@
|
||||
# Instagram Video Bypass - Emergency Deep Level Fixes
|
||||
"""
|
||||
Tiefgreifende Instagram Video Bypass Techniken
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
logger = logging.getLogger("instagram_video_bypass")
|
||||
|
||||
class InstagramVideoBypass:
|
||||
"""Deep-level Instagram video bypass techniques"""
|
||||
|
||||
def __init__(self, page: Any):
|
||||
self.page = page
|
||||
|
||||
def apply_emergency_bypass(self) -> None:
|
||||
"""Wendet Emergency Deep-Level Bypass an"""
|
||||
|
||||
# 1. Complete Automation Signature Removal
|
||||
automation_removal_script = """
|
||||
() => {
|
||||
// Remove ALL automation signatures
|
||||
|
||||
// 1. Navigator properties cleanup
|
||||
delete navigator.__webdriver_script_fn;
|
||||
delete navigator.__fxdriver_evaluate;
|
||||
delete navigator.__driver_unwrapped;
|
||||
delete navigator.__webdriver_unwrapped;
|
||||
delete navigator.__driver_evaluate;
|
||||
delete navigator.__selenium_unwrapped;
|
||||
delete navigator.__fxdriver_unwrapped;
|
||||
|
||||
// 2. Window properties cleanup
|
||||
delete window.navigator.webdriver;
|
||||
delete window.webdriver;
|
||||
delete window.chrome.webdriver;
|
||||
delete window.callPhantom;
|
||||
delete window._phantom;
|
||||
delete window.__nightmare;
|
||||
delete window._selenium;
|
||||
delete window.calledSelenium;
|
||||
delete window.$cdc_asdjflasutopfhvcZLmcfl_;
|
||||
delete window.$chrome_asyncScriptInfo;
|
||||
delete window.__webdriver_evaluate;
|
||||
delete window.__selenium_evaluate;
|
||||
delete window.__webdriver_script_function;
|
||||
delete window.__webdriver_script_func;
|
||||
delete window.__webdriver_script_fn;
|
||||
delete window.__fxdriver_evaluate;
|
||||
delete window.__driver_unwrapped;
|
||||
delete window.__webdriver_unwrapped;
|
||||
delete window.__driver_evaluate;
|
||||
delete window.__selenium_unwrapped;
|
||||
delete window.__fxdriver_unwrapped;
|
||||
|
||||
// 3. Document cleanup
|
||||
delete document.__webdriver_script_fn;
|
||||
delete document.__selenium_unwrapped;
|
||||
delete document.__webdriver_unwrapped;
|
||||
delete document.__driver_evaluate;
|
||||
delete document.__webdriver_evaluate;
|
||||
delete document.__fxdriver_evaluate;
|
||||
delete document.__fxdriver_unwrapped;
|
||||
delete document.__driver_unwrapped;
|
||||
|
||||
// 4. Chrome object enhancement
|
||||
if (!window.chrome) {
|
||||
window.chrome = {};
|
||||
}
|
||||
if (!window.chrome.runtime) {
|
||||
window.chrome.runtime = {
|
||||
onConnect: {addListener: function() {}},
|
||||
onMessage: {addListener: function() {}},
|
||||
connect: function() { return {postMessage: function() {}, onMessage: {addListener: function() {}}} }
|
||||
};
|
||||
}
|
||||
if (!window.chrome.app) {
|
||||
window.chrome.app = {
|
||||
isInstalled: false,
|
||||
InstallState: {DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed'},
|
||||
RunningState: {CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running'}
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Plugin array enhancement
|
||||
const fakePlugins = [
|
||||
{name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format'},
|
||||
{name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: 'Portable Document Format'},
|
||||
{name: 'Native Client', filename: 'internal-nacl-plugin', description: 'Native Client'}
|
||||
];
|
||||
|
||||
Object.defineProperty(navigator, 'plugins', {
|
||||
get: () => {
|
||||
const pluginArray = [...fakePlugins];
|
||||
pluginArray.length = fakePlugins.length;
|
||||
pluginArray.item = function(index) { return this[index] || null; };
|
||||
pluginArray.namedItem = function(name) { return this.find(p => p.name === name) || null; };
|
||||
pluginArray.refresh = function() {};
|
||||
return pluginArray;
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
"""
|
||||
|
||||
# 2. Instagram-specific video API spoofing
|
||||
instagram_video_api_script = """
|
||||
() => {
|
||||
// Instagram Video API Deep Spoofing
|
||||
|
||||
// 1. MSE (Media Source Extensions) proper support
|
||||
if (window.MediaSource) {
|
||||
const originalIsTypeSupported = MediaSource.isTypeSupported;
|
||||
MediaSource.isTypeSupported = function(type) {
|
||||
const supportedTypes = [
|
||||
'video/mp4; codecs="avc1.42E01E"',
|
||||
'video/mp4; codecs="avc1.4D401F"',
|
||||
'video/mp4; codecs="avc1.640028"',
|
||||
'video/webm; codecs="vp8"',
|
||||
'video/webm; codecs="vp9"',
|
||||
'audio/mp4; codecs="mp4a.40.2"',
|
||||
'audio/webm; codecs="opus"'
|
||||
];
|
||||
|
||||
if (supportedTypes.includes(type)) {
|
||||
return true;
|
||||
}
|
||||
return originalIsTypeSupported.call(this, type);
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Encrypted Media Extensions deep spoofing
|
||||
if (navigator.requestMediaKeySystemAccess) {
|
||||
const originalRequestAccess = navigator.requestMediaKeySystemAccess;
|
||||
navigator.requestMediaKeySystemAccess = function(keySystem, supportedConfigurations) {
|
||||
if (keySystem === 'com.widevine.alpha') {
|
||||
return Promise.resolve({
|
||||
keySystem: 'com.widevine.alpha',
|
||||
getConfiguration: () => ({
|
||||
initDataTypes: ['cenc', 'keyids', 'webm'],
|
||||
audioCapabilities: [
|
||||
{contentType: 'audio/mp4; codecs="mp4a.40.2"', robustness: 'SW_SECURE_CRYPTO'},
|
||||
{contentType: 'audio/webm; codecs="opus"', robustness: 'SW_SECURE_CRYPTO'}
|
||||
],
|
||||
videoCapabilities: [
|
||||
{contentType: 'video/mp4; codecs="avc1.42E01E"', robustness: 'SW_SECURE_DECODE'},
|
||||
{contentType: 'video/mp4; codecs="avc1.4D401F"', robustness: 'SW_SECURE_DECODE'},
|
||||
{contentType: 'video/webm; codecs="vp9"', robustness: 'SW_SECURE_DECODE'}
|
||||
],
|
||||
distinctiveIdentifier: 'optional',
|
||||
persistentState: 'required',
|
||||
sessionTypes: ['temporary', 'persistent-license']
|
||||
}),
|
||||
createMediaKeys: () => Promise.resolve({
|
||||
createSession: (sessionType) => {
|
||||
const session = {
|
||||
sessionId: 'session_' + Math.random().toString(36).substr(2, 9),
|
||||
expiration: NaN,
|
||||
closed: Promise.resolve(),
|
||||
keyStatuses: new Map(),
|
||||
addEventListener: function() {},
|
||||
removeEventListener: function() {},
|
||||
generateRequest: function(initDataType, initData) {
|
||||
setTimeout(() => {
|
||||
if (this.onmessage) {
|
||||
this.onmessage({
|
||||
type: 'message',
|
||||
message: new ArrayBuffer(8)
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
return Promise.resolve();
|
||||
},
|
||||
load: function() { return Promise.resolve(false); },
|
||||
update: function(response) {
|
||||
setTimeout(() => {
|
||||
if (this.onkeystatuseschange) {
|
||||
this.onkeystatuseschange();
|
||||
}
|
||||
}, 50);
|
||||
return Promise.resolve();
|
||||
},
|
||||
close: function() { return Promise.resolve(); },
|
||||
remove: function() { return Promise.resolve(); }
|
||||
};
|
||||
|
||||
// Add event target methods
|
||||
session.dispatchEvent = function() {};
|
||||
|
||||
return session;
|
||||
},
|
||||
setServerCertificate: () => Promise.resolve(true)
|
||||
})
|
||||
});
|
||||
}
|
||||
return originalRequestAccess.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Hardware media key handling
|
||||
if (navigator.mediaSession) {
|
||||
navigator.mediaSession.setActionHandler = function() {};
|
||||
navigator.mediaSession.playbackState = 'playing';
|
||||
} else {
|
||||
navigator.mediaSession = {
|
||||
metadata: null,
|
||||
playbackState: 'playing',
|
||||
setActionHandler: function() {},
|
||||
setPositionState: function() {}
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Picture-in-Picture API
|
||||
if (!document.pictureInPictureEnabled) {
|
||||
Object.defineProperty(document, 'pictureInPictureEnabled', {
|
||||
get: () => true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Web Audio API enhancement for video
|
||||
if (window.AudioContext || window.webkitAudioContext) {
|
||||
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
||||
const originalAudioContext = AudioCtx;
|
||||
|
||||
window.AudioContext = function(...args) {
|
||||
const ctx = new originalAudioContext(...args);
|
||||
|
||||
// Override audio context properties for consistency
|
||||
Object.defineProperty(ctx, 'baseLatency', {
|
||||
get: () => 0.01,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(ctx, 'outputLatency', {
|
||||
get: () => 0.02,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
// Copy static methods
|
||||
Object.keys(originalAudioContext).forEach(key => {
|
||||
window.AudioContext[key] = originalAudioContext[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# 3. Network request interception for video
|
||||
network_interception_script = """
|
||||
() => {
|
||||
// Advanced network request interception for Instagram videos
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(input, init) {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
|
||||
// Instagram video CDN requests
|
||||
if (url.includes('instagram.com') || url.includes('fbcdn.net') || url.includes('cdninstagram.com')) {
|
||||
const enhancedInit = {
|
||||
...init,
|
||||
headers: {
|
||||
...init?.headers,
|
||||
'Accept': '*/*',
|
||||
'Accept-Encoding': 'identity;q=1, *;q=0',
|
||||
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'Cache-Control': 'no-cache',
|
||||
'DNT': '1',
|
||||
'Origin': 'https://www.instagram.com',
|
||||
'Pragma': 'no-cache',
|
||||
'Referer': 'https://www.instagram.com/',
|
||||
'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
|
||||
'Sec-Ch-Ua-Mobile': '?0',
|
||||
'Sec-Ch-Ua-Platform': '"Windows"',
|
||||
'Sec-Fetch-Dest': 'video',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'cross-site',
|
||||
'User-Agent': navigator.userAgent,
|
||||
'X-Asbd-Id': '129477',
|
||||
'X-Fb-Lsd': document.querySelector('[name="fb_dtsg"]')?.value || '',
|
||||
'X-Instagram-Ajax': '1'
|
||||
}
|
||||
};
|
||||
|
||||
// Remove problematic headers that might indicate automation
|
||||
delete enhancedInit.headers['sec-ch-ua-arch'];
|
||||
delete enhancedInit.headers['sec-ch-ua-bitness'];
|
||||
delete enhancedInit.headers['sec-ch-ua-full-version'];
|
||||
delete enhancedInit.headers['sec-ch-ua-full-version-list'];
|
||||
delete enhancedInit.headers['sec-ch-ua-model'];
|
||||
delete enhancedInit.headers['sec-ch-ua-wow64'];
|
||||
|
||||
return originalFetch.call(this, input, enhancedInit);
|
||||
}
|
||||
|
||||
return originalFetch.apply(this, arguments);
|
||||
};
|
||||
|
||||
// XMLHttpRequest interception
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
|
||||
this._url = url;
|
||||
return originalXHROpen.apply(this, arguments);
|
||||
};
|
||||
|
||||
const originalXHRSend = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.send = function(body) {
|
||||
if (this._url && (this._url.includes('instagram.com') || this._url.includes('fbcdn.net'))) {
|
||||
// Add video-specific headers
|
||||
this.setRequestHeader('Accept', '*/*');
|
||||
this.setRequestHeader('Accept-Language', 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7');
|
||||
this.setRequestHeader('Cache-Control', 'no-cache');
|
||||
this.setRequestHeader('Pragma', 'no-cache');
|
||||
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
}
|
||||
return originalXHRSend.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
"""
|
||||
|
||||
# 4. Timing and behavior normalization
|
||||
timing_script = """
|
||||
() => {
|
||||
// Normalize timing functions to avoid detection
|
||||
|
||||
// Performance timing spoofing
|
||||
if (window.performance && window.performance.timing) {
|
||||
const timing = performance.timing;
|
||||
const now = Date.now();
|
||||
|
||||
Object.defineProperty(performance.timing, 'navigationStart', {
|
||||
get: () => now - Math.floor(Math.random() * 1000) - 1000,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(performance.timing, 'loadEventEnd', {
|
||||
get: () => now - Math.floor(Math.random() * 500),
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
// Date/Time consistency
|
||||
const originalDate = Date;
|
||||
const startTime = originalDate.now();
|
||||
|
||||
Date.now = function() {
|
||||
return startTime + (originalDate.now() - startTime);
|
||||
};
|
||||
|
||||
// Remove timing inconsistencies that indicate automation
|
||||
const originalSetTimeout = window.setTimeout;
|
||||
window.setTimeout = function(fn, delay, ...args) {
|
||||
// Add slight randomization to timing
|
||||
const randomDelay = delay + Math.floor(Math.random() * 10) - 5;
|
||||
return originalSetTimeout.call(this, fn, Math.max(0, randomDelay), ...args);
|
||||
};
|
||||
}
|
||||
"""
|
||||
|
||||
# Apply all scripts in sequence
|
||||
scripts = [
|
||||
automation_removal_script,
|
||||
instagram_video_api_script,
|
||||
network_interception_script,
|
||||
timing_script
|
||||
]
|
||||
|
||||
for i, script in enumerate(scripts):
|
||||
try:
|
||||
self.page.add_init_script(script)
|
||||
logger.info(f"Applied emergency bypass script {i+1}/4")
|
||||
time.sleep(0.1) # Small delay between scripts
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to apply emergency bypass script {i+1}: {e}")
|
||||
|
||||
logger.info("Emergency Instagram video bypass applied")
|
||||
|
||||
def inject_video_session_data(self) -> None:
|
||||
"""Injiziert realistische Video-Session-Daten"""
|
||||
|
||||
session_script = """
|
||||
() => {
|
||||
// Inject realistic video session data
|
||||
|
||||
// 1. Video viewing history
|
||||
localStorage.setItem('instagram_video_history', JSON.stringify({
|
||||
last_viewed: Date.now() - Math.floor(Math.random() * 86400000),
|
||||
view_count: Math.floor(Math.random() * 50) + 10,
|
||||
preferences: {
|
||||
autoplay: true,
|
||||
quality: 'auto',
|
||||
captions: false
|
||||
}
|
||||
}));
|
||||
|
||||
// 2. Media session state
|
||||
localStorage.setItem('media_session_state', JSON.stringify({
|
||||
hasInteracted: true,
|
||||
lastInteraction: Date.now() - Math.floor(Math.random() * 3600000),
|
||||
playbackRate: 1,
|
||||
volume: 0.8
|
||||
}));
|
||||
|
||||
// 3. DRM license cache simulation
|
||||
sessionStorage.setItem('drm_licenses', JSON.stringify({
|
||||
widevine: {
|
||||
version: '4.10.2449.0',
|
||||
lastUpdate: Date.now() - Math.floor(Math.random() * 604800000),
|
||||
status: 'valid'
|
||||
}
|
||||
}));
|
||||
|
||||
// 4. Instagram session tokens
|
||||
const csrfToken = document.querySelector('[name="csrfmiddlewaretoken"]')?.value ||
|
||||
document.querySelector('meta[name="csrf-token"]')?.content ||
|
||||
'missing';
|
||||
|
||||
if (csrfToken !== 'missing') {
|
||||
sessionStorage.setItem('csrf_token', csrfToken);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
try:
|
||||
self.page.evaluate(session_script)
|
||||
logger.info("Video session data injected successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to inject video session data: {e}")
|
||||
|
||||
def simulate_user_interaction(self) -> None:
|
||||
"""Simuliert authentische Benutzerinteraktion"""
|
||||
|
||||
try:
|
||||
# Random mouse movements
|
||||
for _ in range(3):
|
||||
x = random.randint(100, 800)
|
||||
y = random.randint(100, 600)
|
||||
self.page.mouse.move(x, y)
|
||||
time.sleep(random.uniform(0.1, 0.3))
|
||||
|
||||
# Random scroll
|
||||
self.page.mouse.wheel(0, random.randint(-200, 200))
|
||||
time.sleep(random.uniform(0.2, 0.5))
|
||||
|
||||
# Click somewhere safe (not on video)
|
||||
self.page.click('body', position={'x': random.randint(50, 100), 'y': random.randint(50, 100)})
|
||||
time.sleep(random.uniform(0.3, 0.7))
|
||||
|
||||
logger.info("User interaction simulation completed")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to simulate user interaction: {e}")
|
||||
|
||||
def check_video_errors(self) -> Dict[str, Any]:
|
||||
"""Überprüft Video-Fehler und DRM-Status"""
|
||||
|
||||
try:
|
||||
result = self.page.evaluate("""
|
||||
() => {
|
||||
const errors = [];
|
||||
const diagnostics = {
|
||||
drm_support: false,
|
||||
media_source: false,
|
||||
codec_support: {},
|
||||
video_elements: 0,
|
||||
error_messages: []
|
||||
};
|
||||
|
||||
// Check for DRM support
|
||||
if (navigator.requestMediaKeySystemAccess) {
|
||||
diagnostics.drm_support = true;
|
||||
}
|
||||
|
||||
// Check Media Source Extensions
|
||||
if (window.MediaSource) {
|
||||
diagnostics.media_source = true;
|
||||
diagnostics.codec_support = {
|
||||
h264: MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"'),
|
||||
vp9: MediaSource.isTypeSupported('video/webm; codecs="vp9"'),
|
||||
aac: MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.2"')
|
||||
};
|
||||
}
|
||||
|
||||
// Count video elements
|
||||
diagnostics.video_elements = document.querySelectorAll('video').length;
|
||||
|
||||
// Look for error messages
|
||||
const errorElements = document.querySelectorAll('[class*="error"], [class*="fail"]');
|
||||
errorElements.forEach(el => {
|
||||
if (el.textContent.includes('Video') || el.textContent.includes('video')) {
|
||||
diagnostics.error_messages.push(el.textContent.trim());
|
||||
}
|
||||
});
|
||||
|
||||
// Console errors
|
||||
const consoleErrors = [];
|
||||
const originalConsoleError = console.error;
|
||||
console.error = function(...args) {
|
||||
consoleErrors.push(args.join(' '));
|
||||
originalConsoleError.apply(console, arguments);
|
||||
};
|
||||
|
||||
return {
|
||||
diagnostics,
|
||||
console_errors: consoleErrors,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
""")
|
||||
|
||||
logger.info(f"Video diagnostics: {result}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Video error check failed: {e}")
|
||||
return {}
|
||||
@ -17,6 +17,7 @@ from infrastructure.services.browser_protection_service import BrowserProtection
|
||||
# Konfiguriere Logger
|
||||
logger = logging.getLogger("playwright_manager")
|
||||
|
||||
|
||||
class PlaywrightManager:
|
||||
"""
|
||||
Verwaltet Browser-Sitzungen mit Playwright, einschließlich Stealth-Modus und Proxy-Einstellungen.
|
||||
@ -25,6 +26,9 @@ class PlaywrightManager:
|
||||
# Klassen-Variable: Zählt aktive Browser-Instanzen (Feature 5: Browser-Instanz Schutz)
|
||||
_active_count = 0
|
||||
|
||||
# Klassen-Variable: Referenz auf die zuletzt gestartete Instanz (für Cleanup)
|
||||
_current_instance: "PlaywrightManager" = None
|
||||
|
||||
def __init__(self,
|
||||
headless: bool = False,
|
||||
proxy: Optional[Dict[str, str]] = None,
|
||||
@ -149,24 +153,30 @@ class PlaywrightManager:
|
||||
|
||||
# Feature 5: Browser-Instanz Schutz - Nur eine Instanz gleichzeitig
|
||||
if PlaywrightManager._active_count >= 1:
|
||||
# Safety-Check: Prüfe ob Counter "hängt" (Absturz-Schutz)
|
||||
# Wenn ProcessGuard NICHT locked ist, aber Counter > 0, dann ist Counter "tot"
|
||||
from utils.process_guard import get_guard
|
||||
guard = get_guard()
|
||||
# Es gibt noch einen alten Browser - prüfe ob er geschlossen werden kann
|
||||
old_instance = PlaywrightManager._current_instance
|
||||
|
||||
if not guard.is_locked():
|
||||
# Counter hängt! Process Guard ist frei, aber Counter sagt Browser läuft
|
||||
if old_instance is not None and old_instance is not self:
|
||||
# Alte Instanz existiert und ist nicht diese - schließen
|
||||
logger.warning(
|
||||
f"⚠️ BROWSER-CLEANUP: Alte Browser-Instanz wird geschlossen "
|
||||
f"(Counter war {PlaywrightManager._active_count})"
|
||||
)
|
||||
try:
|
||||
old_instance.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Schließen alter Browser-Instanz: {e}")
|
||||
|
||||
# Counter und Referenz zurücksetzen
|
||||
PlaywrightManager._active_count = 0
|
||||
PlaywrightManager._current_instance = None
|
||||
else:
|
||||
# Keine alte Instanz gefunden, aber Counter > 0 - Safety Reset
|
||||
logger.warning(
|
||||
f"⚠️ SAFETY-RESET: _active_count war {PlaywrightManager._active_count}, "
|
||||
f"aber ProcessGuard ist nicht locked. Counter wird zurückgesetzt."
|
||||
f"aber keine alte Instanz gefunden. Counter wird zurückgesetzt."
|
||||
)
|
||||
PlaywrightManager._active_count = 0
|
||||
else:
|
||||
# Guard ist locked UND Counter ist > 0 → echte parallele Instanz
|
||||
raise RuntimeError(
|
||||
"Browser bereits aktiv. Nur eine Browser-Instanz gleichzeitig erlaubt. "
|
||||
"Beenden Sie den aktuellen Prozess."
|
||||
)
|
||||
|
||||
try:
|
||||
self.playwright = sync_playwright().start()
|
||||
@ -281,8 +291,9 @@ class PlaywrightManager:
|
||||
self.browser.on("disconnected", self._on_browser_disconnected)
|
||||
logger.debug("Browser-Disconnect-Handler registriert")
|
||||
|
||||
# Feature 5: Browser-Instanz Counter erhöhen
|
||||
# Feature 5: Browser-Instanz Counter erhöhen und aktuelle Instanz speichern
|
||||
PlaywrightManager._active_count += 1
|
||||
PlaywrightManager._current_instance = self
|
||||
logger.info(f"Browser gestartet (aktive Instanzen: {PlaywrightManager._active_count})")
|
||||
|
||||
return self.page
|
||||
@ -524,15 +535,22 @@ class PlaywrightManager:
|
||||
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:
|
||||
def click_element(self, selector: str, force: bool = False, timeout: int = 5000,
|
||||
use_bezier_mouse: bool = True) -> bool:
|
||||
"""
|
||||
Klickt auf ein Element mit Anti-Bot-Bypass-Strategien.
|
||||
|
||||
|
||||
Diese Methode simuliert menschliches Klick-Verhalten durch:
|
||||
- Bézier-Kurven-Mausbewegungen zum Element
|
||||
- Natürliches Scrolling (auch bei sichtbaren Elementen)
|
||||
- Variable Verzögerungen
|
||||
|
||||
Args:
|
||||
selector: Selektor für das Element
|
||||
force: Force-Click verwenden
|
||||
timeout: Timeout in Millisekunden
|
||||
|
||||
use_bezier_mouse: Ob Bézier-Mausbewegung vor dem Klick verwendet werden soll
|
||||
|
||||
Returns:
|
||||
bool: True bei Erfolg, False bei Fehler
|
||||
"""
|
||||
@ -541,24 +559,164 @@ class PlaywrightManager:
|
||||
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))
|
||||
|
||||
|
||||
# Verbessertes Scrolling mit menschlichem Verhalten
|
||||
self._human_scroll_to_element(element)
|
||||
|
||||
# Bézier-Mausbewegung zum Element (Anti-Detection)
|
||||
if use_bezier_mouse:
|
||||
self._move_mouse_to_element_bezier(element)
|
||||
|
||||
# Menschenähnliches Verhalten - variable Verzögerung vor dem Klick
|
||||
time.sleep(random.uniform(0.15, 0.4))
|
||||
|
||||
# Element klicken mit variablem Delay
|
||||
element.click(force=force, delay=random.uniform(30, 120))
|
||||
|
||||
logger.info(f"Element geklickt: {selector}")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Klicken auf {selector}: {e}")
|
||||
# Bei Fehlern verwende robuste Click-Strategien
|
||||
return self.robust_click(selector, timeout)
|
||||
|
||||
def _human_scroll_to_element(self, element) -> None:
|
||||
"""
|
||||
Scrollt zum Element mit menschlichem Verhalten.
|
||||
|
||||
Simuliert natürliches Scrollverhalten:
|
||||
- Gelegentlich erst in falsche Richtung scrollen
|
||||
- Auch bei sichtbaren Elementen leicht scrollen
|
||||
- Variable Scroll-Geschwindigkeit
|
||||
|
||||
Args:
|
||||
element: Das Playwright ElementHandle
|
||||
"""
|
||||
try:
|
||||
# Gelegentlich erst in "falsche" Richtung scrollen (15% Chance)
|
||||
if random.random() < 0.15:
|
||||
wrong_direction = random.choice(['up', 'down'])
|
||||
scroll_amount = random.randint(50, 150)
|
||||
if wrong_direction == 'up':
|
||||
self.page.evaluate(f"window.scrollBy(0, -{scroll_amount})")
|
||||
else:
|
||||
self.page.evaluate(f"window.scrollBy(0, {scroll_amount})")
|
||||
time.sleep(random.uniform(0.2, 0.5))
|
||||
logger.debug(f"Korrektur-Scroll: erst {wrong_direction}")
|
||||
|
||||
# Scroll-Verhalten zufällig wählen
|
||||
scroll_behavior = random.choice(['smooth', 'smooth', 'auto']) # 66% smooth
|
||||
scroll_block = random.choice(['center', 'center', 'nearest']) # 66% center
|
||||
|
||||
# Hauptscroll zum Element
|
||||
scroll_script = f"""
|
||||
(element) => {{
|
||||
const rect = element.getBoundingClientRect();
|
||||
const isFullyVisible = rect.top >= 0 && rect.bottom <= window.innerHeight;
|
||||
|
||||
// Auch wenn sichtbar, leicht scrollen für natürliches Verhalten (60% Chance)
|
||||
if (isFullyVisible && Math.random() < 0.6) {{
|
||||
const smallScroll = Math.floor(Math.random() * 60) - 30;
|
||||
window.scrollBy(0, smallScroll);
|
||||
}}
|
||||
|
||||
// Zum Element scrollen
|
||||
element.scrollIntoView({{ behavior: '{scroll_behavior}', block: '{scroll_block}' }});
|
||||
}}
|
||||
"""
|
||||
self.page.evaluate(scroll_script, element)
|
||||
|
||||
# Variable Wartezeit nach Scroll
|
||||
time.sleep(random.uniform(0.4, 1.0))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Human-Scroll: {e}")
|
||||
# Fallback: einfaches Scroll
|
||||
try:
|
||||
self.page.evaluate("element => element.scrollIntoView({ behavior: 'smooth', block: 'center' })", element)
|
||||
time.sleep(random.uniform(0.3, 0.7))
|
||||
except:
|
||||
pass
|
||||
|
||||
def _move_mouse_to_element_bezier(self, element) -> None:
|
||||
"""
|
||||
Bewegt die Maus mit Bézier-Kurve zum Element.
|
||||
|
||||
Simuliert realistische menschliche Mausbewegungen:
|
||||
- Kubische Bézier-Kurve mit 2 Kontrollpunkten
|
||||
- Variable Geschwindigkeit (langsamer am Anfang/Ende)
|
||||
- Leichtes Zittern für Natürlichkeit
|
||||
- Gelegentliche Mikro-Pausen
|
||||
|
||||
Args:
|
||||
element: Das Playwright ElementHandle
|
||||
"""
|
||||
try:
|
||||
# Aktuelle Mausposition oder zufälliger Startpunkt
|
||||
viewport = self.page.viewport_size
|
||||
if viewport:
|
||||
current_x = random.randint(100, viewport['width'] - 100)
|
||||
current_y = random.randint(100, viewport['height'] - 100)
|
||||
else:
|
||||
current_x = random.randint(100, 1820)
|
||||
current_y = random.randint(100, 980)
|
||||
|
||||
# Zielpunkt: Mitte des Elements mit leichter Variation
|
||||
box = element.bounding_box()
|
||||
if not box:
|
||||
logger.debug("Kein Bounding-Box für Element, überspringe Bézier-Bewegung")
|
||||
return
|
||||
|
||||
# Zielposition mit leichter Variation (nicht exakt Mitte)
|
||||
target_x = box['x'] + box['width'] / 2 + random.uniform(-5, 5)
|
||||
target_y = box['y'] + box['height'] / 2 + random.uniform(-5, 5)
|
||||
|
||||
# Entfernung berechnen
|
||||
distance = ((target_x - current_x)**2 + (target_y - current_y)**2)**0.5
|
||||
|
||||
# Anzahl der Schritte basierend auf Entfernung (mehr Schritte = flüssiger)
|
||||
steps = max(25, int(distance / 8))
|
||||
|
||||
# Kontrollpunkte für kubische Bézier-Kurve
|
||||
ctrl_variance = distance / 4
|
||||
ctrl1_x = current_x + (target_x - current_x) * random.uniform(0.2, 0.4) + random.uniform(-ctrl_variance, ctrl_variance)
|
||||
ctrl1_y = current_y + (target_y - current_y) * random.uniform(0.1, 0.3) + random.uniform(-ctrl_variance, ctrl_variance)
|
||||
ctrl2_x = current_x + (target_x - current_x) * random.uniform(0.6, 0.8) + random.uniform(-ctrl_variance, ctrl_variance)
|
||||
ctrl2_y = current_y + (target_y - current_y) * random.uniform(0.7, 0.9) + random.uniform(-ctrl_variance, ctrl_variance)
|
||||
|
||||
# Mausbewegung ausführen
|
||||
for i in range(steps + 1):
|
||||
t = i / steps
|
||||
|
||||
# Kubische Bézier-Formel: B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
|
||||
x = (1-t)**3 * current_x + 3*(1-t)**2*t * ctrl1_x + 3*(1-t)*t**2 * ctrl2_x + t**3 * target_x
|
||||
y = (1-t)**3 * current_y + 3*(1-t)**2*t * ctrl1_y + 3*(1-t)*t**2 * ctrl2_y + t**3 * target_y
|
||||
|
||||
# Leichtes Zittern hinzufügen für Realismus
|
||||
x += random.uniform(-1, 1)
|
||||
y += random.uniform(-1, 1)
|
||||
|
||||
# Maus bewegen
|
||||
self.page.mouse.move(x, y)
|
||||
|
||||
# Variable Geschwindigkeit: langsamer am Anfang/Ende, schneller in der Mitte
|
||||
if i < steps * 0.15 or i > steps * 0.85:
|
||||
# Langsamer am Anfang und Ende (Beschleunigung/Abbremsen)
|
||||
time.sleep(random.uniform(0.008, 0.018))
|
||||
else:
|
||||
# Schneller in der Mitte
|
||||
time.sleep(random.uniform(0.003, 0.008))
|
||||
|
||||
# Gelegentliche Mikro-Pause (5% Chance) - simuliert Zögern
|
||||
if random.random() < 0.05:
|
||||
time.sleep(random.uniform(0.02, 0.08))
|
||||
|
||||
logger.debug(f"Bézier-Mausbewegung: ({current_x:.0f},{current_y:.0f}) -> ({target_x:.0f},{target_y:.0f})")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Bézier-Mausbewegung fehlgeschlagen: {e}")
|
||||
# Kein Fallback nötig - Klick funktioniert auch ohne Mausbewegung
|
||||
|
||||
def robust_click(self, selector: str, timeout: int = 5000) -> bool:
|
||||
"""
|
||||
@ -1017,6 +1175,10 @@ class PlaywrightManager:
|
||||
else:
|
||||
logger.warning("Browser disconnected aber Counter war bereits 0")
|
||||
|
||||
# Aktuelle Instanz-Referenz löschen wenn diese Instanz die aktuelle war
|
||||
if PlaywrightManager._current_instance is self:
|
||||
PlaywrightManager._current_instance = None
|
||||
|
||||
def close(self):
|
||||
"""Schließt den Browser und gibt Ressourcen frei."""
|
||||
try:
|
||||
@ -1072,6 +1234,10 @@ class PlaywrightManager:
|
||||
else:
|
||||
logger.debug("Counter wurde bereits durch disconnected-Event dekrementiert")
|
||||
|
||||
# Aktuelle Instanz-Referenz löschen wenn diese Instanz die aktuelle war
|
||||
if PlaywrightManager._current_instance is self:
|
||||
PlaywrightManager._current_instance = None
|
||||
|
||||
logger.info("Browser-Sitzung erfolgreich geschlossen")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren