• 24-07-2026, 22:37:22
    #1
    Merhaba,
    Python kaynak kodu olarak ücretsiz paylaşıyorum. İsteyen exe derleyebilir. Nasıl kullanırım diye PM atanlara cevap verilmeyecektir.
    Sıradaki ücretsiz yazılım için takipte kalın
    import re
    import requests
    from bs4 import BeautifulSoup
    import customtkinter as ctk
    from tkinter import messagebox
    import threading
    from datetime import datetime
    import json
    import concurrent.futures
    import pandas as pd
    import os
    
    
    class YouTubeTrackerNoApiApp(ctk.CTk):
        def __init__(self):
            super().__init__()
    
    
            self.title("Wisex Kanal Kontrol")
            self.geometry("1400x800")
            ctk.set_appearance_mode("dark")
            ctk.set_default_color_theme("blue")
    
    
            # Değişkenler
            self.channel_urls = []
            self.request_count = 0
            self.max_requests = 100
            self.is_fetching = False
            self.results_data = []
            self.successful_channels = 0
    
    
            # Ana frame
            self.main_frame = ctk.CTkFrame(self)
            self.main_frame.pack(fill="both", expand=True, padx=10, pady=10)
    
    
            # Sol Frame: Girişler
            self.left_frame = ctk.CTkFrame(self.main_frame)
            self.left_frame.pack(side="left", fill="y", padx=5, pady=5)
    
    
            # Çoklu URL Ekleme
            ctk.CTkLabel(self.left_frame, text="Kanal Linkleri (her satıra bir link):").pack(pady=5)
            self.url_textbox = ctk.CTkTextbox(self.left_frame, height=150, width=400)
            self.url_textbox.pack(pady=5)
            
            self.example_button = ctk.CTkButton(self.left_frame, text="Örnek URL Ekle", command=self.add_example_urls)
            self.example_button.pack(pady=5)
    
    
            button_frame = ctk.CTkFrame(self.left_frame)
            button_frame.pack(pady=5)
            
            self.add_button = ctk.CTkButton(button_frame, text="Linkleri Ekle", command=self.add_channels)
            self.add_button.pack(side="left", padx=5)
            
            self.clear_button = ctk.CTkButton(button_frame, text="Listeyi Temizle", command=self.clear_channels)
            self.clear_button.pack(side="left", padx=5)
    
    
            ctk.CTkLabel(self.left_frame, text="Eklenen Kanallar:").pack(pady=5)
            self.channel_listbox = ctk.CTkTextbox(self.left_frame, height=200, width=400)
            self.channel_listbox.pack(pady=5, fill="both", expand=True)
    
    
            # Sağ Frame
            self.right_frame = ctk.CTkFrame(self.main_frame)
            self.right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
    
    
            control_frame = ctk.CTkFrame(self.right_frame)
            control_frame.pack(pady=10, fill="x")
            
            self.fetch_button = ctk.CTkButton(control_frame, text="Bilgileri Çek", command=self.start_fetch_info)
            self.fetch_button.pack(side="left", padx=5)
            
            self.export_button = ctk.CTkButton(control_frame, text="TXT Aktar", command=self.export_results_txt)
            self.export_button.pack(side="left", padx=5)
            
            self.export_excel_button = ctk.CTkButton(control_frame, text="Excel Aktar", command=self.export_results_excel)
            self.export_excel_button.pack(side="left", padx=5)
            
            self.reset_counter_button = ctk.CTkButton(control_frame, text="Sayacı Sıfırla", command=self.reset_counter)
            self.reset_counter_button.pack(side="left", padx=5)
    
    
            request_frame = ctk.CTkFrame(self.right_frame)
            request_frame.pack(pady=5, fill="x")
            
            ctk.CTkLabel(request_frame, text="Maksimum İstek Limiti:").pack(side="left", padx=5)
            self.request_limit_entry = ctk.CTkEntry(request_frame, width=80)
            self.request_limit_entry.insert(0, str(self.max_requests))
            self.request_limit_entry.pack(side="left", padx=5)
            
            self.set_request_limit_button = ctk.CTkButton(request_frame, text="Ayarla", command=self.set_request_limit)
            self.set_request_limit_button.pack(side="left", padx=5)
    
    
            ctk.CTkLabel(request_frame, text="Paralel İstek:").pack(side="left", padx=(20,5))
            self.parallel_entry = ctk.CTkEntry(request_frame, width=50)
            self.parallel_entry.insert(0, "3")
            self.parallel_entry.pack(side="left", padx=5)
    
    
            ctk.CTkLabel(self.right_frame, text="HTTP İstek Kullanımı:").pack(pady=5)
            self.request_progress = ctk.CTkProgressBar(self.right_frame, width=400)
            self.request_progress.pack(pady=5)
            self.request_label = ctk.CTkLabel(self.right_frame, text=f"Kullanılan: 0 / {self.max_requests}")
            self.request_label.pack(pady=5)
    
    
            self.success_label = ctk.CTkLabel(self.right_frame, text="Başarılı Kanallar: 0")
            self.success_label.pack(pady=5)
    
    
            ctk.CTkLabel(self.right_frame, text="Kanal Bilgileri:").pack(pady=5)
            self.result_frame = ctk.CTkFrame(self.right_frame)
            self.result_frame.pack(fill="both", expand=True, pady=5)
            
            headers_frame = ctk.CTkFrame(self.result_frame)
            headers_frame.pack(fill="x", padx=5, pady=2)
            
            ctk.CTkLabel(headers_frame, text="Kanal Adı", width=180, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(headers_frame, text="Kanal Linki", width=150, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(headers_frame, text="Abone", width=80, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(headers_frame, text="Video", width=60, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(headers_frame, text="Görüntülenme", width=90, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(headers_frame, text="Kuruluş", width=90, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(headers_frame, text="Durum", width=70, anchor="w").pack(side="left", padx=2)
    
    
            self.results_container = ctk.CTkScrollableFrame(self.result_frame, height=400)
            self.results_container.pack(fill="both", expand=True, padx=5, pady=5)
    
    
            self.status_label = ctk.CTkLabel(self.right_frame, text="Hazır")
            self.status_label.pack(pady=5)
    
    
        def add_example_urls(self):
            example_urls = """https://www.youtube.com/@SenAnlatKaradeniz
    https://www.youtube.com/@KVPEfsaneSahneler
    https://www.youtube.com/c/example2
    https://www.youtube.com/channel/UCqfo40MC0FrR90_gpbagHjg"""
            self.url_textbox.delete("1.0", "end")
            self.url_textbox.insert("1.0", example_urls)
    
    
        def add_channels(self):
            urls_text = self.url_textbox.get("1.0", "end").strip()
            if not urls_text:
                messagebox.showwarning("Uyarı", "Lütfen kanal linkleri girin.")
                return
                
            urls = [url.strip() for url in urls_text.split('\n') if url.strip()]
            new_urls = []
            
            for url in urls:
                normalized_url = self.normalize_url(url)
                if normalized_url and normalized_url not in self.channel_urls:
                    self.channel_urls.append(normalized_url)
                    new_urls.append(normalized_url)
            
            if new_urls:
                self.update_channel_list()
                messagebox.showinfo("Başarılı", f"{len(new_urls)} yeni kanal eklendi!")
            else:
                messagebox.showinfo("Bilgi", "Tüm kanallar zaten listede mevcut.")
    
    
        def normalize_url(self, url):
            if '/about' in url:
                return url.split('?')[0]
            if '/channel/' in url or '/@' in url or '/c/' in url:
                base_url = url.split('?')[0]
                return base_url + '/about' if not base_url.endswith('/about') else base_url
            return url
    
    
        def clear_channels(self):
            self.channel_urls.clear()
            self.update_channel_list()
            self.url_textbox.delete("1.0", "end")
    
    
        def update_channel_list(self):
            self.channel_listbox.delete("1.0", "end")
            for i, url in enumerate(self.channel_urls, 1):
                self.channel_listbox.insert("end", f"{i}. {url}\n")
    
    
        def set_request_limit(self):
            try:
                self.max_requests = int(self.request_limit_entry.get())
                self.update_request_display()
                messagebox.showinfo("Bilgi", f"Maksimum istek limiti {self.max_requests} olarak ayarlandı.")
            except ValueError:
                messagebox.showerror("Hata", "Lütfen geçerli bir sayı girin.")
    
    
        def reset_counter(self):
            self.request_count = 0
            self.successful_channels = 0
            self.update_request_display()
            self.update_success_counter()
            messagebox.showinfo("Bilgi", "İstek sayacı sıfırlandı.")
    
    
        def start_fetch_info(self):
            if not self.channel_urls:
                messagebox.showerror("Hata", "Lütfen en az bir kanal ekleyin.")
                return
                
            if self.is_fetching:
                messagebox.showwarning("Uyarı", "Zaten veri çekiliyor...")
                return
                
            self.is_fetching = True
            self.fetch_button.configure(state="disabled")
            self.status_label.configure(text="Veriler çekiliyor...")
            self.results_data = []
            self.successful_channels = 0
            self.update_success_counter()
            
            for widget in self.results_container.winfo_children():
                widget.destroy()
            
            thread = threading.Thread(target=self.fetch_info_parallel)
            thread.daemon = True
            thread.start()
    
    
        def fetch_info_parallel(self):
            try:
                parallel_workers = min(int(self.parallel_entry.get()), 5)
            except:
                parallel_workers = 3
    
    
            with concurrent.futures.ThreadPoolExecutor(max_workers=parallel_workers) as executor:
                future_to_url = {
                    executor.submit(self.fetch_single_channel, url, i): (url, i) 
                    for i, url in enumerate(self.channel_urls) 
                    if self.request_count < self.max_requests
                }
                
                for future in concurrent.futures.as_completed(future_to_url):
                    url, index = future_to_url[future]
                    try:
                        channel_info = future.result()
                        if channel_info and self.is_valid_channel(channel_info):
                            self.results_data.append(channel_info)
                            self.add_result_row(channel_info)
                            self.successful_channels += 1
                            self.update_success_counter()
                    except Exception:
                        continue
    
    
            self.is_fetching = False
            self.fetch_button.configure(state="normal")
            self.show_status(f"Tamamlandı! {self.successful_channels} başarılı kanal bulundu.")
    
    
        def is_valid_channel(self, channel_info):
            if channel_info['name'] in ['Bilinmiyor', 'Hata', 'HTTP Hatası', 'Bağlantı Hatası']:
                return False
            if channel_info['status'] != 'Başarılı':
                return False
            return True
    
    
        def fetch_single_channel(self, url, index):
            if self.request_count >= self.max_requests:
                return None
                
            self.show_status(f"Çekiliyor: {index+1}/{len(self.channel_urls)} - {self.get_short_url(url)}")
            
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                'Accept-Language': 'tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3',
                'Connection': 'keep-alive',
            }
    
    
            try:
                response = requests.get(url, headers=headers, timeout=8)
                self.request_count += 1
                self.update_request_display()
    
    
                if response.status_code == 200:
                    channel_info = self.parse_channel_info_fast(response.text, url)
                    if channel_info['name'] == 'Bilinmiyor':
                        return None
                    return channel_info
                else:
                    return None
            except Exception:
                return None
    
    
        def get_short_url(self, url):
            if '/@' in url:
                return '@' + url.split('/@')[-1].split('/')[0]
            elif '/channel/' in url:
                channel_id = url.split('/channel/')[-1].split('/')[0]
                return f"channel:{channel_id[:10]}..."
            elif '/c/' in url:
                return 'c/' + url.split('/c/')[-1].split('/')[0]
            else:
                return url[-15:]
    
    
        # -------------------- GÜNCELLENMİŞ PARSE FONKSİYONU (TABLO ODAKLI) --------------------
        def parse_channel_info_fast(self, html_content, url):
            """
            Kanal bilgilerini doğrudan ytd-about-channel-renderer tablosundan alır.
            Bulamazsa regex fallback kullanır.
            """
            soup = BeautifulSoup(html_content, 'html.parser')
            
            # 1. Kanal adını al (meta/başlık)
            channel_name = self.extract_channel_name_fast(html_content)
            if channel_name == 'Bilinmiyor':
                return {
                    'name': 'Bilinmiyor',
                    'short_url': self.get_short_url(url),
                    'full_url': url,
                    'subscribers': 'N/A',
                    'video_count': 'N/A',
                    'views': 'N/A',
                    'join_date': 'N/A',
                    'status': 'Başarısız',
                    'timestamp': datetime.now().strftime("%H:%M:%S"),
                    'url': url
                }
    
    
            # 2. Tabloyu bul
            table = soup.find('table', class_='style-scope ytd-about-channel-renderer')
            details = {'subscribers': None, 'video_count': None, 'views': None, 'join_date': None}
            
            if table:
                # Tablo içindeki her satırı gez (hidden olmayanları)
                for tr in table.find_all('tr', class_='description-item'):
                    # hidden attribute'u kontrol et (true, false veya boş)
                    if tr.get('hidden') is not None:
                        continue
                    
                    tds = tr.find_all('td')
                    if len(tds) < 2:
                        continue
                    
                    icon_td = tds[0]
                    text_td = tds[1]
                    
                    yt_icon = icon_td.find('yt-icon')
                    if not yt_icon:
                        continue
                    
                    icon_name = yt_icon.get('icon', '')
                    # Metni temizle (&nbsp; vs.)
                    text = text_td.get_text(strip=True)
                    
                    if icon_name == 'person_radar':
                        details['subscribers'] = text
                    elif icon_name == 'my_videos':
                        details['video_count'] = text
                    elif icon_name == 'trending_up':
                        details['views'] = text
                    elif icon_name == 'info_outline':
                        details['join_date'] = text
    
    
            # Eğer tablodan hiçbir şey gelmediyse regex fallback
            if not any(details.values()):
                details = {
                    'subscribers': self.extract_subscribers_fast(html_content),
                    'video_count': self.extract_video_count_fast(html_content),
                    'views': self.extract_total_views_corrected(html_content),
                    'join_date': self.extract_join_date_fast(html_content)
                }
    
    
            return {
                'name': channel_name,
                'short_url': self.get_short_url(url),
                'full_url': url,
                'subscribers': details.get('subscribers', 'Bilinmiyor'),
                'video_count': details.get('video_count', 'Bilinmiyor'),
                'views': details.get('views', 'Bilinmiyor'),
                'join_date': details.get('join_date', 'Bilinmiyor'),
                'status': 'Başarılı',
                'timestamp': datetime.now().strftime("%H:%M:%S"),
                'url': url
            }
    
    
        # -------------------- REGEX FALLBACK METOTLARI --------------------
        def extract_channel_name_fast(self, html_content):
            meta_match = re.search(r'<meta property="og:title" content="([^"]+)"', html_content)
            if meta_match:
                name = meta_match.group(1).replace(' - YouTube', '').strip()
                if name and name != "YouTube" and len(name) > 1:
                    return name[:30]
            title_match = re.search(r'<title>([^<]+)</title>', html_content)
            if title_match:
                name = title_match.group(1).replace(' - YouTube', '').strip()
                if name and name != "YouTube" and len(name) > 1:
                    return name[:30]
            json_ld_match = re.search(r'<script type="application/ld\+json">([^<]+)</script>', html_content)
            if json_ld_match:
                try:
                    json_data = json.loads(json_ld_match.group(1))
                    if isinstance(json_data, list):
                        json_data = json_data[0]
                    if json_data.get('name'):
                        name = json_data['name']
                        if name and name != "YouTube" and len(name) > 1:
                            return name[:30]
                except:
                    pass
            return "Bilinmiyor"
    
    
        def extract_subscribers_fast(self, html_content):
            patterns = [
                r'([\d.,]+\s*(?:[Mm]n|[Mm]ilyon)?)\s*abone',
                r'aria-label="([\d,]+)\s+abone"',
                r'<span[^>]*>([\d,]+)\s+abone</span>',
                r'"subscriberCountText"[^}]*"simpleText":"([\d,]+)"',
                r'"subscribers":"([\d,]+)"',
                r'(\d+(?:,\d+)*)\s*abone',
            ]
            for pattern in patterns:
                match = re.search(pattern, html_content, re.IGNORECASE)
                if match:
                    return match.group(1).strip()
            return "Gizli"
    
    
        def extract_video_count_fast(self, html_content):
            patterns = [
                r'([\d.,]+)\s*video',
                r'aria-label="([\d,]+)\s+video"',
                r'<span[^>]*>([\d,]+)\s+video</span>',
                r'"videosCountText"[^}]*"runs":[^[]*\[[^]]*"text":"([\d,]+)"',
                r'"videoCount":"([\d,]+)"',
                r'(\d+(?:,\d+)*)\s*video',
            ]
            for pattern in patterns:
                match = re.search(pattern, html_content, re.IGNORECASE)
                if match:
                    return match.group(1).strip()
            return "Bilinmiyor"
    
    
        def extract_total_views_corrected(self, html_content):
            patterns = [
                r'([\d.,]+)\s*görüntüleme',
                r'YtmAboutChannelRendererChannelDetail[^>]*>[\s\S]*?([\d,\.]+)\s*görüntüleme',
                r'engagement-panel-section-list[^>]*>[\s\S]*?([\d,\.]+)\s*görüntüleme',
                r'"viewCountText"[^}]*"simpleText":"([\d,\.]+)"',
                r'totalViews["\s:]*([\d,\.]+)',
            ]
            for pattern in patterns:
                match = re.search(pattern, html_content, re.IGNORECASE)
                if match:
                    views = match.group(1).strip()
                    numeric_views = views.replace(',', '').replace('.', '')
                    if numeric_views.isdigit() and len(numeric_views) >= 3:
                        return views
            return "Bilinmiyor"
    
    
        def extract_join_date_fast(self, html_content):
            patterns = [
                r'(\d{1,2}\s+\w+\s+\d{4})\s*tarihinde katıldı',
                r'Joined\s+(\w+\s+\d{1,2},\s+\d{4})',
            ]
            for pattern in patterns:
                match = re.search(pattern, html_content)
                if match:
                    return match.group(1)
            return "Bilinmiyor"
    
    
        # -------------------- UI METOTLARI (değişmedi) --------------------
        def add_result_row(self, channel_info):
            self.after(0, self._add_result_row_ui, channel_info)
    
    
        def _add_result_row_ui(self, channel_info):
            row_frame = ctk.CTkFrame(self.results_container)
            row_frame.pack(fill="x", padx=2, pady=1)
            
            ctk.CTkLabel(row_frame, text=channel_info['name'], width=180, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(row_frame, text=channel_info['short_url'], width=150, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(row_frame, text=channel_info['subscribers'], width=80, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(row_frame, text=channel_info['video_count'], width=60, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(row_frame, text=channel_info['views'], width=90, anchor="w").pack(side="left", padx=2)
            ctk.CTkLabel(row_frame, text=channel_info['join_date'], width=90, anchor="w").pack(side="left", padx=2)
            
            status_color = "green" if channel_info['status'] == 'Başarılı' else "red"
            ctk.CTkLabel(row_frame, text=channel_info['status'], width=70, anchor="w", 
                         text_color=status_color).pack(side="left", padx=2)
    
    
        def show_status(self, message):
            self.after(0, lambda: self.status_label.configure(text=message))
    
    
        def update_request_display(self):
            progress = min(self.request_count / self.max_requests, 1.0)
            self.request_progress.set(progress)
            self.request_label.configure(text=f"Kullanılan: {self.request_count} / {self.max_requests}")
            if progress > 0.8:
                self.request_label.configure(text_color="red")
            elif progress > 0.6:
                self.request_label.configure(text_color="orange")
            else:
                self.request_label.configure(text_color="white")
    
    
        def update_success_counter(self):
            self.success_label.configure(text=f"Başarılı Kanallar: {self.successful_channels}")
    
    
        def export_results_txt(self):
            if not self.results_data:
                messagebox.showwarning("Uyarı", "Dışa aktarılacak veri bulunamadı.\nLütfen önce 'Bilgileri Çek' butonuna tıklayın.")
                return
            try:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"youtube_kanal_sonuclari_{timestamp}.txt"
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write("YouTube Kanal Bilgileri\n")
                    f.write("=" * 150 + "\n")
                    f.write(f"Oluşturulma: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                    f.write(f"Toplam Kanal: {len(self.channel_urls)}\n")
                    f.write(f"Başarılı Kanallar: {self.successful_channels}\n")
                    f.write(f"Kullanılan İstek: {self.request_count}\n")
                    f.write("=" * 150 + "\n\n")
                    f.write(f"{'Kanal Adı':<25} {'Tam Kanal Linki':<50} {'Abone':<10} {'Video':<8} {'Görüntülenme':<12} {'Kuruluş':<12} {'Durum':<10}\n")
                    f.write("-" * 150 + "\n")
                    for data in self.results_data:
                        f.write(f"{data['name']:<25} {data['full_url']:<50} {data['subscribers']:<10} {data['video_count']:<8} {data['views']:<12} {data['join_date']:<12} {data['status']:<10}\n")
                messagebox.showinfo("Başarılı", f"Sonuçlar TXT dosyasına kaydedildi!\nDosya: {filename}")
            except Exception as e:
                messagebox.showerror("Hata", f"TXT dosyası kaydedilemedi: {str(e)}")
    
    
        def export_results_excel(self):
            if not self.results_data:
                messagebox.showwarning("Uyarı", "Dışa aktarılacak veri bulunamadı.\nLütfen önce 'Bilgileri Çek' butonuna tıklayın.")
                return
            try:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"youtube_kanal_sonuclari_{timestamp}.xlsx"
                df_data = []
                for data in self.results_data:
                    df_data.append({
                        'Kanal Adı': data['name'],
                        'Tam Kanal Linki': data['full_url'],
                        'Abone Sayısı': data['subscribers'],
                        'Video Sayısı': data['video_count'],
                        'Toplam Görüntülenme': data['views'],
                        'Katılma Tarihi': data['join_date'],
                        'Durum': data['status'],
                        'Son Güncelleme': data['timestamp']
                    })
                df = pd.DataFrame(df_data)
                with pd.ExcelWriter(filename, engine='openpyxl') as writer:
                    df.to_excel(writer, sheet_name='YouTube Kanalları', index=False)
                    workbook = writer.book
                    worksheet = writer.sheets['YouTube Kanalları']
                    worksheet.column_dimensions['A'].width = 25
                    worksheet.column_dimensions['B'].width = 50
                    worksheet.column_dimensions['C'].width = 15
                    worksheet.column_dimensions['D'].width = 12
                    worksheet.column_dimensions['E'].width = 18
                    worksheet.column_dimensions['F'].width = 15
                    worksheet.column_dimensions['G'].width = 12
                    worksheet.column_dimensions['H'].width = 15
                    for cell in worksheet[1]:
                        cell.font = cell.font.copy(bold=True)
                messagebox.showinfo("Başarılı", f"Sonuçlar Excel dosyasına kaydedildi!\nDosya: {filename}")
            except ImportError:
                messagebox.showerror("Hata", "Excel için pandas ve openpyxl gerekli.\npip install pandas openpyxl")
            except Exception as e:
                messagebox.showerror("Hata", f"Excel dosyası kaydedilemedi: {str(e)}")
    
    
    if __name__ == "__main__":
        app = YouTubeTrackerNoApiApp()
        app.mainloop()
  • 24-07-2026, 22:38:23
    #2
    Hocam ne işe yaradığını yazar mısınız?
  • 24-07-2026, 22:39:29
    #3
    TolgaBayraks adlı üyeden alıntı: mesajı görüntüle
    Hocam ne işe yaradığını yazar mısınız?
    Toplu kanal linklerinin kuruluş abone izlenim gibi özelliklerini tek sefer oto çekiyor scraper tarzı.