This commit is contained in:
salvacybersec
2025-11-13 07:09:57 +03:00
parent cb257f84cd
commit 5e45b06365

View File

@@ -92,7 +92,13 @@ class TranscriptExtractor:
def _make_flaresolverr_request(self, url: str, method: str = 'GET', **kwargs) -> Optional: def _make_flaresolverr_request(self, url: str, method: str = 'GET', **kwargs) -> Optional:
"""FlareSolverr üzerinden istek yap""" """FlareSolverr üzerinden istek yap"""
if not self.use_flaresolverr: # Availability kontrolü: Eğer FlareSolverr erişilemiyorsa hiç deneme
if not self.use_flaresolverr or not self.flaresolverr_url:
return None
# Eğer daha önce erişilemediği tespit edildiyse, tekrar deneme
if hasattr(self, 'flaresolverr_available') and not self.flaresolverr_available:
logger.debug(f"[FLARESOLVERR] FlareSolverr erişilemiyor (available=False), istek atlanıyor")
return None return None
try: try:
@@ -104,7 +110,7 @@ class TranscriptExtractor:
flaresolverr_payload = { flaresolverr_payload = {
"cmd": "request.post", "cmd": "request.post",
"url": url, "url": url,
"maxTimeout": 60000, # 60 saniye timeout "maxTimeout": 90000, # 90 saniye timeout (FlareSolverr'ın tarayıcı açması zaman alabilir)
} }
# POST data'sını ekle # POST data'sını ekle
@@ -116,7 +122,7 @@ class TranscriptExtractor:
flaresolverr_payload = { flaresolverr_payload = {
"cmd": "request.get", "cmd": "request.get",
"url": url, "url": url,
"maxTimeout": 60000, # 60 saniye timeout "maxTimeout": 90000, # 90 saniye timeout (FlareSolverr'ın tarayıcı açması zaman alabilir)
} }
# Header'ları ekle # Header'ları ekle
@@ -126,11 +132,12 @@ class TranscriptExtractor:
logger.debug(f"[FLARESOLVERR] İstek gönderiliyor: {url[:50]}...") logger.debug(f"[FLARESOLVERR] İstek gönderiliyor: {url[:50]}...")
# FlareSolverr timeout'unu kısalt (erişilemezse hemen normal isteğe geç) # FlareSolverr timeout'u (FlareSolverr'ın tarayıcı açması zaman alabilir)
# maxTimeout 90 saniye olduğu için, HTTP timeout'u da biraz daha uzun tutuyoruz
response = requests.post( response = requests.post(
self.flaresolverr_url, self.flaresolverr_url,
json=flaresolverr_payload, json=flaresolverr_payload,
timeout=10 # 10 saniye timeout (erişilemezse hemen normal isteğe geç) timeout=95 # 95 saniye timeout (maxTimeout'dan biraz fazla)
) )
if response.status_code == 200: if response.status_code == 200:
@@ -147,11 +154,24 @@ class TranscriptExtractor:
def __init__(self, status_code, text, headers, url, is_post=False): def __init__(self, status_code, text, headers, url, is_post=False):
self.status_code = status_code self.status_code = status_code
self.text = text self.text = text
self.content = text.encode('utf-8') if isinstance(text, str) else text # Encoding'i doğru ayarla (UTF-8)
self.headers = headers if headers else {} if isinstance(text, str):
self.content = text.encode('utf-8')
self.encoding = 'utf-8'
else:
self.content = text
self.encoding = 'utf-8'
# Header'ları dict olarak ayarla (requests.Response uyumluluğu için)
if isinstance(headers, dict):
self.headers = headers
else:
self.headers = dict(headers) if headers else {}
self.url = url self.url = url
self.ok = 200 <= status_code < 300 self.ok = 200 <= status_code < 300
self.is_post = is_post # POST isteği mi? self.is_post = is_post # POST isteği mi?
# requests.Response uyumluluğu için ek özellikler
self.reason = 'OK' if self.ok else 'Error'
self.apparent_encoding = 'utf-8'
def json(self): def json(self):
import json import json
@@ -173,22 +193,30 @@ class TranscriptExtractor:
content_size = len(response_content) content_size = len(response_content)
logger.info(f"[FLARESOLVERR] ✅ İstek başarılı: HTTP {status_code}, {content_size} byte içerik ({'POST' if is_post_request else 'GET'})") logger.info(f"[FLARESOLVERR] ✅ İstek başarılı: HTTP {status_code}, {content_size} byte içerik ({'POST' if is_post_request else 'GET'})")
# Debug: İçeriğin ilk 500 karakterini logla # Debug: İçeriğin ilk 2000 karakterini logla (daha detaylı analiz için)
logger.debug(f"[FLARESOLVERR] İçerik önizleme (ilk 500 karakter): {response_content[:500]}") logger.debug(f"[FLARESOLVERR] İçerik önizleme (ilk 2000 karakter): {response_content[:2000]}")
# GET istekleri için HTML analizi (POST istekleri genellikle JSON) # GET istekleri için HTML analizi (POST istekleri genellikle JSON)
transcript_urls = []
yt_initial_player_response_found = False
if not is_post_request: if not is_post_request:
import re import re
if 'ytInitialPlayerResponse' in response_content or 'ytInitialData' in response_content: # YouTube player response kontrolü
logger.debug(f"[FLARESOLVERR] ✅ YouTube player response bulundu HTML'de") if 'ytInitialPlayerResponse' in response_content:
logger.debug(f"[FLARESOLVERR] ✅ ytInitialPlayerResponse bulundu HTML'de")
yt_initial_player_response_found = True
elif 'ytInitialData' in response_content:
logger.debug(f"[FLARESOLVERR] ✅ ytInitialData bulundu HTML'de")
yt_initial_player_response_found = True
else: else:
logger.warning(f"[FLARESOLVERR] ⚠️ YouTube player response bulunamadı HTML'de") logger.warning(f"[FLARESOLVERR] ⚠️ YouTube player response bulunamadı HTML'de")
# Debug: Transcript endpoint URL'lerini ara # Transcript endpoint URL'lerini ara (daha kapsamlı pattern)
transcript_urls = re.findall(r'https?://[^"\s]+timedtext[^"\s]*', response_content) transcript_urls = re.findall(r'https?://[^"\s<>]+timedtext[^"\s<>]*', response_content)
if transcript_urls: if transcript_urls:
logger.debug(f"[FLARESOLVERR] ✅ Transcript URL'leri bulundu: {len(transcript_urls)} adet") logger.debug(f"[FLARESOLVERR] ✅ Transcript URL'leri bulundu: {len(transcript_urls)} adet")
logger.debug(f"[FLARESOLVERR] İlk transcript URL: {transcript_urls[0][:100]}...") logger.debug(f"[FLARESOLVERR] İlk transcript URL: {transcript_urls[0][:150]}...")
else: else:
logger.warning(f"[FLARESOLVERR] ⚠️ Transcript URL'leri bulunamadı HTML'de") logger.warning(f"[FLARESOLVERR] ⚠️ Transcript URL'leri bulunamadı HTML'de")
else: else:
@@ -200,7 +228,36 @@ class TranscriptExtractor:
except: except:
logger.warning(f"[FLARESOLVERR] ⚠️ POST response JSON değil, HTML olabilir") logger.warning(f"[FLARESOLVERR] ⚠️ POST response JSON değil, HTML olabilir")
return FlareSolverrResponse(status_code, response_content, headers, url, is_post=is_post_request) # Response objesine transcript URL'lerini ve analiz sonuçlarını ekle (alternatif parse için)
response_obj = FlareSolverrResponse(status_code, response_content, headers, url, is_post=is_post_request)
response_obj.transcript_urls = transcript_urls # Alternatif parse için sakla
response_obj.yt_initial_player_response_found = yt_initial_player_response_found
# Debug: FlareSolverr response'unu geçici dosyaya kaydet (YouTubeDataUnparsable hatası için)
if not is_post_request and logger.isEnabledFor(logging.DEBUG):
try:
import tempfile
import os
from pathlib import Path
# Video ID'yi URL'den çıkar
video_id_match = None
if 'watch?v=' in url:
video_id_match = url.split('watch?v=')[1].split('&')[0]
if video_id_match:
debug_dir = Path('output') / 'flaresolverr_debug'
debug_dir.mkdir(parents=True, exist_ok=True)
debug_file = debug_dir / f"flaresolverr_response_{video_id_match}_{int(time.time())}.html"
with open(debug_file, 'w', encoding='utf-8') as f:
f.write(response_content)
logger.debug(f"[FLARESOLVERR] Debug: Response kaydedildi: {debug_file}")
except Exception as debug_err:
logger.debug(f"[FLARESOLVERR] Debug dosyası kaydedilemedi: {debug_err}")
return response_obj
else: else:
error = result.get('message', 'Unknown error') error = result.get('message', 'Unknown error')
logger.error(f"[FLARESOLVERR] ❌ FlareSolverr hatası: {error}") logger.error(f"[FLARESOLVERR] ❌ FlareSolverr hatası: {error}")
@@ -209,6 +266,16 @@ class TranscriptExtractor:
logger.error(f"[FLARESOLVERR] ❌ FlareSolverr HTTP hatası: {response.status_code}") logger.error(f"[FLARESOLVERR] ❌ FlareSolverr HTTP hatası: {response.status_code}")
return None return None
except requests.exceptions.Timeout:
logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr timeout (95 saniye), normal isteğe geçiliyor")
return None
except requests.exceptions.ReadTimeout:
logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr read timeout, normal isteğe geçiliyor")
return None
except requests.exceptions.ConnectTimeout:
logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr connect timeout (erişilemiyor), devre dışı bırakılıyor")
self.flaresolverr_available = False # Erişilemediği için devre dışı bırak
return None
except Exception as e: except Exception as e:
logger.error(f"[FLARESOLVERR] ❌ FlareSolverr istek hatası: {type(e).__name__} - {str(e)[:200]}") logger.error(f"[FLARESOLVERR] ❌ FlareSolverr istek hatası: {type(e).__name__} - {str(e)[:200]}")
return None return None
@@ -233,11 +300,14 @@ class TranscriptExtractor:
is_video_page = ('youtube.com/watch' in url or 'youtu.be/' in url) and '/api/' not in url is_video_page = ('youtube.com/watch' in url or 'youtu.be/' in url) and '/api/' not in url
# FlareSolverr kullanılıyorsa ve video sayfası ise # FlareSolverr kullanılıyorsa ve video sayfası ise
if extractor_instance.use_flaresolverr and is_video_page: if extractor_instance.use_flaresolverr and is_video_page and extractor_instance.flaresolverr_available:
logger.debug(f"[FLARESOLVERR] Video sayfası FlareSolverr üzerinden deneniyor: {url[:50]}...") logger.debug(f"[FLARESOLVERR] Video sayfası 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") logger.debug(f"[FLARESOLVERR] ✅ FlareSolverr başarılı, response döndürülüyor")
# FlareSolverr response'unu instance'a kaydet (YouTubeDataUnparsable hatası için analiz)
extractor_instance._last_flaresolverr_response = flaresolverr_response
# 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):
@@ -247,6 +317,9 @@ class TranscriptExtractor:
self.headers = flaresolverr_response.headers self.headers = flaresolverr_response.headers
self.url = flaresolverr_response.url self.url = flaresolverr_response.url
self.ok = 200 <= self.status_code < 300 self.ok = 200 <= self.status_code < 300
# FlareSolverr response metadata'sını sakla
self.flaresolverr_transcript_urls = getattr(flaresolverr_response, 'transcript_urls', [])
self.flaresolverr_yt_response_found = getattr(flaresolverr_response, 'yt_initial_player_response_found', False)
def json(self): def json(self):
import json import json
@@ -278,9 +351,12 @@ class TranscriptExtractor:
return PatchedResponse(flaresolverr_response) return PatchedResponse(flaresolverr_response)
else: else:
logger.debug(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor") logger.debug(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor")
elif extractor_instance.use_flaresolverr and is_video_page and not extractor_instance.flaresolverr_available: elif extractor_instance.use_flaresolverr and is_video_page:
# FlareSolverr erişilemiyor, normal istek yap # FlareSolverr erişilemiyor veya available=False, normal istek yap
logger.debug(f"[FLARESOLVERR] FlareSolverr erişilemiyor, normal istek yapılıyor: {url[:50]}...") if not extractor_instance.flaresolverr_available:
logger.debug(f"[FLARESOLVERR] FlareSolverr erişilemiyor (available=False), normal istek yapılıyor: {url[:50]}...")
else:
logger.debug(f"[FLARESOLVERR] FlareSolverr kullanılamıyor, normal istek yapılıyor: {url[:50]}...")
elif extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url) and '/api/' in url: elif extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url) and '/api/' in url:
# Transcript API endpoint'leri için FlareSolverr kullanma, sadece header'ları ekle # Transcript API endpoint'leri için FlareSolverr kullanma, sadece header'ları ekle
logger.debug(f"[FLARESOLVERR] Transcript API endpoint'i tespit edildi, FlareSolverr atlanıyor: {url[:50]}...") logger.debug(f"[FLARESOLVERR] Transcript API endpoint'i tespit edildi, FlareSolverr atlanıyor: {url[:50]}...")
@@ -524,17 +600,65 @@ class TranscriptExtractor:
logger.error(f"[TRANSCRIPT] ❌ Tüm denemeler başarısız (FlareSolverr ve normal istek)") logger.error(f"[TRANSCRIPT] ❌ Tüm denemeler başarısız (FlareSolverr ve normal istek)")
return None return None
# YouTubeDataUnparsable hatası için retry yap # YouTubeDataUnparsable hatası için retry yap ve alternatif yöntemler dene
if "YouTubeDataUnparsable" in error_type or "Unparsable" in error_type: 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] ⚠️ 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] Hata mesajı: {error_msg[:300]}")
# FlareSolverr kullanıldıysa, normal isteğe geç
if self.use_flaresolverr and attempt == 0:
logger.warning(f"[TRANSCRIPT] ⚠️ FlareSolverr ile parse edilemedi, normal isteğe geçiliyor...")
# FlareSolverr'ı geçici olarak devre dışı bırak
original_use_flaresolverr = self.use_flaresolverr
self.use_flaresolverr = False
try:
# Normal istek dene
api = YouTubeTranscriptApi()
fetched_transcript = api.fetch(video_id, languages=languages)
transcript = fetched_transcript.to_raw_data()
transcript_count = len(transcript) if transcript else 0
logger.info(f"[TRANSCRIPT] ✅ Video {video_id} transcript'i normal istek ile başarıyla çıkarıldı ({transcript_count} segment)")
# FlareSolverr'ı tekrar etkinleştir
self.use_flaresolverr = original_use_flaresolverr
return transcript
except Exception as e2:
# FlareSolverr'ı tekrar etkinleştir
self.use_flaresolverr = original_use_flaresolverr
logger.warning(f"[TRANSCRIPT] ⚠️ Normal istek de başarısız: {type(e2).__name__} - {str(e2)[:200]}")
# Retry yap
if attempt < max_retries:
continue
# Retry yap
if attempt < max_retries:
logger.warning(f"[TRANSCRIPT] Retry yapılacak...") logger.warning(f"[TRANSCRIPT] Retry yapılacak...")
continue # Retry yap continue
else: else:
logger.error(f"[TRANSCRIPT] ❌ Video {video_id} parse hatası - Tüm denemeler başarısız: {error_type}") 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] 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)") logger.error(f"[TRANSCRIPT] Bu video için transcript çıkarılamıyor (YouTube HTML yapısı değişmiş olabilir)")
# Debug: FlareSolverr response'unu analiz et (eğer kullanıldıysa)
if self.use_flaresolverr and hasattr(self, '_last_flaresolverr_response'):
logger.debug(f"[TRANSCRIPT] FlareSolverr response analizi yapılıyor...")
last_response = self._last_flaresolverr_response
# Transcript URL'leri var mı kontrol et
transcript_urls = getattr(last_response, 'transcript_urls', [])
if transcript_urls:
logger.warning(f"[TRANSCRIPT] ⚠️ FlareSolverr response'unda {len(transcript_urls)} transcript URL bulundu ama parse edilemedi")
logger.warning(f"[TRANSCRIPT] Bu URL'ler doğrudan kullanılabilir (fallback mekanizması henüz implement edilmedi)")
# ytInitialPlayerResponse var mı kontrol et
yt_response_found = getattr(last_response, 'yt_initial_player_response_found', False)
if yt_response_found:
logger.warning(f"[TRANSCRIPT] ⚠️ FlareSolverr response'unda ytInitialPlayerResponse bulundu ama parse edilemedi")
logger.warning(f"[TRANSCRIPT] YouTube HTML yapısı değişmiş olabilir veya kütüphane FlareSolverr response'unu tanımıyor")
else:
logger.warning(f"[TRANSCRIPT] ⚠️ FlareSolverr response'unda ytInitialPlayerResponse bulunamadı")
logger.debug(f"[TRANSCRIPT] FlareSolverr kullanıldı ama parse edilemedi - YouTube HTML yapısı değişmiş olabilir")
return None return None
# Diğer hatalar için normal işlem # Diğer hatalar için normal işlem