batch batch
This commit is contained in:
29
README.md
29
README.md
@@ -60,9 +60,9 @@ curl -H "X-API-Key: demo_key_12345" \
|
|||||||
# Channel Handle ile (API key query parametresi)
|
# Channel Handle ile (API key query parametresi)
|
||||||
curl "http://localhost:5000/?channel=@tavakfi&format=Atom&api_key=demo_key_12345"
|
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" \
|
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)
|
**Detaylı API dokümantasyonu için:** [API.md](API.md)
|
||||||
@@ -108,7 +108,7 @@ channel:
|
|||||||
rss_bridge:
|
rss_bridge:
|
||||||
base_url: "https://rss-bridge.org/bridge01"
|
base_url: "https://rss-bridge.org/bridge01"
|
||||||
format: "Atom"
|
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ı
|
### 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.
|
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ı
|
## Proje Yapısı
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ security:
|
|||||||
channel_id: 50
|
channel_id: 50
|
||||||
channel_handle: 50
|
channel_handle: 50
|
||||||
channel_url: 200
|
channel_url: 200
|
||||||
max_items: 500
|
max_items: 100 # Maksimum transcript sayısı (20'şer batch'ler halinde işlenir)
|
||||||
|
|
||||||
# CORS Settings
|
# CORS Settings
|
||||||
cors:
|
cors:
|
||||||
|
|||||||
@@ -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ü
|
- **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
|
- **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 (max_items × 2, minimum 50 video)
|
||||||
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
|
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)
|
- Eğer 3 gün içinde işlenmişse, transcript çıkarma (cache'den kullan)
|
||||||
- 3 günden eskiyse, yeni transcript çek
|
- 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)
|
- [ ] Transaction yönetimi (ACID compliance)
|
||||||
- [ ] Connection pooling ve error handling
|
- [ ] Connection pooling ve error handling
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- FLASK_ENV=production
|
- 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
|
restart: unless-stopped
|
||||||
# Web server modu (varsayılan)
|
# Web server modu (varsayılan)
|
||||||
command: python app.py
|
command: python app.py
|
||||||
|
|||||||
@@ -177,12 +177,13 @@ class SecurityManager:
|
|||||||
max_items parametresini doğrula
|
max_items parametresini doğrula
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
max_items: Maksimum item sayısı
|
max_items: Maksimum transcript sayısı (her istekte işlenecek)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Geçerli mi?
|
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
|
# Global security manager instance
|
||||||
@@ -312,7 +313,7 @@ def validate_input(f):
|
|||||||
if not security.validate_max_items(max_items_int):
|
if not security.validate_max_items(max_items_int):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'error': 'Geçersiz max_items değeri',
|
'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
|
}), 400
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Optional
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
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()
|
extractor = get_extractor()
|
||||||
cleaner = get_cleaner()
|
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:
|
try:
|
||||||
videos = fetch_videos_from_rss_bridge(
|
videos = fetch_videos_from_rss_bridge(
|
||||||
base_url="https://rss-bridge.org/bridge01",
|
base_url="https://rss-bridge.org/bridge01",
|
||||||
channel_id=channel_id,
|
channel_id=channel_id,
|
||||||
format="Atom",
|
format="Atom",
|
||||||
max_items=max_items
|
max_items=rss_bridge_limit
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"RSS-Bridge hatası: {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']):
|
if not db.is_video_processed(video['video_id']):
|
||||||
db.add_video(video)
|
db.add_video(video)
|
||||||
|
|
||||||
# Bekleyen videoları işle (YouTube IP blocking'i önlemek için sadece 5 video)
|
# Bekleyen videoları işle (max_items kadar, 20'şer batch'ler halinde)
|
||||||
pending_videos = db.get_pending_videos()[:5]
|
# 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:
|
# Tüm bekleyen videoları al (channel_id'ye göre filtrele)
|
||||||
if video['channel_id'] != channel_id:
|
all_pending_videos = [v for v in db.get_pending_videos() if v['channel_id'] == channel_id]
|
||||||
continue
|
|
||||||
|
|
||||||
# Cache kontrolü: 3 gün içinde işlenmiş transcript varsa atla
|
# max_items kadar transcript işlenene kadar batch'ler halinde işle
|
||||||
if db.is_transcript_cached(video['video_id'], cache_days=3):
|
for batch_start in range(0, len(all_pending_videos), batch_size):
|
||||||
print(f"Video {video['video_id']} transcript'i cache'de (3 gün içinde işlenmiş), atlanıyor")
|
if processed_count >= max_items:
|
||||||
continue
|
break
|
||||||
|
|
||||||
try:
|
batch_videos = all_pending_videos[batch_start:batch_start + batch_size]
|
||||||
# Transcript çıkar
|
|
||||||
transcript = extractor.fetch_transcript(
|
|
||||||
video['video_id'],
|
|
||||||
languages=['tr', 'en']
|
|
||||||
)
|
|
||||||
|
|
||||||
if transcript:
|
for video in batch_videos:
|
||||||
# Transcript temizle
|
if processed_count >= max_items:
|
||||||
raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3)
|
break
|
||||||
|
|
||||||
# Veritabanına kaydet
|
# Cache kontrolü: 3 gün içinde işlenmiş transcript varsa atla
|
||||||
db.update_video_transcript(
|
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(
|
||||||
video['video_id'],
|
video['video_id'],
|
||||||
raw,
|
languages=['tr', 'en']
|
||||||
clean,
|
|
||||||
status=1,
|
|
||||||
language='tr'
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
print(f"Transcript çıkarım hatası {video['video_id']}: {e}")
|
if transcript:
|
||||||
db.mark_video_failed(video['video_id'], str(e))
|
# 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
|
# İşlenmiş videoları getir
|
||||||
processed_videos = db.get_processed_videos(
|
processed_videos = db.get_processed_videos(
|
||||||
@@ -259,9 +281,11 @@ def generate_feed():
|
|||||||
channel_url = request.args.get('channel_url')
|
channel_url = request.args.get('channel_url')
|
||||||
format_type = request.args.get('format', 'Atom').lower() # Atom veya Rss
|
format_type = request.args.get('format', 'Atom').lower() # Atom veya Rss
|
||||||
try:
|
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):
|
except (ValueError, TypeError):
|
||||||
max_items = 50
|
max_items = 10
|
||||||
|
|
||||||
# Channel ID'yi normalize et
|
# Channel ID'yi normalize et
|
||||||
normalized_channel_id = normalize_channel_id(
|
normalized_channel_id = normalize_channel_id(
|
||||||
@@ -273,12 +297,12 @@ def generate_feed():
|
|||||||
if not normalized_channel_id:
|
if not normalized_channel_id:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'error': 'Channel ID bulunamadı',
|
'error': 'Channel ID bulunamadı',
|
||||||
'usage': {
|
'usage': {
|
||||||
'channel_id': 'UC... (YouTube Channel ID)',
|
'channel_id': 'UC... (YouTube Channel ID)',
|
||||||
'channel': '@username veya username',
|
'channel': '@username veya username',
|
||||||
'channel_url': 'https://www.youtube.com/@username veya https://www.youtube.com/channel/UC...',
|
'channel_url': 'https://www.youtube.com/@username veya https://www.youtube.com/channel/UC...',
|
||||||
'format': 'Atom veya Rss (varsayılan: Atom)',
|
'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
|
}), 400
|
||||||
|
|
||||||
@@ -362,12 +386,12 @@ def info():
|
|||||||
'channel': '@username veya username',
|
'channel': '@username veya username',
|
||||||
'channel_url': 'Full YouTube channel URL',
|
'channel_url': 'Full YouTube channel URL',
|
||||||
'format': 'Atom veya Rss (varsayılan: Atom)',
|
'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': [
|
'examples': [
|
||||||
'/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom',
|
'/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom',
|
||||||
'/?channel=@tavakfi&format=Rss',
|
'/?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'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user