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
|
- ✅ **Web Server Modu** - Flask ile RESTful API
|
||||||
- ✅ **API Key Authentication** - Tüm endpoint'ler API key gerektirir
|
- ✅ **API Key Authentication** - Tüm endpoint'ler API key gerektirir
|
||||||
- ✅ **Güvenlik Önlemleri** - SQL injection, XSS, rate limiting koruması
|
- ✅ **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)
|
- ✅ RSS-Bridge entegrasyonu (100+ video desteği)
|
||||||
- ✅ Async rate limiting (AIOLimiter)
|
- ✅ Async rate limiting (AIOLimiter)
|
||||||
- ✅ SpaCy ile Sentence Boundary Detection
|
- ✅ SpaCy ile Sentence Boundary Detection
|
||||||
@@ -127,6 +128,17 @@ security:
|
|||||||
|
|
||||||
**Detaylı güvenlik dokümantasyonu için:** [SECURITY.md](SECURITY.md)
|
**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ı
|
## 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
|
- `update_video_transcript(video_id, raw, clean, status, language)` - Transcript güncelleme
|
||||||
- `get_processed_videos(limit=None, channel_id=None)` - İşlenmiş videoları getir
|
- `get_processed_videos(limit=None, channel_id=None)` - İşlenmiş videoları getir
|
||||||
- `mark_video_failed(video_id, reason)` - Kalıcı hata işaretleme (status=2)
|
- `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
|
- **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ı:
|
- [ ] Yeni video tespiti algoritması:
|
||||||
1. RSS-Bridge feed'den son videoları çek
|
1. RSS-Bridge feed'den son videoları çek
|
||||||
2. SQLite veritabanında `video_id` ile sorgula
|
2. SQLite veritabanında `video_id` ile sorgula
|
||||||
3. Sadece yeni videoları (veritabanında olmayan) işle
|
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)
|
- [ ] Transaction yönetimi (ACID compliance)
|
||||||
- [ ] Connection pooling ve error handling
|
- [ ] Connection pooling ve error handling
|
||||||
|
|
||||||
|
|||||||
@@ -216,4 +216,73 @@ class Database:
|
|||||||
WHERE video_id = ?
|
WHERE video_id = ?
|
||||||
""", (datetime.now(timezone.utc).isoformat(), video_id))
|
""", (datetime.now(timezone.utc).isoformat(), video_id))
|
||||||
self.conn.commit()
|
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:
|
class TranscriptExtractor:
|
||||||
"""YouTube transcript çıkarıcı sınıfı"""
|
"""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:
|
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)
|
time_window: Zaman penceresi (saniye)
|
||||||
"""
|
"""
|
||||||
self.rate_limit = rate_limit
|
self.rate_limit = rate_limit
|
||||||
self.time_window = time_window
|
self.time_window = time_window
|
||||||
self.request_times = []
|
self.request_times = []
|
||||||
|
self.last_blocked_time = 0
|
||||||
|
|
||||||
def _check_rate_limit(self):
|
def _check_rate_limit(self):
|
||||||
"""Rate limiting kontrolü (basit implementasyon)"""
|
"""Rate limiting kontrolü (YouTube IP blocking'i önlemek için)"""
|
||||||
now = time.time()
|
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
|
# Son time_window saniyesindeki istekleri filtrele
|
||||||
self.request_times = [t for t in self.request_times if now - t < self.time_window]
|
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:
|
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...")
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
# Tekrar filtrele
|
# Tekrar filtrele
|
||||||
now = time.time()
|
now = time.time()
|
||||||
self.request_times = [t for t in self.request_times if now - t < self.time_window]
|
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
|
# İstek zamanını kaydet
|
||||||
self.request_times.append(time.time())
|
self.request_times.append(time.time())
|
||||||
|
|
||||||
@@ -64,6 +82,13 @@ class TranscriptExtractor:
|
|||||||
|
|
||||||
return transcript
|
return transcript
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Flask web server - RSS-Bridge benzeri URL template sistemi
|
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
|
from typing import Optional
|
||||||
import sys
|
import sys
|
||||||
import os
|
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']):
|
if not db.is_video_processed(video['video_id']):
|
||||||
db.add_video(video)
|
db.add_video(video)
|
||||||
|
|
||||||
# Bekleyen videoları işle (ilk 20)
|
# Bekleyen videoları işle (YouTube IP blocking'i önlemek için sadece 5 video)
|
||||||
pending_videos = db.get_pending_videos()[:20]
|
pending_videos = db.get_pending_videos()[:5]
|
||||||
|
|
||||||
for video in pending_videos:
|
for video in pending_videos:
|
||||||
if video['channel_id'] != channel_id:
|
if video['channel_id'] != channel_id:
|
||||||
continue
|
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:
|
try:
|
||||||
# Transcript çıkar
|
# Transcript çıkar
|
||||||
@@ -285,7 +290,8 @@ def generate_feed():
|
|||||||
return jsonify({
|
return jsonify({
|
||||||
'error': 'Henüz işlenmiş video yok',
|
'error': 'Henüz işlenmiş video yok',
|
||||||
'channel_id': normalized_channel_id,
|
'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
|
}), 404
|
||||||
|
|
||||||
# RSS feed oluştur
|
# RSS feed oluştur
|
||||||
|
|||||||
Reference in New Issue
Block a user