Transkript ip blocked
This commit is contained in:
12
README.md
12
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ı
|
||||
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -217,3 +217,72 @@ class Database:
|
||||
""", (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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,13 +193,18 @@ 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
|
||||
transcript = extractor.fetch_transcript(
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user