diff --git a/README.md b/README.md index 0cec382..bfba142 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ curl -H "X-API-Key: demo_key_12345" \ # Channel Handle ile (API key query parametresi) curl "http://localhost:5000/?channel=@tavakfi&format=Atom&api_key=demo_key_12345" -# Channel URL ile +# Channel URL ile (max_items: her istekte işlenecek transcript sayısı, default: 10, max: 100, 20'şer batch'ler) curl -H "X-API-Key: demo_key_12345" \ - "http://localhost:5000/?channel_url=https://www.youtube.com/@tavakfi&format=Atom&max_items=100" + "http://localhost:5000/?channel_url=https://www.youtube.com/@tavakfi&format=Atom&max_items=50" ``` **Detaylı API dokümantasyonu için:** [API.md](API.md) @@ -108,7 +108,7 @@ channel: rss_bridge: base_url: "https://rss-bridge.org/bridge01" format: "Atom" - max_items: 100 + max_items: 100 # RSS-Bridge'den çekilecek video sayısı (web server'da max_items parametresi farklı) ``` ### Güvenlik Yapılandırması @@ -139,6 +139,29 @@ Sistem, işlenmiş transcript'leri **3 gün boyunca cache'de tutar**. Bu özelli Cache kontrolü otomatik yapılır ve kullanıcı müdahalesi gerektirmez. +### max_items Parametresi + +Her API isteğinde kaç video transcript'inin işleneceğini kontrol eder: + +- **Varsayılan**: 10 transcript +- **Maksimum**: 100 transcript +- **Kullanım**: `?max_items=50` query parametresi ile belirtilir +- **Batch İşleme**: 20'şer batch'ler halinde işlenir (YouTube IP blocking önleme için) + +**Önemli Notlar:** +- `max_items` parametresi **her istekte işlenecek transcript sayısını** belirler +- RSS-Bridge'den daha fazla video çekilir (max_items × 2, minimum 50) çünkü bazı videolar transcript'siz olabilir +- **Batch İşleme**: YouTube IP blocking'i önlemek için 20'şer batch'ler halinde işlenir +- **Veritabanı Kaydı**: Her batch işlendikten sonra hemen veritabanına kaydedilir, böylece sonraki sorgularda görülebilir +- İlk isteklerde daha az transcript görebilirsiniz; sonraki isteklerde cache'den daha fazla transcript döner + +**Örnek:** +```bash +# 50 transcript işle (20+20+10 batch'ler halinde) +curl -H "X-API-Key: demo_key_12345" \ + "http://localhost:5000/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&max_items=50&format=Atom" +``` + ## Proje Yapısı ``` diff --git a/config/security.yaml b/config/security.yaml index f115161..4137f0e 100644 --- a/config/security.yaml +++ b/config/security.yaml @@ -29,7 +29,7 @@ security: channel_id: 50 channel_handle: 50 channel_url: 200 - max_items: 500 + max_items: 100 # Maksimum transcript sayısı (20'şer batch'ler halinde işlenir) # CORS Settings cors: diff --git a/development_plan.md b/development_plan.md index 3227d66..f0dd5e6 100644 --- a/development_plan.md +++ b/development_plan.md @@ -325,12 +325,19 @@ channel_id = get_channel_id_from_handle(handle_url) - **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 + 1. RSS-Bridge feed'den son videoları çek (max_items × 2, minimum 50 video) 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 + 5. **max_items Parametresi**: Her API isteğinde işlenecek transcript sayısı + - **Varsayılan**: 10 transcript + - **Maksimum**: 100 transcript + - **Kullanım**: `?max_items=50` query parametresi ile belirtilir + - **Batch İşleme**: 20'şer batch'ler halinde işlenir (YouTube IP blocking önleme için) + - **Veritabanı Kaydı**: Her batch işlendikten sonra hemen veritabanına kaydedilir + - **RSS-Bridge Limit**: max_items × 2 kadar video çekilir (bazı videolar transcript'siz olabilir) - [ ] Transaction yönetimi (ACID compliance) - [ ] Connection pooling ve error handling diff --git a/docker-compose.yml b/docker-compose.yml index 67f74ac..ca2fcaa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,11 @@ services: environment: - PYTHONUNBUFFERED=1 - FLASK_ENV=production + dns: + - 100.64.0.39 # Host DNS server + - 8.8.8.8 # Google DNS (fallback) + - 1.1.1.1 # Cloudflare DNS (fallback) + network_mode: bridge restart: unless-stopped # Web server modu (varsayılan) command: python app.py diff --git a/src/security.py b/src/security.py index 4460407..2cb50aa 100644 --- a/src/security.py +++ b/src/security.py @@ -177,12 +177,13 @@ class SecurityManager: max_items parametresini doğrula Args: - max_items: Maksimum item sayısı + max_items: Maksimum transcript sayısı (her istekte işlenecek) Returns: Geçerli mi? """ - return isinstance(max_items, int) and 1 <= max_items <= 500 + # Maksimum 100 transcript (20'şer batch'ler halinde işlenir) + return isinstance(max_items, int) and 1 <= max_items <= 100 # Global security manager instance @@ -312,7 +313,7 @@ def validate_input(f): if not security.validate_max_items(max_items_int): return jsonify({ 'error': 'Geçersiz max_items değeri', - 'message': 'max_items 1-500 arasında olmalı' + 'message': 'max_items 1-100 arasında olmalı (20\'şer batch\'ler halinde işlenir)' }), 400 except ValueError: return jsonify({ diff --git a/src/web_server.py b/src/web_server.py index 509eb50..e161b3b 100644 --- a/src/web_server.py +++ b/src/web_server.py @@ -6,6 +6,7 @@ from typing import Optional import sys import os import yaml +import time from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) @@ -176,13 +177,15 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict: extractor = get_extractor() cleaner = get_cleaner() - # RSS-Bridge'den videoları çek + # 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 try: videos = fetch_videos_from_rss_bridge( base_url="https://rss-bridge.org/bridge01", channel_id=channel_id, format="Atom", - max_items=max_items + max_items=rss_bridge_limit ) except Exception as e: raise Exception(f"RSS-Bridge hatası: {e}") @@ -193,40 +196,59 @@ 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 (YouTube IP blocking'i önlemek için sadece 5 video) - pending_videos = db.get_pending_videos()[:5] + # 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 + # max_items: Her istekte kaç video transcript işleneceği (maksimum 100) + batch_size = 20 # Her batch'te işlenecek video sayısı + processed_count = 0 # İşlenen transcript sayısı - for video in pending_videos: - if video['channel_id'] != channel_id: - continue + # 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] + + # max_items kadar transcript işlenene kadar batch'ler halinde işle + for batch_start in range(0, len(all_pending_videos), batch_size): + if processed_count >= max_items: + break + + batch_videos = all_pending_videos[batch_start:batch_start + batch_size] - # 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 + for video in batch_videos: + if processed_count >= max_items: + break - try: - # Transcript çıkar - transcript = extractor.fetch_transcript( - video['video_id'], - languages=['tr', 'en'] - ) - - if transcript: - # Transcript temizle - raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3) + # 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 - # Veritabanına kaydet - db.update_video_transcript( + try: + # Transcript çıkar + transcript = extractor.fetch_transcript( video['video_id'], - raw, - clean, - status=1, - language='tr' + languages=['tr', 'en'] ) - except Exception as e: - print(f"Transcript çıkarım hatası {video['video_id']}: {e}") - db.mark_video_failed(video['video_id'], str(e)) + + if transcript: + # Transcript temizle + raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3) + + # Veritabanına kaydet (her batch hemen kaydedilir) + db.update_video_transcript( + video['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})") + except Exception as e: + print(f"Transcript çıkarım hatası {video['video_id']}: {e}") + db.mark_video_failed(video['video_id'], str(e)) + + # 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 # İşlenmiş videoları getir processed_videos = db.get_processed_videos( @@ -259,9 +281,11 @@ def generate_feed(): channel_url = request.args.get('channel_url') format_type = request.args.get('format', 'Atom').lower() # Atom veya Rss try: - max_items = int(request.args.get('max_items', 50)) + max_items = int(request.args.get('max_items', 10)) # Default: 10 transcript + # Maksimum 100 transcript (20'şer batch'ler halinde işlenir) + max_items = min(max_items, 100) except (ValueError, TypeError): - max_items = 50 + max_items = 10 # Channel ID'yi normalize et normalized_channel_id = normalize_channel_id( @@ -273,12 +297,12 @@ def generate_feed(): if not normalized_channel_id: return jsonify({ 'error': 'Channel ID bulunamadı', - 'usage': { + 'usage': { 'channel_id': 'UC... (YouTube Channel ID)', 'channel': '@username veya username', 'channel_url': 'https://www.youtube.com/@username veya https://www.youtube.com/channel/UC...', 'format': 'Atom veya Rss (varsayılan: Atom)', - 'max_items': 'Maksimum video sayısı (varsayılan: 50)' + 'max_items': 'Maksimum transcript sayısı (varsayılan: 10, maksimum: 100, 20\'şer batch\'ler halinde işlenir)' } }), 400 @@ -362,12 +386,12 @@ def info(): 'channel': '@username veya username', 'channel_url': 'Full YouTube channel URL', 'format': 'Atom veya Rss (varsayılan: Atom)', - 'max_items': 'Maksimum video sayısı (varsayılan: 50)' + 'max_items': 'Her istekte işlenecek maksimum transcript sayısı (varsayılan: 10, maksimum: 100, 20\'şer batch\'ler halinde işlenir)' }, 'examples': [ '/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom', '/?channel=@tavakfi&format=Rss', - '/?channel_url=https://www.youtube.com/@tavakfi&format=Atom&max_items=100' + '/?channel_url=https://www.youtube.com/@tavakfi&format=Atom&max_items=50' ] })