This commit is contained in:
salvacybersec
2025-11-13 05:16:12 +03:00
parent 1210d1bff1
commit 01f9cfc8b2
4 changed files with 104 additions and 15 deletions

21
app.py
View File

@@ -2,8 +2,29 @@
""" """
Flask Web Server - RSS-Bridge benzeri URL template sistemi Flask Web Server - RSS-Bridge benzeri URL template sistemi
""" """
import logging
import sys
from datetime import datetime
# Logging konfigürasyonu
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
# Flask ve werkzeug loglarını azalt
logging.getLogger('werkzeug').setLevel(logging.WARNING)
logging.getLogger('flask').setLevel(logging.WARNING)
from src.web_server import app from src.web_server import app
if __name__ == '__main__': if __name__ == '__main__':
logging.info("=" * 60)
logging.info("YouTube Transcript RSS Feed Generator başlatılıyor...")
logging.info("=" * 60)
app.run(host='0.0.0.0', port=5000, debug=False) app.run(host='0.0.0.0', port=5000, debug=False)

View File

@@ -5,9 +5,13 @@ import sqlite3
import os import os
import re import re
import threading import threading
import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional, List, Dict from typing import Optional, List, Dict
# Logger oluştur
logger = logging.getLogger(__name__)
class Database: class Database:
"""SQLite veritabanı yönetim sınıfı (thread-safe)""" """SQLite veritabanı yönetim sınıfı (thread-safe)"""
@@ -91,7 +95,7 @@ class Database:
""") """)
conn.commit() conn.commit()
print("Database initialized successfully") logger.info("[DATABASE] ✅ Veritabanı başarıyla initialize edildi")
def close(self): def close(self):
"""Veritabanı bağlantısını kapat (thread-safe)""" """Veritabanı bağlantısını kapat (thread-safe)"""

View File

@@ -4,6 +4,10 @@ YouTube transcript çıkarımı modülü
from youtube_transcript_api import YouTubeTranscriptApi from youtube_transcript_api import YouTubeTranscriptApi
from typing import List, Dict, Optional from typing import List, Dict, Optional
import time import time
import logging
# Logger oluştur
logger = logging.getLogger(__name__)
class TranscriptExtractor: class TranscriptExtractor:
@@ -27,7 +31,7 @@ class TranscriptExtractor:
# Eğer son 5 dakikada IP blocking hatası aldıysak, daha uzun bekle # Eğer son 5 dakikada IP blocking hatası aldıysak, daha uzun bekle
if self.last_blocked_time > 0 and (now - self.last_blocked_time) < 300: if self.last_blocked_time > 0 and (now - self.last_blocked_time) < 300:
wait_time = 60 # 1 dakika bekle wait_time = 60 # 1 dakika bekle
print(f"IP blocking sonrası bekleme: {wait_time} saniye") logger.warning(f"[RATE_LIMIT] IP blocking sonrası bekleme: {wait_time} saniye (Son blocking: {int(now - self.last_blocked_time)} saniye önce)")
time.sleep(wait_time) time.sleep(wait_time)
self.last_blocked_time = 0 # Reset self.last_blocked_time = 0 # Reset
@@ -38,7 +42,7 @@ class TranscriptExtractor:
if len(self.request_times) >= self.rate_limit: if len(self.request_times) >= self.rate_limit:
sleep_time = self.time_window - (now - self.request_times[0]) sleep_time = self.time_window - (now - self.request_times[0])
if sleep_time > 0: if sleep_time > 0:
print(f"Rate limit: {sleep_time:.1f} saniye bekleniyor...") logger.info(f"[RATE_LIMIT] Rate limit aşıldı ({len(self.request_times)}/{self.rate_limit} istek), {sleep_time:.1f} saniye bekleniyor...")
time.sleep(sleep_time) time.sleep(sleep_time)
# Tekrar filtrele # Tekrar filtrele
now = time.time() now = time.time()
@@ -50,10 +54,12 @@ class TranscriptExtractor:
min_interval = 3 # Minimum 3 saniye min_interval = 3 # Minimum 3 saniye
if time_since_last < min_interval: if time_since_last < min_interval:
sleep_time = min_interval - time_since_last sleep_time = min_interval - time_since_last
logger.debug(f"[RATE_LIMIT] Minimum interval bekleme: {sleep_time:.2f} saniye (Son istek: {time_since_last:.2f} saniye önce)")
time.sleep(sleep_time) time.sleep(sleep_time)
# İstek zamanını kaydet # İstek zamanını kaydet
self.request_times.append(time.time()) self.request_times.append(time.time())
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']) -> Optional[List[Dict]]:
@@ -67,10 +73,14 @@ class TranscriptExtractor:
Returns: Returns:
Transcript listesi veya None Transcript listesi veya None
""" """
logger.info(f"[TRANSCRIPT] Video {video_id} için transcript çıkarımı başlatılıyor (Diller: {languages})")
# Rate limiting kontrolü # Rate limiting kontrolü
self._check_rate_limit() self._check_rate_limit()
try: 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) # YouTube Transcript API kullanımı (yeni versiyon)
# API instance oluştur ve fetch() metodunu kullan # API instance oluştur ve fetch() metodunu kullan
api = YouTubeTranscriptApi() api = YouTubeTranscriptApi()
@@ -80,15 +90,21 @@ class TranscriptExtractor:
# Format: [{'text': '...', 'start': 1.36, 'duration': 1.68}, ...] # Format: [{'text': '...', 'start': 1.36, 'duration': 1.68}, ...]
transcript = fetched_transcript.to_raw_data() 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 return transcript
except Exception as e: except Exception as e:
error_msg = str(e) error_msg = str(e)
print(f"Error fetching transcript for {video_id}: {error_msg}") 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 # IP blocking hatası tespit edilirse işaretle
if "blocking" in error_msg.lower() or "blocked" in error_msg.lower(): if "blocking" in error_msg.lower() or "blocked" in error_msg.lower() or "IP" in error_msg:
self.last_blocked_time = time.time() self.last_blocked_time = time.time()
print(f"IP blocking tespit edildi, sonraki isteklerde daha uzun bekleme yapılacak") logger.warning(f"[TRANSCRIPT] 🚫 IP blocking tespit edildi! Video: {video_id}, Sonraki isteklerde 60 saniye bekleme yapılacak")
logger.warning(f"[TRANSCRIPT] IP blocking detayları: {error_msg[:500]}")
return None return None

View File

@@ -7,8 +7,12 @@ import sys
import os import os
import yaml import yaml
import time import time
import logging
from pathlib import Path from pathlib import Path
# Logger oluştur
logger = logging.getLogger(__name__)
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
from src.database import Database from src.database import Database
@@ -180,6 +184,8 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
# RSS-Bridge'den videoları çek (max_items'ın 2 katı kadar çek, böylece yeterli video olur) # RSS-Bridge'den videoları çek (max_items'ın 2 katı kadar çek, böylece yeterli video olur)
# RSS-Bridge'den daha fazla video çekiyoruz çünkü bazıları transcript'siz olabilir # RSS-Bridge'den daha fazla video çekiyoruz çünkü bazıları transcript'siz olabilir
rss_bridge_limit = max(max_items * 2, 50) # En az 50 video çek rss_bridge_limit = max(max_items * 2, 50) # En az 50 video çek
logger.info(f"[PROCESS] Channel {channel_id} için RSS-Bridge'den video listesi çekiliyor (limit: {rss_bridge_limit})")
try: try:
videos = fetch_videos_from_rss_bridge( videos = fetch_videos_from_rss_bridge(
base_url="https://rss-bridge.org/bridge01", base_url="https://rss-bridge.org/bridge01",
@@ -187,14 +193,23 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
format="Atom", format="Atom",
max_items=rss_bridge_limit max_items=rss_bridge_limit
) )
logger.info(f"[PROCESS] RSS-Bridge'den {len(videos)} video alındı")
except Exception as e: except Exception as e:
logger.error(f"[PROCESS] ❌ RSS-Bridge hatası: {type(e).__name__} - {str(e)}")
raise Exception(f"RSS-Bridge hatası: {e}") raise Exception(f"RSS-Bridge hatası: {e}")
# Yeni videoları veritabanına ekle # Yeni videoları veritabanına ekle
new_videos_count = 0
for video in videos: for video in videos:
video['channel_id'] = channel_id video['channel_id'] = channel_id
if not db.is_video_processed(video['video_id']): if not db.is_video_processed(video['video_id']):
db.add_video(video) db.add_video(video)
new_videos_count += 1
if new_videos_count > 0:
logger.info(f"[PROCESS] {new_videos_count} yeni video veritabanına eklendi")
else:
logger.debug(f"[PROCESS] Tüm videolar zaten veritabanında")
# Bekleyen videoları işle (max_items kadar, 20'şer batch'ler halinde) # Bekleyen videoları işle (max_items kadar, 20'şer batch'ler halinde)
# YouTube IP blocking'i önlemek için her batch'te 20 video işlenir # YouTube IP blocking'i önlemek için her batch'te 20 video işlenir
@@ -204,51 +219,82 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
# Tüm bekleyen videoları al (channel_id'ye göre filtrele) # Tüm bekleyen videoları al (channel_id'ye göre filtrele)
all_pending_videos = [v for v in db.get_pending_videos() if v['channel_id'] == channel_id] all_pending_videos = [v for v in db.get_pending_videos() if v['channel_id'] == channel_id]
logger.info(f"[PROCESS] Channel {channel_id} için {len(all_pending_videos)} bekleyen video bulundu (max_items: {max_items})")
# max_items kadar transcript işlenene kadar batch'ler halinde işle # max_items kadar transcript işlenene kadar batch'ler halinde işle
total_batches = (len(all_pending_videos) + batch_size - 1) // batch_size
current_batch = 0
for batch_start in range(0, len(all_pending_videos), batch_size): for batch_start in range(0, len(all_pending_videos), batch_size):
if processed_count >= max_items: if processed_count >= max_items:
logger.info(f"[PROCESS] Maksimum transcript sayısına ulaşıldı ({processed_count}/{max_items})")
break break
current_batch += 1
batch_videos = all_pending_videos[batch_start:batch_start + batch_size] batch_videos = all_pending_videos[batch_start:batch_start + batch_size]
logger.info(f"[BATCH] Batch {current_batch}/{total_batches} başlatılıyor ({len(batch_videos)} video, Toplam işlenen: {processed_count}/{max_items})")
batch_processed = 0
batch_cached = 0
batch_failed = 0
for video in batch_videos: for video in batch_videos:
if processed_count >= max_items: if processed_count >= max_items:
break break
video_id = video['video_id']
video_title = video.get('video_title', 'N/A')[:50]
# Cache kontrolü: 3 gün içinde işlenmiş transcript varsa atla # Cache kontrolü: 3 gün içinde işlenmiş transcript varsa atla
if db.is_transcript_cached(video['video_id'], cache_days=3): if db.is_transcript_cached(video_id, cache_days=3):
print(f"Video {video['video_id']} transcript'i cache'de (3 gün içinde işlenmiş), atlanıyor") logger.debug(f"[CACHE] Video {video_id} ({video_title}) transcript'i cache'de, atlanıyor")
batch_cached += 1
continue continue
try: try:
logger.info(f"[VIDEO] Video işleniyor: {video_id} - {video_title}")
# Transcript çıkar # Transcript çıkar
transcript = extractor.fetch_transcript( transcript = extractor.fetch_transcript(
video['video_id'], video_id,
languages=['tr', 'en'] languages=['tr', 'en']
) )
if transcript: if transcript:
# Transcript temizle # Transcript temizle
logger.debug(f"[VIDEO] Video {video_id} transcript'i temizleniyor...")
raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3) raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3)
# Veritabanına kaydet (her batch hemen kaydedilir) # Veritabanına kaydet (her batch hemen kaydedilir)
db.update_video_transcript( db.update_video_transcript(
video['video_id'], video_id,
raw, raw,
clean, clean,
status=1, status=1,
language='tr' language='tr'
) )
processed_count += 1 processed_count += 1
print(f"Video {video['video_id']} transcript'i işlendi ve veritabanına kaydedildi ({processed_count}/{max_items})") batch_processed += 1
logger.info(f"[VIDEO] ✅ Video {video_id} başarıyla işlendi ve kaydedildi ({processed_count}/{max_items})")
else:
logger.warning(f"[VIDEO] ⚠️ Video {video_id} transcript'i alınamadı (None döndü)")
batch_failed += 1
db.mark_video_failed(video_id, "Transcript None döndü")
except Exception as e: except Exception as e:
print(f"Transcript çıkarım hatası {video['video_id']}: {e}") error_type = type(e).__name__
db.mark_video_failed(video['video_id'], str(e)) error_msg = str(e)[:200]
logger.error(f"[VIDEO] ❌ Video {video_id} işleme hatası: {error_type} - {error_msg}")
db.mark_video_failed(video_id, str(e))
batch_failed += 1
# Batch özeti
logger.info(f"[BATCH] Batch {current_batch}/{total_batches} tamamlandı - İşlenen: {batch_processed}, Cache: {batch_cached}, Başarısız: {batch_failed}")
# Batch tamamlandı, kısa bir bekleme (rate limiting için) # Batch tamamlandı, kısa bir bekleme (rate limiting için)
if processed_count < max_items and batch_start + batch_size < len(all_pending_videos): if processed_count < max_items and batch_start + batch_size < len(all_pending_videos):
time.sleep(2) # Batch'ler arası 2 saniye bekleme wait_time = 2
logger.debug(f"[BATCH] Batch'ler arası bekleme: {wait_time} saniye")
time.sleep(wait_time)
# İşlenmiş videoları getir # İşlenmiş videoları getir
processed_videos = db.get_processed_videos( processed_videos = db.get_processed_videos(
@@ -256,6 +302,8 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
channel_id=channel_id channel_id=channel_id
) )
logger.info(f"[PROCESS] ✅ Channel {channel_id} işleme tamamlandı - {len(processed_videos)} işlenmiş video döndürülüyor")
return { return {
'videos': processed_videos, 'videos': processed_videos,
'channel_id': channel_id, 'channel_id': channel_id,