log all
This commit is contained in:
21
app.py
21
app.py
@@ -2,8 +2,29 @@
|
||||
"""
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -5,9 +5,13 @@ import sqlite3
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
# Logger oluştur
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Database:
|
||||
"""SQLite veritabanı yönetim sınıfı (thread-safe)"""
|
||||
@@ -91,7 +95,7 @@ class Database:
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
print("Database initialized successfully")
|
||||
logger.info("[DATABASE] ✅ Veritabanı başarıyla initialize edildi")
|
||||
|
||||
def close(self):
|
||||
"""Veritabanı bağlantısını kapat (thread-safe)"""
|
||||
|
||||
@@ -4,6 +4,10 @@ YouTube transcript çıkarımı modülü
|
||||
from youtube_transcript_api import YouTubeTranscriptApi
|
||||
from typing import List, Dict, Optional
|
||||
import time
|
||||
import logging
|
||||
|
||||
# Logger oluştur
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TranscriptExtractor:
|
||||
@@ -27,7 +31,7 @@ class TranscriptExtractor:
|
||||
# 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:
|
||||
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)
|
||||
self.last_blocked_time = 0 # Reset
|
||||
|
||||
@@ -38,7 +42,7 @@ class TranscriptExtractor:
|
||||
if len(self.request_times) >= self.rate_limit:
|
||||
sleep_time = self.time_window - (now - self.request_times[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)
|
||||
# Tekrar filtrele
|
||||
now = time.time()
|
||||
@@ -50,10 +54,12 @@ class TranscriptExtractor:
|
||||
min_interval = 3 # Minimum 3 saniye
|
||||
if time_since_last < min_interval:
|
||||
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)
|
||||
|
||||
# İstek zamanını kaydet
|
||||
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,
|
||||
languages: List[str] = ['en']) -> Optional[List[Dict]]:
|
||||
@@ -67,10 +73,14 @@ class TranscriptExtractor:
|
||||
Returns:
|
||||
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ü
|
||||
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()
|
||||
@@ -80,15 +90,21 @@ class TranscriptExtractor:
|
||||
# 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)
|
||||
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
|
||||
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()
|
||||
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
|
||||
|
||||
|
||||
@@ -7,8 +7,12 @@ import sys
|
||||
import os
|
||||
import yaml
|
||||
import time
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# Logger oluştur
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
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 daha fazla video çekiyoruz çünkü bazıları transcript'siz olabilir
|
||||
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:
|
||||
videos = fetch_videos_from_rss_bridge(
|
||||
base_url="https://rss-bridge.org/bridge01",
|
||||
@@ -187,14 +193,23 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
|
||||
format="Atom",
|
||||
max_items=rss_bridge_limit
|
||||
)
|
||||
logger.info(f"[PROCESS] RSS-Bridge'den {len(videos)} video alındı")
|
||||
except Exception as e:
|
||||
logger.error(f"[PROCESS] ❌ RSS-Bridge hatası: {type(e).__name__} - {str(e)}")
|
||||
raise Exception(f"RSS-Bridge hatası: {e}")
|
||||
|
||||
# Yeni videoları veritabanına ekle
|
||||
new_videos_count = 0
|
||||
for video in videos:
|
||||
video['channel_id'] = channel_id
|
||||
if not db.is_video_processed(video['video_id']):
|
||||
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)
|
||||
# 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)
|
||||
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
|
||||
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):
|
||||
if processed_count >= max_items:
|
||||
logger.info(f"[PROCESS] Maksimum transcript sayısına ulaşıldı ({processed_count}/{max_items})")
|
||||
break
|
||||
|
||||
current_batch += 1
|
||||
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:
|
||||
if processed_count >= max_items:
|
||||
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
|
||||
if db.is_transcript_cached(video['video_id'], cache_days=3):
|
||||
print(f"Video {video['video_id']} transcript'i cache'de (3 gün içinde işlenmiş), atlanıyor")
|
||||
if db.is_transcript_cached(video_id, cache_days=3):
|
||||
logger.debug(f"[CACHE] Video {video_id} ({video_title}) transcript'i cache'de, atlanıyor")
|
||||
batch_cached += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
logger.info(f"[VIDEO] Video işleniyor: {video_id} - {video_title}")
|
||||
|
||||
# Transcript çıkar
|
||||
transcript = extractor.fetch_transcript(
|
||||
video['video_id'],
|
||||
video_id,
|
||||
languages=['tr', 'en']
|
||||
)
|
||||
|
||||
if transcript:
|
||||
# Transcript temizle
|
||||
logger.debug(f"[VIDEO] Video {video_id} transcript'i temizleniyor...")
|
||||
raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3)
|
||||
|
||||
# Veritabanına kaydet (her batch hemen kaydedilir)
|
||||
db.update_video_transcript(
|
||||
video['video_id'],
|
||||
video_id,
|
||||
raw,
|
||||
clean,
|
||||
status=1,
|
||||
language='tr'
|
||||
)
|
||||
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:
|
||||
print(f"Transcript çıkarım hatası {video['video_id']}: {e}")
|
||||
db.mark_video_failed(video['video_id'], str(e))
|
||||
error_type = type(e).__name__
|
||||
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)
|
||||
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
|
||||
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
|
||||
)
|
||||
|
||||
logger.info(f"[PROCESS] ✅ Channel {channel_id} işleme tamamlandı - {len(processed_videos)} işlenmiş video döndürülüyor")
|
||||
|
||||
return {
|
||||
'videos': processed_videos,
|
||||
'channel_id': channel_id,
|
||||
|
||||
Reference in New Issue
Block a user