YouTubeDataUnparsable

This commit is contained in:
salvacybersec
2025-11-13 06:16:46 +03:00
parent c120467f55
commit fc64184f5f

View File

@@ -50,10 +50,14 @@ class TranscriptExtractor:
# FlareSolverr ayarları # FlareSolverr ayarları
self.flaresolverr_url = flaresolverr_url or os.getenv('FLARESOLVERR_URL', 'http://192.168.1.27:8191/v1') self.flaresolverr_url = flaresolverr_url or os.getenv('FLARESOLVERR_URL', 'http://192.168.1.27:8191/v1')
self.use_flaresolverr = bool(self.flaresolverr_url) self.use_flaresolverr = bool(self.flaresolverr_url)
self.flaresolverr_available = False # Test sonucu
if self.use_flaresolverr: if self.use_flaresolverr:
logger.info(f"[FLARESOLVERR] FlareSolverr etkin: {self.flaresolverr_url}") logger.info(f"[FLARESOLVERR] FlareSolverr URL ayarlandı: {self.flaresolverr_url}")
# Test et ama başarısız olsa bile kullanmayı dene (test sırasında erişilemez olabilir)
self._test_flaresolverr() self._test_flaresolverr()
if not self.flaresolverr_available:
logger.warning("[FLARESOLVERR] ⚠ Test başarısız, ancak gerçek isteklerde tekrar denenilecek")
else: else:
logger.info("[FLARESOLVERR] FlareSolverr devre dışı") logger.info("[FLARESOLVERR] FlareSolverr devre dışı")
@@ -68,14 +72,17 @@ class TranscriptExtractor:
test_response = requests.get(f"{self.flaresolverr_url.replace('/v1', '')}/v1", timeout=5) test_response = requests.get(f"{self.flaresolverr_url.replace('/v1', '')}/v1", timeout=5)
if test_response.status_code == 405: # Method Not Allowed normal (GET yerine POST bekliyor) if test_response.status_code == 405: # Method Not Allowed normal (GET yerine POST bekliyor)
logger.info("[FLARESOLVERR] ✅ FlareSolverr erişilebilir") logger.info("[FLARESOLVERR] ✅ FlareSolverr erişilebilir")
self.flaresolverr_available = True
return True return True
else: else:
logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr yanıtı beklenmedik: {test_response.status_code}") logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr yanıtı beklenmedik: {test_response.status_code}")
self.flaresolverr_available = False
return False return False
except Exception as e: except Exception as e:
logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr test edilemedi: {e}") logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr test edilemedi: {e}")
logger.warning(f"[FLARESOLVERR] FlareSolverr devre dışı bırakılıyor") logger.warning(f"[FLARESOLVERR] Test başarısız, ancak gerçek isteklerde tekrar denenilecek")
self.use_flaresolverr = False self.flaresolverr_available = False
# use_flaresolverr'ı False yapma, gerçek isteklerde tekrar dene
return False return False
def _make_flaresolverr_request(self, url: str, method: str = 'GET', **kwargs) -> Optional: def _make_flaresolverr_request(self, url: str, method: str = 'GET', **kwargs) -> Optional:
@@ -164,9 +171,10 @@ class TranscriptExtractor:
"""requests.Session.get'i patch et - header'ları ekle ve FlareSolverr kullan""" """requests.Session.get'i patch et - header'ları ekle ve FlareSolverr kullan"""
# FlareSolverr kullanılıyorsa ve YouTube URL'si ise # FlareSolverr kullanılıyorsa ve YouTube URL'si ise
if extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url): if extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url):
logger.info(f"[FLARESOLVERR] YouTube isteği FlareSolverr üzerinden: {url[:50]}...") logger.debug(f"[FLARESOLVERR] YouTube isteği FlareSolverr üzerinden deneniyor: {url[:50]}...")
flaresolverr_response = extractor_instance._make_flaresolverr_request(url, 'GET', **kwargs) flaresolverr_response = extractor_instance._make_flaresolverr_request(url, 'GET', **kwargs)
if flaresolverr_response: if flaresolverr_response:
logger.debug(f"[FLARESOLVERR] ✅ FlareSolverr başarılı, response döndürülüyor")
# FlareSolverr response'unu requests.Response'a benzet # FlareSolverr response'unu requests.Response'a benzet
class PatchedResponse: class PatchedResponse:
def __init__(self, flaresolverr_response): def __init__(self, flaresolverr_response):
@@ -192,7 +200,7 @@ class TranscriptExtractor:
return PatchedResponse(flaresolverr_response) return PatchedResponse(flaresolverr_response)
else: else:
logger.warning(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor") logger.debug(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor")
# Normal istek (header'ları ekle) # Normal istek (header'ları ekle)
headers = kwargs.get('headers', {}) headers = kwargs.get('headers', {})
@@ -205,9 +213,10 @@ class TranscriptExtractor:
"""requests.Session.post'i patch et - header'ları ekle ve FlareSolverr kullan""" """requests.Session.post'i patch et - header'ları ekle ve FlareSolverr kullan"""
# FlareSolverr kullanılıyorsa ve YouTube URL'si ise # FlareSolverr kullanılıyorsa ve YouTube URL'si ise
if extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url): if extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url):
logger.info(f"[FLARESOLVERR] YouTube POST isteği FlareSolverr üzerinden: {url[:50]}...") logger.debug(f"[FLARESOLVERR] YouTube POST isteği FlareSolverr üzerinden deneniyor: {url[:50]}...")
flaresolverr_response = extractor_instance._make_flaresolverr_request(url, 'POST', **kwargs) flaresolverr_response = extractor_instance._make_flaresolverr_request(url, 'POST', **kwargs)
if flaresolverr_response: if flaresolverr_response:
logger.debug(f"[FLARESOLVERR] ✅ FlareSolverr başarılı, response döndürülüyor")
class PatchedResponse: class PatchedResponse:
def __init__(self, flaresolverr_response): def __init__(self, flaresolverr_response):
self.status_code = flaresolverr_response.status_code self.status_code = flaresolverr_response.status_code
@@ -232,7 +241,7 @@ class TranscriptExtractor:
return PatchedResponse(flaresolverr_response) return PatchedResponse(flaresolverr_response)
else: else:
logger.warning(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor") logger.debug(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor")
# Normal istek (header'ları ekle) # Normal istek (header'ları ekle)
headers = kwargs.get('headers', {}) headers = kwargs.get('headers', {})
@@ -326,13 +335,15 @@ class TranscriptExtractor:
logger.debug(f"[RATE_LIMIT] İstek kaydedildi (Toplam aktif istek: {len(self.request_times)})") logger.debug(f"[RATE_LIMIT] İstek kaydedildi (Toplam aktif istek: {len(self.request_times)})")
def fetch_transcript(self, video_id: str, def fetch_transcript(self, video_id: str,
languages: List[str] = ['en']) -> Optional[List[Dict]]: languages: List[str] = ['en'],
max_retries: int = 2) -> Optional[List[Dict]]:
""" """
Transcript çıkar (sync) Transcript çıkar (sync) - Retry mekanizması ile
Args: Args:
video_id: YouTube video ID video_id: YouTube video ID
languages: Öncelik sırasına göre dil listesi languages: Öncelik sırasına göre dil listesi
max_retries: Maksimum retry sayısı (YouTubeDataUnparsable için)
Returns: Returns:
Transcript listesi veya None Transcript listesi veya None
@@ -342,43 +353,72 @@ class TranscriptExtractor:
# Rate limiting kontrolü # Rate limiting kontrolü
self._check_rate_limit() self._check_rate_limit()
try: # Retry mekanizması
logger.debug(f"[TRANSCRIPT] YouTube Transcript API çağrısı yapılıyor: video_id={video_id}") for attempt in range(max_retries + 1):
try:
# YouTube Transcript API kullanımı (yeni versiyon) if attempt > 0:
# API instance oluştur ve fetch() metodunu kullan # Retry için bekleme (YouTube'un HTML'i yenilemesi için)
api = YouTubeTranscriptApi() wait_time = 5 + (attempt * 3) # 5, 8, 11 saniye
fetched_transcript = api.fetch(video_id, languages=languages) logger.warning(f"[TRANSCRIPT] ⚠️ Retry {attempt}/{max_retries} - {wait_time} saniye bekleniyor...")
time.sleep(wait_time)
# Eski formatı döndürmek için to_raw_data() kullan # Rate limit kontrolü tekrar yap
# Format: [{'text': '...', 'start': 1.36, 'duration': 1.68}, ...] self._check_rate_limit()
transcript = fetched_transcript.to_raw_data()
transcript_count = len(transcript) if transcript else 0
logger.info(f"[TRANSCRIPT] ✅ Video {video_id} transcript'i başarıyla çıkarıldı ({transcript_count} segment)")
return transcript
except Exception as e:
error_msg = str(e)
error_type = type(e).__name__
logger.error(f"[TRANSCRIPT] ❌ Video {video_id} transcript çıkarımı başarısız: {error_type} - {error_msg[:200]}")
# IP blocking hatası tespit edilirse işaretle
if "blocking" in error_msg.lower() or "blocked" in error_msg.lower() or "IP" in error_msg or "IpBlocked" in error_type:
self.last_blocked_time = time.time()
self.block_count += 1
wait_time = 300 + (self.block_count * 60) # 5 dakika + (blocking sayısı * 1 dakika)
wait_time = min(wait_time, 1800) # Maksimum 30 dakika
logger.error(f"[TRANSCRIPT] 🚫 IP blocking tespit edildi! Video: {video_id}") logger.debug(f"[TRANSCRIPT] YouTube Transcript API çağrısı yapılıyor: video_id={video_id} (Deneme: {attempt + 1}/{max_retries + 1})")
logger.error(f"[TRANSCRIPT] Toplam blocking sayısı: {self.block_count}, Sonraki isteklerde {wait_time} saniye ({wait_time/60:.1f} dakika) bekleme yapılacak")
logger.warning(f"[TRANSCRIPT] IP blocking detayları: {error_msg[:500]}")
# Çok fazla blocking varsa uyar # YouTube Transcript API kullanımı (yeni versiyon)
if self.block_count >= 3: # API instance oluştur ve fetch() metodunu kullan
logger.error(f"[TRANSCRIPT] ⚠️ UYARI: {self.block_count} kez IP blocking alındı! YouTube IP'nizi geçici olarak engellemiş olabilir.") api = YouTubeTranscriptApi()
logger.error(f"[TRANSCRIPT] Öneriler: 1) Daha uzun bekleme (30+ dakika), 2) Farklı IP kullan, 3) Proxy/VPN kullan") fetched_transcript = api.fetch(video_id, languages=languages)
return None # Eski formatı döndürmek için to_raw_data() kullan
# Format: [{'text': '...', 'start': 1.36, 'duration': 1.68}, ...]
transcript = fetched_transcript.to_raw_data()
transcript_count = len(transcript) if transcript else 0
logger.info(f"[TRANSCRIPT] ✅ Video {video_id} transcript'i başarıyla çıkarıldı ({transcript_count} segment, Deneme: {attempt + 1})")
return transcript
except Exception as e:
error_msg = str(e)
error_type = type(e).__name__
# YouTubeDataUnparsable hatası için retry yap
if "YouTubeDataUnparsable" in error_type or "Unparsable" in error_type:
if attempt < max_retries:
logger.warning(f"[TRANSCRIPT] ⚠️ Video {video_id} parse hatası (Deneme {attempt + 1}/{max_retries + 1}): {error_type}")
logger.warning(f"[TRANSCRIPT] Hata mesajı: {error_msg[:300]}")
logger.warning(f"[TRANSCRIPT] Retry yapılacak...")
continue # Retry yap
else:
logger.error(f"[TRANSCRIPT] ❌ Video {video_id} parse hatası - Tüm denemeler başarısız: {error_type}")
logger.error(f"[TRANSCRIPT] Hata detayları: {error_msg[:500]}")
logger.error(f"[TRANSCRIPT] Bu video için transcript çıkarılamıyor (YouTube HTML yapısı değişmiş olabilir)")
return None
# Diğer hatalar için normal işlem
logger.error(f"[TRANSCRIPT] ❌ Video {video_id} transcript çıkarımı başarısız: {error_type} - {error_msg[:200]}")
# IP blocking hatası tespit edilirse işaretle
if "blocking" in error_msg.lower() or "blocked" in error_msg.lower() or "IP" in error_msg or "IpBlocked" in error_type:
self.last_blocked_time = time.time()
self.block_count += 1
wait_time = 300 + (self.block_count * 60) # 5 dakika + (blocking sayısı * 1 dakika)
wait_time = min(wait_time, 1800) # Maksimum 30 dakika
logger.error(f"[TRANSCRIPT] 🚫 IP blocking tespit edildi! Video: {video_id}")
logger.error(f"[TRANSCRIPT] Toplam blocking sayısı: {self.block_count}, Sonraki isteklerde {wait_time} saniye ({wait_time/60:.1f} dakika) bekleme yapılacak")
logger.warning(f"[TRANSCRIPT] IP blocking detayları: {error_msg[:500]}")
# Çok fazla blocking varsa uyar
if self.block_count >= 3:
logger.error(f"[TRANSCRIPT] ⚠️ UYARI: {self.block_count} kez IP blocking alındı! YouTube IP'nizi geçici olarak engellemiş olabilir.")
logger.error(f"[TRANSCRIPT] Öneriler: 1) Daha uzun bekleme (30+ dakika), 2) Farklı IP kullan, 3) Proxy/VPN kullan")
# Retry yapılmayacak hatalar için direkt dön
return None
# Tüm retry'lar başarısız
return None