From fc64184f5f5f9d8d302e5d9ce09833d02fa340cd Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Thu, 13 Nov 2025 06:16:46 +0300 Subject: [PATCH] YouTubeDataUnparsable --- src/transcript_extractor.py | 132 +++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 46 deletions(-) diff --git a/src/transcript_extractor.py b/src/transcript_extractor.py index 3824f87..6759832 100644 --- a/src/transcript_extractor.py +++ b/src/transcript_extractor.py @@ -50,10 +50,14 @@ class TranscriptExtractor: # FlareSolverr ayarları 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.flaresolverr_available = False # Test sonucu 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() + if not self.flaresolverr_available: + logger.warning("[FLARESOLVERR] ⚠ Test başarısız, ancak gerçek isteklerde tekrar denenilecek") else: 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) if test_response.status_code == 405: # Method Not Allowed normal (GET yerine POST bekliyor) logger.info("[FLARESOLVERR] ✅ FlareSolverr erişilebilir") + self.flaresolverr_available = True return True else: logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr yanıtı beklenmedik: {test_response.status_code}") + self.flaresolverr_available = False return False except Exception as e: logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr test edilemedi: {e}") - logger.warning(f"[FLARESOLVERR] FlareSolverr devre dışı bırakılıyor") - self.use_flaresolverr = False + logger.warning(f"[FLARESOLVERR] Test başarısız, ancak gerçek isteklerde tekrar denenilecek") + self.flaresolverr_available = False + # use_flaresolverr'ı False yapma, gerçek isteklerde tekrar dene return False 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""" # FlareSolverr kullanılıyorsa ve YouTube URL'si ise 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) if flaresolverr_response: + logger.debug(f"[FLARESOLVERR] ✅ FlareSolverr başarılı, response döndürülüyor") # FlareSolverr response'unu requests.Response'a benzet class PatchedResponse: def __init__(self, flaresolverr_response): @@ -192,7 +200,7 @@ class TranscriptExtractor: return PatchedResponse(flaresolverr_response) 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) headers = kwargs.get('headers', {}) @@ -205,9 +213,10 @@ class TranscriptExtractor: """requests.Session.post'i patch et - header'ları ekle ve FlareSolverr kullan""" # FlareSolverr kullanılıyorsa ve YouTube URL'si ise 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) if flaresolverr_response: + logger.debug(f"[FLARESOLVERR] ✅ FlareSolverr başarılı, response döndürülüyor") class PatchedResponse: def __init__(self, flaresolverr_response): self.status_code = flaresolverr_response.status_code @@ -232,7 +241,7 @@ class TranscriptExtractor: return PatchedResponse(flaresolverr_response) 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) headers = kwargs.get('headers', {}) @@ -326,13 +335,15 @@ class TranscriptExtractor: logger.debug(f"[RATE_LIMIT] İstek kaydedildi (Toplam aktif istek: {len(self.request_times)})") 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: video_id: YouTube video ID languages: Öncelik sırasına göre dil listesi + max_retries: Maksimum retry sayısı (YouTubeDataUnparsable için) Returns: Transcript listesi veya None @@ -342,43 +353,72 @@ class TranscriptExtractor: # Rate limiting kontrolü self._check_rate_limit() - try: - logger.debug(f"[TRANSCRIPT] YouTube Transcript API çağrısı yapılıyor: video_id={video_id}") - - # YouTube Transcript API kullanımı (yeni versiyon) - # API instance oluştur ve fetch() metodunu kullan - api = YouTubeTranscriptApi() - fetched_transcript = api.fetch(video_id, languages=languages) - - # 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)") - - 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 + # Retry mekanizması + for attempt in range(max_retries + 1): + try: + if attempt > 0: + # Retry için bekleme (YouTube'un HTML'i yenilemesi için) + wait_time = 5 + (attempt * 3) # 5, 8, 11 saniye + logger.warning(f"[TRANSCRIPT] ⚠️ Retry {attempt}/{max_retries} - {wait_time} saniye bekleniyor...") + time.sleep(wait_time) + # Rate limit kontrolü tekrar yap + self._check_rate_limit() - 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]}") + logger.debug(f"[TRANSCRIPT] YouTube Transcript API çağrısı yapılıyor: video_id={video_id} (Deneme: {attempt + 1}/{max_retries + 1})") - # Ç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") - - return None + # YouTube Transcript API kullanımı (yeni versiyon) + # API instance oluştur ve fetch() metodunu kullan + api = YouTubeTranscriptApi() + fetched_transcript = api.fetch(video_id, languages=languages) + + # 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