yt bot protection
This commit is contained in:
@@ -5,35 +5,126 @@ from youtube_transcript_api import YouTubeTranscriptApi
|
|||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
|
||||||
# Logger oluştur
|
# Logger oluştur
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Gerçek tarayıcı User-Agent'ları (rotasyon için)
|
||||||
|
USER_AGENTS = [
|
||||||
|
# Chrome (Windows)
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
# Chrome (macOS)
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
# Chrome (Linux)
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
# Firefox (Windows)
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
||||||
|
# Firefox (macOS)
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
|
||||||
|
# Safari (macOS)
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
|
||||||
|
# Edge (Windows)
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TranscriptExtractor:
|
class TranscriptExtractor:
|
||||||
"""YouTube transcript çıkarıcı sınıfı"""
|
"""YouTube transcript çıkarıcı sınıfı"""
|
||||||
|
|
||||||
def __init__(self, rate_limit: int = 2, time_window: int = 30):
|
def __init__(self, rate_limit: int = 1, time_window: int = 60):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
rate_limit: Zaman penceresi başına maksimum istek sayısı (YouTube IP blocking'i önlemek için düşük)
|
rate_limit: Zaman penceresi başına maksimum istek sayısı (YouTube IP blocking'i önlemek için çok düşük)
|
||||||
time_window: Zaman penceresi (saniye)
|
time_window: Zaman penceresi (saniye) - daha uzun süre
|
||||||
"""
|
"""
|
||||||
self.rate_limit = rate_limit
|
self.rate_limit = rate_limit # 1 istek/60 saniye (çok konservatif)
|
||||||
self.time_window = time_window
|
self.time_window = time_window
|
||||||
self.request_times = []
|
self.request_times = []
|
||||||
self.last_blocked_time = 0
|
self.last_blocked_time = 0
|
||||||
|
self.block_count = 0 # Toplam blocking sayısı
|
||||||
|
|
||||||
|
# Gerçek tarayıcı header'larını ayarla
|
||||||
|
self._setup_browser_headers()
|
||||||
|
|
||||||
|
def _setup_browser_headers(self):
|
||||||
|
"""Gerçek tarayıcı gibi HTTP header'larını ayarla"""
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# requests.Session'ın get/post metodlarını patch et
|
||||||
|
# Bu, youtube-transcript-api'nin yaptığı tüm isteklere header ekler
|
||||||
|
original_get = requests.Session.get
|
||||||
|
original_post = requests.Session.post
|
||||||
|
|
||||||
|
def patched_get(self, url, **kwargs):
|
||||||
|
"""requests.Session.get'i patch et - header'ları ekle"""
|
||||||
|
headers = kwargs.get('headers', {})
|
||||||
|
browser_headers = TranscriptExtractor._get_browser_headers()
|
||||||
|
# Mevcut header'ları koru, browser header'larını ekle
|
||||||
|
merged_headers = {**browser_headers, **headers}
|
||||||
|
kwargs['headers'] = merged_headers
|
||||||
|
return original_get(self, url, **kwargs)
|
||||||
|
|
||||||
|
def patched_post(self, url, **kwargs):
|
||||||
|
"""requests.Session.post'i patch et - header'ları ekle"""
|
||||||
|
headers = kwargs.get('headers', {})
|
||||||
|
browser_headers = TranscriptExtractor._get_browser_headers()
|
||||||
|
# Mevcut header'ları koru, browser header'larını ekle
|
||||||
|
merged_headers = {**browser_headers, **headers}
|
||||||
|
kwargs['headers'] = merged_headers
|
||||||
|
return original_post(self, url, **kwargs)
|
||||||
|
|
||||||
|
# Patch'i uygula (sadece bir kez)
|
||||||
|
if not hasattr(requests.Session, '_browser_headers_patched'):
|
||||||
|
requests.Session.get = patched_get
|
||||||
|
requests.Session.post = patched_post
|
||||||
|
requests.Session._browser_headers_patched = True
|
||||||
|
|
||||||
|
logger.info("[HEADERS] ✅ requests.Session patch edildi - Gerçek tarayıcı header'ları eklendi")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[HEADERS] ⚠️ Header patch edilemedi: {e}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_browser_headers() -> Dict[str, str]:
|
||||||
|
"""Gerçek tarayıcı header'larını döndür"""
|
||||||
|
user_agent = random.choice(USER_AGENTS)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': user_agent,
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||||
|
'Accept-Language': 'en-US,en;q=0.9,tr;q=0.8',
|
||||||
|
'Accept-Encoding': 'gzip, deflate, br',
|
||||||
|
'DNT': '1',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Upgrade-Insecure-Requests': '1',
|
||||||
|
'Sec-Fetch-Dest': 'document',
|
||||||
|
'Sec-Fetch-Mode': 'navigate',
|
||||||
|
'Sec-Fetch-Site': 'none',
|
||||||
|
'Sec-Fetch-User': '?1',
|
||||||
|
'Cache-Control': 'max-age=0',
|
||||||
|
'Referer': 'https://www.youtube.com/',
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"[HEADERS] Header'lar oluşturuldu - User-Agent: {user_agent[:50]}...")
|
||||||
|
return headers
|
||||||
|
|
||||||
def _check_rate_limit(self):
|
def _check_rate_limit(self):
|
||||||
"""Rate limiting kontrolü (YouTube IP blocking'i önlemek için)"""
|
"""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
|
# Eğer son 10 dakikada IP blocking hatası aldıysak, çok daha uzun bekle
|
||||||
if self.last_blocked_time > 0 and (now - self.last_blocked_time) < 300:
|
if self.last_blocked_time > 0 and (now - self.last_blocked_time) < 600:
|
||||||
wait_time = 60 # 1 dakika bekle
|
# Blocking sayısına göre bekleme süresini artır
|
||||||
logger.warning(f"[RATE_LIMIT] IP blocking sonrası bekleme: {wait_time} saniye (Son blocking: {int(now - self.last_blocked_time)} saniye önce)")
|
base_wait = 300 # 5 dakika base
|
||||||
|
wait_time = base_wait + (self.block_count * 60) # Her blocking'de +1 dakika
|
||||||
|
wait_time = min(wait_time, 1800) # Maksimum 30 dakika
|
||||||
|
|
||||||
|
logger.warning(f"[RATE_LIMIT] IP blocking sonrası bekleme: {wait_time} saniye ({wait_time/60:.1f} dakika) - Blocking sayısı: {self.block_count}")
|
||||||
|
logger.warning(f"[RATE_LIMIT] Son blocking: {int(now - self.last_blocked_time)} saniye önce")
|
||||||
time.sleep(wait_time)
|
time.sleep(wait_time)
|
||||||
self.last_blocked_time = 0 # Reset
|
# Reset etme, 10 dakika boyunca hatırla
|
||||||
|
|
||||||
# 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]
|
||||||
@@ -49,12 +140,18 @@ class TranscriptExtractor:
|
|||||||
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)
|
# İstekler arasında minimum bekleme (YouTube blocking'i önlemek için)
|
||||||
|
# Human-like behavior: Random bekleme (10-20 saniye arası)
|
||||||
if self.request_times:
|
if self.request_times:
|
||||||
time_since_last = now - self.request_times[-1]
|
time_since_last = now - self.request_times[-1]
|
||||||
min_interval = 3 # Minimum 3 saniye
|
# Blocking varsa daha uzun bekle
|
||||||
|
if self.block_count > 0:
|
||||||
|
min_interval = 30 + random.uniform(0, 30) # 30-60 saniye random
|
||||||
|
else:
|
||||||
|
min_interval = 10 + random.uniform(0, 10) # 10-20 saniye random
|
||||||
|
|
||||||
if time_since_last < min_interval:
|
if time_since_last < min_interval:
|
||||||
sleep_time = min_interval - time_since_last
|
sleep_time = min_interval - time_since_last
|
||||||
logger.debug(f"[RATE_LIMIT] Minimum interval bekleme: {sleep_time:.2f} saniye (Son istek: {time_since_last:.2f} saniye önce)")
|
logger.info(f"[RATE_LIMIT] Human-like bekleme: {sleep_time:.1f} saniye (Son istek: {time_since_last:.1f} saniye önce, Blocking sayısı: {self.block_count})")
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
# İstek zamanını kaydet
|
# İstek zamanını kaydet
|
||||||
@@ -101,10 +198,20 @@ class TranscriptExtractor:
|
|||||||
logger.error(f"[TRANSCRIPT] ❌ Video {video_id} transcript çıkarımı başarısız: {error_type} - {error_msg[:200]}")
|
logger.error(f"[TRANSCRIPT] ❌ Video {video_id} transcript çıkarımı başarısız: {error_type} - {error_msg[:200]}")
|
||||||
|
|
||||||
# IP blocking hatası tespit edilirse işaretle
|
# IP blocking hatası tespit edilirse işaretle
|
||||||
if "blocking" in error_msg.lower() or "blocked" in error_msg.lower() or "IP" in error_msg:
|
if "blocking" in error_msg.lower() or "blocked" in error_msg.lower() or "IP" in error_msg or "IpBlocked" in error_type:
|
||||||
self.last_blocked_time = time.time()
|
self.last_blocked_time = time.time()
|
||||||
logger.warning(f"[TRANSCRIPT] 🚫 IP blocking tespit edildi! Video: {video_id}, Sonraki isteklerde 60 saniye bekleme yapılacak")
|
self.block_count += 1
|
||||||
|
wait_time = 300 + (self.block_count * 60) # 5 dakika + (blocking sayısı * 1 dakika)
|
||||||
|
wait_time = min(wait_time, 1800) # Maksimum 30 dakika
|
||||||
|
|
||||||
|
logger.error(f"[TRANSCRIPT] 🚫 IP blocking tespit edildi! Video: {video_id}")
|
||||||
|
logger.error(f"[TRANSCRIPT] Toplam blocking sayısı: {self.block_count}, Sonraki isteklerde {wait_time} saniye ({wait_time/60:.1f} dakika) bekleme yapılacak")
|
||||||
logger.warning(f"[TRANSCRIPT] IP blocking detayları: {error_msg[:500]}")
|
logger.warning(f"[TRANSCRIPT] IP blocking detayları: {error_msg[:500]}")
|
||||||
|
|
||||||
|
# Çok fazla blocking varsa uyar
|
||||||
|
if self.block_count >= 3:
|
||||||
|
logger.error(f"[TRANSCRIPT] ⚠️ UYARI: {self.block_count} kez IP blocking alındı! YouTube IP'nizi geçici olarak engellemiş olabilir.")
|
||||||
|
logger.error(f"[TRANSCRIPT] Öneriler: 1) Daha uzun bekleme (30+ dakika), 2) Farklı IP kullan, 3) Proxy/VPN kullan")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import os
|
|||||||
import yaml
|
import yaml
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Logger oluştur
|
# Logger oluştur
|
||||||
@@ -211,10 +212,10 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
|
|||||||
else:
|
else:
|
||||||
logger.debug(f"[PROCESS] Tüm videolar zaten veritabanında")
|
logger.debug(f"[PROCESS] Tüm videolar zaten veritabanında")
|
||||||
|
|
||||||
# Bekleyen videoları işle (max_items kadar, 20'şer batch'ler halinde)
|
# Bekleyen videoları işle (max_items kadar, küçük batch'ler halinde)
|
||||||
# YouTube IP blocking'i önlemek için her batch'te 20 video işlenir
|
# YouTube IP blocking'i önlemek için her batch'te sadece 5 video işlenir
|
||||||
# max_items: Her istekte kaç video transcript işleneceği (maksimum 100)
|
# max_items: Her istekte kaç video transcript işleneceği (maksimum 100)
|
||||||
batch_size = 20 # Her batch'te işlenecek video sayısı
|
batch_size = 5 # Her batch'te işlenecek video sayısı (küçük batch = daha az blocking riski)
|
||||||
processed_count = 0 # İşlenen transcript sayısı
|
processed_count = 0 # İşlenen transcript sayısı
|
||||||
|
|
||||||
# Tüm bekleyen videoları al (channel_id'ye göre filtrele)
|
# Tüm bekleyen videoları al (channel_id'ye göre filtrele)
|
||||||
@@ -290,10 +291,11 @@ def process_channel(channel_id: str, max_items: int = 50) -> dict:
|
|||||||
# Batch özeti
|
# Batch özeti
|
||||||
logger.info(f"[BATCH] Batch {current_batch}/{total_batches} tamamlandı - İşlenen: {batch_processed}, Cache: {batch_cached}, Başarısız: {batch_failed}")
|
logger.info(f"[BATCH] Batch {current_batch}/{total_batches} tamamlandı - İşlenen: {batch_processed}, Cache: {batch_cached}, Başarısız: {batch_failed}")
|
||||||
|
|
||||||
# Batch tamamlandı, kısa bir bekleme (rate limiting için)
|
# Batch tamamlandı, uzun bekleme (YouTube IP blocking önleme için)
|
||||||
if processed_count < max_items and batch_start + batch_size < len(all_pending_videos):
|
if processed_count < max_items and batch_start + batch_size < len(all_pending_videos):
|
||||||
wait_time = 2
|
# Blocking varsa daha uzun bekle
|
||||||
logger.debug(f"[BATCH] Batch'ler arası bekleme: {wait_time} saniye")
|
wait_time = 60 + random.uniform(0, 30) # 60-90 saniye random (human-like)
|
||||||
|
logger.info(f"[BATCH] Batch'ler arası bekleme: {wait_time:.1f} saniye ({wait_time/60:.1f} dakika) - YouTube IP blocking önleme")
|
||||||
time.sleep(wait_time)
|
time.sleep(wait_time)
|
||||||
|
|
||||||
# İşlenmiş videoları getir
|
# İşlenmiş videoları getir
|
||||||
|
|||||||
Reference in New Issue
Block a user