Skip to content

Instantly share code, notes, and snippets.

@Bugaddr
Last active October 15, 2025 11:57
Show Gist options
  • Select an option

  • Save Bugaddr/1450f3d94b612fec2ee9a8d3a6f684e7 to your computer and use it in GitHub Desktop.

Select an option

Save Bugaddr/1450f3d94b612fec2ee9a8d3a6f684e7 to your computer and use it in GitHub Desktop.
Python script to subscribe automatically to youtube channels using selenium
# Example channels.txt
#https://www.youtube.com/channel/UCzml9bXoEM0itbcE96CB03w DroneBot Workshop
#https://www.youtube.com/channel/UCzWQYUVCpZqtN93H8RR44Qw Seeker
#https://www.youtube.com/channel/UCyPYQTT20IgzVw92LDvtClw Squat University
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import time
# Try to import webdriver_manager, fall back to manual setup
try:
from webdriver_manager.firefox import GeckoDriverManager
USE_WEBDRIVER_MANAGER = True
except ImportError:
USE_WEBDRIVER_MANAGER = False
print("Note: webdriver_manager not found. Using system GeckoDriver.")
def read_channels(filename):
"""Read YouTube channels from file."""
channels = []
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and line.startswith('https://'):
url, name = line.split('\t')
channels.append({'url': url, 'name': name})
return channels
def setup_driver():
"""Setup Firefox driver with user profile to maintain login."""
options = Options()
# Use your existing Firefox profile to stay logged in
# To find your profile path:
# 1. Open Firefox and type: about:profiles
# 2. Copy the "Root Directory" path of your default profile
# Uncomment and modify the path below:
# Windows Example:
# options.add_argument("-profile")
# options.add_argument(r"C:\Users\YourUsername\AppData\Roaming\Mozilla\Firefox\Profiles\xxxxxxxx.default-release")
# Linux Example:
options.add_argument("-profile")
options.add_argument("/home/bugaddr/.mozilla/firefox/wmptiknq.default-release")
# Mac Example:
# options.add_argument("-profile")
# options.add_argument("/Users/yourusername/Library/Application Support/Firefox/Profiles/xxxxxxxx.default-release")
# Setup driver
if USE_WEBDRIVER_MANAGER:
service = Service(GeckoDriverManager().install())
driver = webdriver.Firefox(service=service, options=options)
else:
# If webdriver_manager is not installed, use system geckodriver
# Make sure geckodriver is in your PATH or specify the path:
# service = Service('/path/to/geckodriver') # Uncomment and set path if needed
driver = webdriver.Firefox(options=options)
driver.maximize_window()
return driver
def wait_for_login(driver):
"""Wait for user to manually log in to YouTube."""
print("\n" + "="*60)
print("PLEASE LOG IN TO YOUTUBE")
print("="*60)
print("The browser will open YouTube's homepage.")
print("Please log in to your YouTube account.")
print("After logging in, return here and press Enter to continue.")
print("="*60 + "\n")
driver.get("https://www.youtube.com")
input("Press Enter after you've logged in to YouTube...")
def subscribe_to_channel(driver, channel_url, channel_name, wait_time=10):
"""
Navigate to a channel and click the Subscribe button.
Returns:
str: Status of subscription attempt
"""
try:
driver.get(channel_url)
time.sleep(2) # Wait for page to start loading
wait = WebDriverWait(driver, wait_time)
# Try to find Subscribe button - YouTube uses different selectors
subscribe_button = None
selectors = [
"//button[contains(@aria-label, 'Subscribe')]",
"//yt-button-shape//button[contains(., 'Subscribe')]",
"//button[@aria-label='Subscribe to']",
"//ytd-subscribe-button-renderer//button",
"//button[contains(text(), 'Subscribe')]"
]
for selector in selectors:
try:
subscribe_button = wait.until(
EC.element_to_be_clickable((By.XPATH, selector))
)
break
except TimeoutException:
continue
if subscribe_button:
# Check if already subscribed
button_text = subscribe_button.get_attribute('aria-label') or subscribe_button.text
if 'subscribed' in button_text.lower():
return "Already Subscribed"
# Click the Subscribe button
subscribe_button.click()
time.sleep(1)
# Handle notification preferences popup if it appears
try:
notification_button = WebDriverWait(driver, 3).until(
EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='All']"))
)
notification_button.click()
time.sleep(0.5)
except:
pass # Popup might not appear
return "Subscribed ✓"
else:
return "Subscribe button not found"
except TimeoutException:
return "Timeout - page took too long to load"
except Exception as e:
return f"Error: {str(e)[:50]}"
def main():
filename = 'channels.txt'
print("YouTube Auto-Subscriber with Selenium (Firefox)")
print("=" * 60)
try:
channels = read_channels(filename)
print(f"\nFound {len(channels)} channels to subscribe to.")
# Setup driver
print("\nSetting up Firefox browser...")
driver = setup_driver()
# Wait for login
wait_for_login(driver)
# Start subscribing
print("\n" + "=" * 60)
print("STARTING SUBSCRIPTION PROCESS")
print("=" * 60 + "\n")
success_count = 0
already_subscribed = 0
failed_count = 0
for i, channel in enumerate(channels, 1):
print(f"[{i}/{len(channels)}] {channel['name']:<40}", end=" ... ")
status = subscribe_to_channel(driver, channel['url'], channel['name'])
print(status)
if status == "Subscribed ✓":
success_count += 1
elif status == "Already Subscribed":
already_subscribed += 1
else:
failed_count += 1
# Small delay between channels
time.sleep(1.5)
# Summary
print("\n" + "=" * 60)
print("SUBSCRIPTION COMPLETE!")
print("=" * 60)
print(f"✓ Successfully subscribed: {success_count}")
print(f"• Already subscribed: {already_subscribed}")
print(f"✗ Failed: {failed_count}")
print("=" * 60)
input("\nPress Enter to close the browser...")
driver.quit()
except FileNotFoundError:
print(f"Error: Could not find '{filename}'")
except Exception as e:
print(f"Error: {e}")
if 'driver' in locals():
driver.quit()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment