Playwright_Videoabspielen
Dieser Commit ist enthalten in:
@ -26,10 +26,15 @@ class PlaywrightManager:
|
|||||||
headless: bool = False,
|
headless: bool = False,
|
||||||
proxy: Optional[Dict[str, str]] = None,
|
proxy: Optional[Dict[str, str]] = None,
|
||||||
browser_type: str = "chromium",
|
browser_type: str = "chromium",
|
||||||
|
browser_channel: Optional[str] = None,
|
||||||
user_agent: Optional[str] = None,
|
user_agent: Optional[str] = None,
|
||||||
screenshots_dir: str = "screenshots",
|
screenshots_dir: str = "screenshots",
|
||||||
slowmo: int = 0,
|
slowmo: int = 0,
|
||||||
window_position: Optional[Tuple[int, int]] = None):
|
window_position: Optional[Tuple[int, int]] = None,
|
||||||
|
enable_autoplay_flag: bool = True,
|
||||||
|
auto_click_video: bool = True,
|
||||||
|
auto_mute_on_play: bool = True,
|
||||||
|
enable_video_enhancements: bool = False):
|
||||||
"""
|
"""
|
||||||
Initialisiert den PlaywrightManager.
|
Initialisiert den PlaywrightManager.
|
||||||
|
|
||||||
@ -45,10 +50,15 @@ class PlaywrightManager:
|
|||||||
self.headless = headless
|
self.headless = headless
|
||||||
self.proxy = proxy
|
self.proxy = proxy
|
||||||
self.browser_type = browser_type
|
self.browser_type = browser_type
|
||||||
|
self.browser_channel = browser_channel
|
||||||
self.user_agent = user_agent
|
self.user_agent = user_agent
|
||||||
self.screenshots_dir = screenshots_dir
|
self.screenshots_dir = screenshots_dir
|
||||||
self.slowmo = slowmo
|
self.slowmo = slowmo
|
||||||
self.window_position = window_position
|
self.window_position = window_position
|
||||||
|
self.enable_autoplay_flag = enable_autoplay_flag
|
||||||
|
self.auto_click_video = auto_click_video
|
||||||
|
self.auto_mute_on_play = auto_mute_on_play
|
||||||
|
self.enable_video_enhancements = enable_video_enhancements
|
||||||
|
|
||||||
# Stelle sicher, dass das Screenshots-Verzeichnis existiert
|
# Stelle sicher, dass das Screenshots-Verzeichnis existiert
|
||||||
os.makedirs(self.screenshots_dir, exist_ok=True)
|
os.makedirs(self.screenshots_dir, exist_ok=True)
|
||||||
@ -62,7 +72,8 @@ class PlaywrightManager:
|
|||||||
# Zähler für Wiederhholungsversuche
|
# Zähler für Wiederhholungsversuche
|
||||||
self.retry_counter = {}
|
self.retry_counter = {}
|
||||||
|
|
||||||
# Lade Stealth-Konfigurationen
|
# Lade Browser-/Stealth-Konfigurationen
|
||||||
|
self.browser_config = self._load_browser_config()
|
||||||
self.stealth_config = self._load_stealth_config()
|
self.stealth_config = self._load_stealth_config()
|
||||||
|
|
||||||
# Browser Protection Service
|
# Browser Protection Service
|
||||||
@ -92,6 +103,27 @@ class PlaywrightManager:
|
|||||||
"fingerprint_noise": True,
|
"fingerprint_noise": True,
|
||||||
"device_scale_factor": 1.0,
|
"device_scale_factor": 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _load_browser_config(self) -> Dict[str, Any]:
|
||||||
|
"""Lädt Browser-bezogene Konfigurationen (Channel, Autoplay-Flags, Video-Helper)."""
|
||||||
|
try:
|
||||||
|
config_dir = Path(__file__).parent.parent / "config"
|
||||||
|
browser_config_path = config_dir / "browser_config.json"
|
||||||
|
if browser_config_path.exists() and browser_config_path.stat().st_size > 0:
|
||||||
|
with open(browser_config_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data if isinstance(data, dict) else {}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Konnte Browser-Konfiguration nicht laden: {e}")
|
||||||
|
|
||||||
|
# Defaults: Chrome/Edge Channel und Autoplay erlauben
|
||||||
|
return {
|
||||||
|
"channel": "chrome", # Alternativ: "msedge"
|
||||||
|
"enable_autoplay_flag": True,
|
||||||
|
"auto_click_video": True,
|
||||||
|
"auto_mute_on_play": True,
|
||||||
|
"enable_video_enhancements": False
|
||||||
|
}
|
||||||
|
|
||||||
def start(self) -> Page:
|
def start(self) -> Page:
|
||||||
"""
|
"""
|
||||||
@ -124,8 +156,14 @@ class PlaywrightManager:
|
|||||||
'--disable-features=IsolateOrigins,site-per-process',
|
'--disable-features=IsolateOrigins,site-per-process',
|
||||||
'--disable-site-isolation-trials',
|
'--disable-site-isolation-trials',
|
||||||
])
|
])
|
||||||
|
# Autoplay-Blockade aufheben (für Video-Tests/Automation)
|
||||||
|
# Aktiviert nur, wenn konfiguriert
|
||||||
|
if self.enable_autoplay_flag if self.enable_autoplay_flag is not None else self.browser_config.get("enable_autoplay_flag", True):
|
||||||
|
browser_args.append('--autoplay-policy=no-user-gesture-required')
|
||||||
|
|
||||||
# Browser-Launch-Optionen
|
# Browser-Launch-Optionen
|
||||||
|
# Speichere Args für mögliche Erweiterungen (z.B. Gmail Fresh Browser)
|
||||||
|
self.browser_args = browser_args
|
||||||
launch_options = {
|
launch_options = {
|
||||||
"headless": self.headless,
|
"headless": self.headless,
|
||||||
"args": browser_args,
|
"args": browser_args,
|
||||||
@ -139,8 +177,33 @@ class PlaywrightManager:
|
|||||||
f'--window-position={x},{y}'
|
f'--window-position={x},{y}'
|
||||||
])
|
])
|
||||||
|
|
||||||
# Browser starten
|
# Für Chromium optional einen Channel (chrome/msedge) nutzen
|
||||||
self.browser = browser_instance.launch(**launch_options)
|
# Fallback: Ohne Channel starten, wenn der Channel nicht verfügbar ist
|
||||||
|
if self.browser_type == "chromium":
|
||||||
|
# Bevorzugung: expliziter Parameter > Konfigurationswert
|
||||||
|
channel = self.browser_channel if self.browser_channel else self.browser_config.get("channel")
|
||||||
|
tried_channel = False
|
||||||
|
if channel:
|
||||||
|
try:
|
||||||
|
logger.info(f"Starte Chromium mit Channel '{channel}' (für H.264/AAC Unterstützung)")
|
||||||
|
self.browser = browser_instance.launch(channel=channel, **launch_options)
|
||||||
|
tried_channel = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Start mit Channel '{channel}' fehlgeschlagen: {e}.")
|
||||||
|
# Zweiter Versuch mit alternativem Channel (msedge/chrome)
|
||||||
|
alt = 'msedge' if channel.startswith('chrome') else 'chrome'
|
||||||
|
try:
|
||||||
|
logger.info(f"Versuche alternativen Channel '{alt}'")
|
||||||
|
self.browser = browser_instance.launch(channel=alt, **launch_options)
|
||||||
|
tried_channel = True
|
||||||
|
except Exception as e2:
|
||||||
|
logger.warning(f"Auch alternativer Channel '{alt}' fehlgeschlagen: {e2}. Fallback auf gebundleten Chromium.")
|
||||||
|
# Fallback ohne Channel unten
|
||||||
|
if not tried_channel or self.browser is None:
|
||||||
|
self.browser = browser_instance.launch(**launch_options)
|
||||||
|
else:
|
||||||
|
# Andere Browser-Typen normal starten
|
||||||
|
self.browser = browser_instance.launch(**launch_options)
|
||||||
|
|
||||||
# Kontext-Optionen für Stealth-Modus
|
# Kontext-Optionen für Stealth-Modus
|
||||||
context_options = {
|
context_options = {
|
||||||
@ -161,6 +224,16 @@ class PlaywrightManager:
|
|||||||
|
|
||||||
# Browserkontext erstellen
|
# Browserkontext erstellen
|
||||||
self.context = self.browser.new_context(**context_options)
|
self.context = self.browser.new_context(**context_options)
|
||||||
|
|
||||||
|
# Optional: Video Stealth/Kompatibilitäts-Verbesserungen anwenden
|
||||||
|
try:
|
||||||
|
enable_video_enh = self.enable_video_enhancements if self.enable_video_enhancements is not None else self.browser_config.get("enable_video_enhancements", False)
|
||||||
|
if enable_video_enh:
|
||||||
|
from browser.video_stealth_enhancement import VideoStealthEnhancement
|
||||||
|
VideoStealthEnhancement(self.context).apply_video_stealth()
|
||||||
|
logger.info("Video-Stealth-Verbesserungen aktiviert")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Konnte Video-Stealth-Verbesserungen nicht anwenden: {e}")
|
||||||
|
|
||||||
# JavaScript-Fingerprinting-Schutz
|
# JavaScript-Fingerprinting-Schutz
|
||||||
self._apply_stealth_scripts()
|
self._apply_stealth_scripts()
|
||||||
@ -288,11 +361,65 @@ class PlaywrightManager:
|
|||||||
try:
|
try:
|
||||||
logger.info(f"Navigiere zu: {url}")
|
logger.info(f"Navigiere zu: {url}")
|
||||||
self.page.goto(url, wait_until=wait_until, timeout=timeout)
|
self.page.goto(url, wait_until=wait_until, timeout=timeout)
|
||||||
|
# Nach erfolgreicher Navigation: Versuche Videos zu starten (optional)
|
||||||
|
try:
|
||||||
|
enable_auto_kick = self.enable_autoplay_flag if self.enable_autoplay_flag is not None else self.browser_config.get("enable_autoplay_flag", True)
|
||||||
|
if enable_auto_kick:
|
||||||
|
self._kickstart_video_playback()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei der Navigation zu {url}: {e}")
|
logger.error(f"Fehler bei der Navigation zu {url}: {e}")
|
||||||
self.take_screenshot(f"navigation_error_{int(time.time())}")
|
self.take_screenshot(f"navigation_error_{int(time.time())}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _kickstart_video_playback(self) -> None:
|
||||||
|
"""
|
||||||
|
Versucht, die Wiedergabe von <video>-Elementen zu starten.
|
||||||
|
- Klickt das erste Video (simuliert User-Gesture)
|
||||||
|
- Setzt muted=true und ruft play() auf allen Videos auf
|
||||||
|
"""
|
||||||
|
if self.page is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# Kurzes Warten auf eventuelle Video-Elemente
|
||||||
|
self.page.wait_for_selector('video', timeout=1500)
|
||||||
|
except Exception:
|
||||||
|
# Kein Video gefunden – nichts zu tun
|
||||||
|
return
|
||||||
|
|
||||||
|
# Optional: Klick auf erstes Video-Element
|
||||||
|
try:
|
||||||
|
if self.auto_click_video if self.auto_click_video is not None else self.browser_config.get("auto_click_video", True):
|
||||||
|
self.page.click('video', timeout=1000)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Erzwinge (stumm) die Wiedergabe aller Videos über JS
|
||||||
|
try:
|
||||||
|
muted = self.auto_mute_on_play if self.auto_mute_on_play is not None else self.browser_config.get("auto_mute_on_play", True)
|
||||||
|
js = (
|
||||||
|
"() => {\n"
|
||||||
|
" const vids = Array.from(document.querySelectorAll('video'));\n"
|
||||||
|
" let started = 0;\n"
|
||||||
|
" for (const v of vids) {\n"
|
||||||
|
" try {\n"
|
||||||
|
" v.playsInline = true;\n"
|
||||||
|
" v.setAttribute('playsinline', '');\n"
|
||||||
|
+ (" v.muted = true;\n" if muted else " v.muted = false;\n") +
|
||||||
|
" const p = v.play();\n"
|
||||||
|
" if (p && typeof p.catch === 'function') p.catch(() => {});\n"
|
||||||
|
" started++;\n"
|
||||||
|
" } catch (e) {}\n"
|
||||||
|
" }\n"
|
||||||
|
" return started;\n"
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
started = self.page.evaluate(js)
|
||||||
|
logger.debug(f"Video-Wiedergabe gestartet für {started} Elemente")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def wait_for_selector(self, selector: str, timeout: int = 30000) -> Optional[ElementHandle]:
|
def wait_for_selector(self, selector: str, timeout: int = 30000) -> Optional[ElementHandle]:
|
||||||
"""
|
"""
|
||||||
@ -903,4 +1030,4 @@ if __name__ == "__main__":
|
|||||||
# Browser starten und zu einer Seite navigieren
|
# Browser starten und zu einer Seite navigieren
|
||||||
with PlaywrightManager(headless=False) as manager:
|
with PlaywrightManager(headless=False) as manager:
|
||||||
manager.navigate_to("https://www.instagram.com")
|
manager.navigate_to("https://www.instagram.com")
|
||||||
time.sleep(5) # Kurze Pause zum Anzeigen der Seite
|
time.sleep(5) # Kurze Pause zum Anzeigen der Seite
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"current_version": "1.0.0",
|
"current_version": "1.0.0",
|
||||||
"last_check": "2025-07-19T01:32:14.527285",
|
"last_check": "2025-10-18T21:12:40.353264",
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"auto_check": true,
|
"auto_check": true,
|
||||||
"auto_download": false
|
"auto_download": false
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"channel": "chrome",
|
||||||
|
"enable_autoplay_flag": true,
|
||||||
|
"auto_click_video": true,
|
||||||
|
"auto_mute_on_play": true,
|
||||||
|
"enable_video_enhancements": false
|
||||||
|
}
|
||||||
|
|||||||
Binäre Datei nicht angezeigt.
@ -43,8 +43,15 @@ def setup_logger(name="main", level=logging.DEBUG):
|
|||||||
|
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
|
|
||||||
# Datehandler
|
# Dateihandler: Stelle sicher, dass das Log-Verzeichnis existiert
|
||||||
log_file = os.path.join("logs", f"{name}.log")
|
log_dir = os.path.join("logs")
|
||||||
|
try:
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
except Exception:
|
||||||
|
# Falls das Anlegen fehlschlägt, weiter mit aktuellem Verzeichnis
|
||||||
|
pass
|
||||||
|
|
||||||
|
log_file = os.path.join(log_dir, f"{name}.log")
|
||||||
file_handler = logging.FileHandler(log_file)
|
file_handler = logging.FileHandler(log_file)
|
||||||
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren