Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,259 @@
"""
Detect Rate Limit Use Case - Erkennt Rate Limits und reagiert entsprechend
"""
import logging
import time
from typing import Any, Dict, Optional, Tuple
from datetime import datetime
from domain.services.rate_limit_service import IRateLimitService
from domain.value_objects.action_timing import ActionTiming, ActionType
from domain.entities.error_event import ErrorEvent, ErrorType, ErrorContext
from domain.entities.rate_limit_policy import RateLimitPolicy
logger = logging.getLogger("detect_rate_limit_use_case")
class DetectRateLimitUseCase:
"""
Use Case für Rate Limit Erkennung und Reaktion.
Analysiert Responses, erkennt Rate Limits und implementiert Backoff-Strategien.
"""
def __init__(self, rate_limit_service: IRateLimitService):
self.rate_limit_service = rate_limit_service
self.detection_patterns = {
'instagram': [
"Bitte warte einige Minuten",
"Please wait a few minutes",
"Try again later",
"Versuche es später erneut",
"too many requests",
"zu viele Anfragen",
"We're sorry, but something went wrong",
"temporarily blocked",
"vorübergehend gesperrt",
"Wir haben deine Anfrage eingeschränkt"
],
'tiktok': [
"Too many attempts",
"Zu viele Versuche",
"Please slow down",
"rate limited",
"Try again in"
],
'general': [
"429",
"rate limit",
"throttled",
"quota exceeded"
]
}
def execute(self, response: Any, context: Optional[Dict[str, Any]] = None) -> Tuple[bool, Optional[ErrorEvent]]:
"""
Analysiert eine Response auf Rate Limiting.
Args:
response: HTTP Response, Page Content oder Error Message
context: Zusätzlicher Kontext (platform, action_type, etc.)
Returns:
Tuple aus (is_rate_limited, error_event)
"""
# Erkenne Rate Limit
is_rate_limited = self._detect_rate_limit(response, context)
if not is_rate_limited:
return False, None
# Erstelle Error Event
error_event = self._create_error_event(response, context)
# Handle Rate Limit
self._handle_rate_limit(error_event, context)
return True, error_event
def _detect_rate_limit(self, response: Any, context: Optional[Dict[str, Any]] = None) -> bool:
"""Erkennt ob Response auf Rate Limiting hindeutet"""
# Nutze Service für Basis-Detection
if self.rate_limit_service.detect_rate_limit(response):
return True
# Erweiterte Detection basierend auf Platform
platform = context.get('platform', 'general') if context else 'general'
patterns = self.detection_patterns.get(platform, []) + self.detection_patterns['general']
# String-basierte Erkennung
response_text = self._extract_text(response)
if response_text:
response_lower = response_text.lower()
for pattern in patterns:
if pattern.lower() in response_lower:
logger.info(f"Rate limit detected: '{pattern}' found in response")
return True
# Status Code Erkennung
status = self._extract_status(response)
if status in [429, 420, 503]: # Common rate limit codes
logger.info(f"Rate limit detected: HTTP {status}")
return True
# Timing-basierte Erkennung
if context and 'timing' in context:
timing = context['timing']
if isinstance(timing, ActionTiming):
# Sehr schnelle Fehler können auf Rate Limits hindeuten
if not timing.success and timing.duration < 0.5:
logger.warning("Possible rate limit: Fast failure detected")
return True
return False
def _extract_text(self, response: Any) -> Optional[str]:
"""Extrahiert Text aus verschiedenen Response-Typen"""
if isinstance(response, str):
return response
elif hasattr(response, 'text'):
try:
return response.text
except:
pass
elif hasattr(response, 'content'):
try:
if callable(response.content):
return response.content()
return str(response.content)
except:
pass
elif hasattr(response, 'message'):
return str(response.message)
return str(response) if response else None
def _extract_status(self, response: Any) -> Optional[int]:
"""Extrahiert Status Code aus Response"""
if hasattr(response, 'status'):
return response.status
elif hasattr(response, 'status_code'):
return response.status_code
elif hasattr(response, 'code'):
try:
return int(response.code)
except:
pass
return None
def _create_error_event(self, response: Any, context: Optional[Dict[str, Any]] = None) -> ErrorEvent:
"""Erstellt Error Event für Rate Limit"""
error_context = ErrorContext(
url=context.get('url') if context else None,
action=context.get('action_type').value if context and 'action_type' in context else None,
step_name=context.get('step_name') if context else None,
screenshot_path=context.get('screenshot_path') if context else None,
additional_data={
'platform': context.get('platform') if context else None,
'response_text': self._extract_text(response)[:500] if self._extract_text(response) else None,
'status_code': self._extract_status(response),
'timestamp': datetime.now().isoformat()
}
)
return ErrorEvent(
error_type=ErrorType.RATE_LIMIT,
error_message="Rate limit detected",
context=error_context,
platform=context.get('platform') if context else None,
session_id=context.get('session_id') if context else None,
correlation_id=context.get('correlation_id') if context else None
)
def _handle_rate_limit(self, error_event: ErrorEvent, context: Optional[Dict[str, Any]] = None) -> None:
"""Behandelt erkanntes Rate Limit"""
# Extrahiere Wait-Zeit aus Response wenn möglich
wait_time = self._extract_wait_time(error_event.context.additional_data.get('response_text', ''))
if not wait_time:
# Verwende exponentielles Backoff
retry_count = context.get('retry_count', 0) if context else 0
wait_time = self._calculate_backoff(retry_count)
logger.warning(f"Rate limit detected - waiting {wait_time}s before retry")
# Update Rate Limit Policy für zukünftige Requests
if context and 'action_type' in context:
action_type = context['action_type']
current_policy = self.rate_limit_service.get_policy(action_type)
# Erhöhe Delays temporär
updated_policy = RateLimitPolicy(
min_delay=min(current_policy.min_delay * 1.5, 10.0),
max_delay=min(current_policy.max_delay * 2, 60.0),
adaptive=current_policy.adaptive,
backoff_multiplier=min(current_policy.backoff_multiplier * 1.2, 3.0),
max_retries=current_policy.max_retries
)
self.rate_limit_service.update_policy(action_type, updated_policy)
def _extract_wait_time(self, response_text: str) -> Optional[float]:
"""Versucht Wait-Zeit aus Response zu extrahieren"""
if not response_text:
return None
import re
# Patterns für Zeitangaben
patterns = [
r'wait (\d+) seconds',
r'warte (\d+) Sekunden',
r'try again in (\d+)s',
r'retry after (\d+)',
r'(\d+) Minuten warten',
r'wait (\d+) minutes'
]
for pattern in patterns:
match = re.search(pattern, response_text.lower())
if match:
value = int(match.group(1))
# Konvertiere Minuten zu Sekunden wenn nötig
if 'minute' in pattern or 'minuten' in pattern:
value *= 60
return float(min(value, 300)) # Max 5 Minuten
return None
def _calculate_backoff(self, retry_count: int) -> float:
"""Berechnet exponentielles Backoff"""
base_wait = 5.0 # 5 Sekunden Basis
max_wait = 300.0 # Max 5 Minuten
# Exponentielles Backoff mit Jitter
wait_time = min(base_wait * (2 ** retry_count), max_wait)
# Füge Jitter hinzu (±20%)
import random
jitter = wait_time * 0.2 * (random.random() - 0.5)
return wait_time + jitter
def analyze_patterns(self, platform: str, timeframe_hours: int = 24) -> Dict[str, Any]:
"""Analysiert Rate Limit Muster für eine Plattform"""
# Diese Methode würde mit einem Analytics Repository arbeiten
# um Muster in Rate Limits zu erkennen
analysis = {
'platform': platform,
'timeframe_hours': timeframe_hours,
'peak_times': [],
'safe_times': [],
'recommended_delays': {},
'incidents': 0
}
# TODO: Implementiere mit Analytics Repository
return analysis