259 Zeilen
9.8 KiB
Python
259 Zeilen
9.8 KiB
Python
"""
|
|
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 |