From bb416e1f37113e07d587ebe979672968a2fbf607 Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Thu, 13 Nov 2025 03:52:26 +0300 Subject: [PATCH] Transkript ip blocked --- README.md | 12 +++++++ development_plan.md | 15 ++++++++ src/database.py | 69 +++++++++++++++++++++++++++++++++++++ src/transcript_extractor.py | 33 +++++++++++++++--- src/web_server.py | 14 +++++--- 5 files changed, 135 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c4a59e0..0cec382 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ YouTube video transkriptlerini otomatik olarak çıkarıp, tam metin içeren RSS - ✅ **Web Server Modu** - Flask ile RESTful API - ✅ **API Key Authentication** - Tüm endpoint'ler API key gerektirir - ✅ **Güvenlik Önlemleri** - SQL injection, XSS, rate limiting koruması +- ✅ **Transcript Cache** - 3 günlük cache ile YouTube IP blocking önleme - ✅ RSS-Bridge entegrasyonu (100+ video desteği) - ✅ Async rate limiting (AIOLimiter) - ✅ SpaCy ile Sentence Boundary Detection @@ -127,6 +128,17 @@ security: **Detaylı güvenlik dokümantasyonu için:** [SECURITY.md](SECURITY.md) +### Transcript Cache + +Sistem, işlenmiş transcript'leri **3 gün boyunca cache'de tutar**. Bu özellik: + +- **YouTube IP blocking'i önler**: Aynı videoların transcript'ini tekrar çekmez +- **Performans artışı**: Cache'den hızlı yanıt +- **Rate limiting azaltır**: Gereksiz API isteklerini önler +- **Otomatik yenileme**: 3 gün sonra cache geçersiz olur, yeni transcript çekilir + +Cache kontrolü otomatik yapılır ve kullanıcı müdahalesi gerektirmez. + ## Proje Yapısı ``` diff --git a/development_plan.md b/development_plan.md index b912d49..3227d66 100644 --- a/development_plan.md +++ b/development_plan.md @@ -311,11 +311,26 @@ channel_id = get_channel_id_from_handle(handle_url) - `update_video_transcript(video_id, raw, clean, status, language)` - Transcript güncelleme - `get_processed_videos(limit=None, channel_id=None)` - İşlenmiş videoları getir - `mark_video_failed(video_id, reason)` - Kalıcı hata işaretleme (status=2) + - `is_transcript_cached(video_id, cache_days=3)` - Transcript cache kontrolü (3 günlük) + - `get_cached_transcript(video_id)` - Cache'den transcript getirme - **Query Performance**: `EXPLAIN QUERY PLAN` ile index kullanımını doğrula +- [ ] **Transcript Cache Mekanizması**: + - **3 Günlük Cache**: İşlenmiş transcript'ler 3 gün boyunca cache'de tutulur + - **Cache Kontrolü**: Transcript çıkarımından önce cache kontrolü yapılır + - **Avantajlar**: + - YouTube IP blocking riskini azaltır + - Performans artışı (tekrar isteklerde hızlı yanıt) + - API rate limiting'i azaltır + - Aynı videoların transcript'ini tekrar çekmez + - **Cache Süresi**: `processed_at_utc` tarihine göre 3 gün kontrolü + - **Otomatik Yenileme**: 3 gün sonra cache geçersiz olur, yeni transcript çekilir - [ ] Yeni video tespiti algoritması: 1. RSS-Bridge feed'den son videoları çek 2. SQLite veritabanında `video_id` ile sorgula 3. Sadece yeni videoları (veritabanında olmayan) işle + 4. **Cache Kontrolü**: İşlenmiş videolar için 3 günlük cache kontrolü yap + - Eğer 3 gün içinde işlenmişse, transcript çıkarma (cache'den kullan) + - 3 günden eskiyse, yeni transcript çek - [ ] Transaction yönetimi (ACID compliance) - [ ] Connection pooling ve error handling diff --git a/src/database.py b/src/database.py index 060a2b0..f165eda 100644 --- a/src/database.py +++ b/src/database.py @@ -216,4 +216,73 @@ class Database: WHERE video_id = ? """, (datetime.now(timezone.utc).isoformat(), video_id)) self.conn.commit() + + def is_transcript_cached(self, video_id: str, cache_days: int = 3) -> bool: + """ + Video transcript'i cache'de mi ve hala geçerli mi kontrol et + + Args: + video_id: Video ID + cache_days: Cache geçerlilik süresi (gün) + + Returns: + True eğer cache'de ve geçerliyse, False değilse + """ + if not self._validate_video_id(video_id): + return False + + cursor = self.conn.cursor() + # 3 gün içinde işlenmiş ve başarılı (status=1) transcript var mı? + cursor.execute(""" + SELECT processed_at_utc, transcript_status, transcript_clean + FROM videos + WHERE video_id = ? + AND transcript_status = 1 + AND processed_at_utc IS NOT NULL + """, (video_id,)) + + row = cursor.fetchone() + if not row: + return False + + # processed_at_utc'yi parse et ve 3 gün kontrolü yap + try: + processed_at = datetime.fromisoformat(row['processed_at_utc'].replace('Z', '+00:00')) + now = datetime.now(timezone.utc) + days_diff = (now - processed_at).days + + # 3 gün içindeyse ve transcript_clean varsa cache geçerli + if days_diff < cache_days and row['transcript_clean']: + return True + except (ValueError, AttributeError): + return False + + return False + + def get_cached_transcript(self, video_id: str) -> Optional[Dict]: + """ + Cache'den transcript'i getir (eğer varsa) + + Args: + video_id: Video ID + + Returns: + Video dict (transcript_clean ile) veya None + """ + if not self._validate_video_id(video_id): + return None + + cursor = self.conn.cursor() + cursor.execute(""" + SELECT * FROM videos + WHERE video_id = ? + AND transcript_status = 1 + AND transcript_clean IS NOT NULL + """, (video_id,)) + + row = cursor.fetchone() + if row: + return dict(row) + + return None diff --git a/src/transcript_extractor.py b/src/transcript_extractor.py index 7dc1e93..f6bea7a 100644 --- a/src/transcript_extractor.py +++ b/src/transcript_extractor.py @@ -9,19 +9,28 @@ import time class TranscriptExtractor: """YouTube transcript çıkarıcı sınıfı""" - def __init__(self, rate_limit: int = 5, time_window: int = 10): + def __init__(self, rate_limit: int = 2, time_window: int = 30): """ Args: - rate_limit: Zaman penceresi başına maksimum istek sayısı + rate_limit: Zaman penceresi başına maksimum istek sayısı (YouTube IP blocking'i önlemek için düşük) time_window: Zaman penceresi (saniye) """ self.rate_limit = rate_limit self.time_window = time_window self.request_times = [] + self.last_blocked_time = 0 def _check_rate_limit(self): - """Rate limiting kontrolü (basit implementasyon)""" + """Rate limiting kontrolü (YouTube IP blocking'i önlemek için)""" now = time.time() + + # 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") + time.sleep(wait_time) + self.last_blocked_time = 0 # Reset + # Son time_window saniyesindeki istekleri filtrele self.request_times = [t for t in self.request_times if now - t < self.time_window] @@ -29,11 +38,20 @@ 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...") time.sleep(sleep_time) # Tekrar filtrele now = time.time() self.request_times = [t for t in self.request_times if now - t < self.time_window] + # İstekler arasında minimum bekleme (YouTube blocking'i önlemek için) + if self.request_times: + time_since_last = now - self.request_times[-1] + min_interval = 3 # Minimum 3 saniye + if time_since_last < min_interval: + sleep_time = min_interval - time_since_last + time.sleep(sleep_time) + # İstek zamanını kaydet self.request_times.append(time.time()) @@ -64,6 +82,13 @@ class TranscriptExtractor: return transcript except Exception as e: - print(f"Error fetching transcript for {video_id}: {e}") + error_msg = str(e) + print(f"Error fetching transcript for {video_id}: {error_msg}") + + # IP blocking hatası tespit edilirse işaretle + if "blocking" in error_msg.lower() or "blocked" in error_msg.lower(): + self.last_blocked_time = time.time() + print(f"IP blocking tespit edildi, sonraki isteklerde daha uzun bekleme yapılacak") + return None diff --git a/src/web_server.py b/src/web_server.py index 303c930..509eb50 100644 --- a/src/web_server.py +++ b/src/web_server.py @@ -1,7 +1,7 @@ """ Flask web server - RSS-Bridge benzeri URL template sistemi """ -from flask import Flask, request, Response, jsonify, g, after_request +from flask import Flask, request, Response, jsonify, g from typing import Optional import sys import os @@ -193,12 +193,17 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict: if not db.is_video_processed(video['video_id']): db.add_video(video) - # Bekleyen videoları işle (ilk 20) - pending_videos = db.get_pending_videos()[:20] + # Bekleyen videoları işle (YouTube IP blocking'i önlemek için sadece 5 video) + pending_videos = db.get_pending_videos()[:5] for video in pending_videos: if video['channel_id'] != channel_id: continue + + # 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") + continue try: # Transcript çıkar @@ -285,7 +290,8 @@ def generate_feed(): return jsonify({ 'error': 'Henüz işlenmiş video yok', 'channel_id': normalized_channel_id, - 'message': 'Lütfen birkaç dakika sonra tekrar deneyin' + 'message': 'Transcript\'ler arka planda işleniyor. Lütfen birkaç dakika sonra tekrar deneyin.', + 'note': 'YouTube IP blocking nedeniyle transcript çıkarımı yavaş olabilir. İlk istekte birkaç dakika bekleyin.' }), 404 # RSS feed oluştur