Need Channel IDs for a data pipeline, research project, or automation? Here is how to extract them with Python — from a single URL to bulk CSV processing.
By the end of this guide, you will have a Python function (or script) that:
requests library: pip install requestsOptionally: pandas for CSV processing (pip install pandas)
import re
import requests
API_KEY = "your_api_key_here"
BASE_URL = "https://www.googleapis.com/youtube/v3/channels"
def get_channel_id(url: str) -> str | None:
"""
Resolve a YouTube channel URL to its Channel ID.
Supports:
- https://youtube.com/@handle
- https://youtube.com/channel/UCxxxxxxxx
- https://youtube.com/c/customname
- https://youtube.com/user/username
"""
url = url.strip()
# Case 1: /channel/ format — ID is already in the URL
match = re.search(r"youtube\.com/channel/(UC[a-zA-Z0-9_-]{22})", url)
if match:
return match.group(1)
# Case 2: @handle format (post-2022)
match = re.search(r"youtube\.com/(@[\w.-]+)", url)
if match:
resp = requests.get(BASE_URL, params={
"part": "id",
"forHandle": match.group(1),
"key": API_KEY
})
resp.raise_for_status()
items = resp.json().get("items", [])
return items[0]["id"] if items else None
# Case 3: Legacy /user/ format
match = re.search(r"youtube\.com/user/([\w-]+)", url)
if match:
resp = requests.get(BASE_URL, params={
"part": "id",
"forUsername": match.group(1),
"key": API_KEY
})
resp.raise_for_status()
items = resp.json().get("items", [])
return items[0]["id"] if items else None
# Case 4: /c/ custom URL — API does not support this directly
match = re.search(r"youtube\.com/c/([\w-]+)", url)
if match:
return _get_id_from_page(url)
return None
def _get_id_from_page(url: str) -> str | None:
"""Extract Channel ID from YouTube page source."""
headers = {"User-Agent": "Mozilla/5.0"}
try:
resp = requests.get(url, headers=headers, timeout=10)
match = re.search(r'"channelId":"(UC[a-zA-Z0-9_-]{22})"', resp.text)
return match.group(1) if match else None
except Exception:
return None
# Quick test
if __name__ == "__main__":
test_urls = [
"https://youtube.com/@mkbhd",
"https://youtube.com/channel/UCBcRF18a7Qf58cCRy5xuWwQ",
"https://youtube.com/user/marquesbrownlee",
]
for url in test_urls:
channel_id = get_channel_id(url)
print(f"{url} → {channel_id}")
If you have a list of channels in a spreadsheet, process them all at once:
channels.csv:
name,url
MKBHD,https://youtube.com/@mkbhd
Linus Tech Tips,https://youtube.com/@linustechtips
Veritasium,https://youtube.com/@veritasium
bulk_lookup.py:
import csv
import time
def process_csv(input_path: str, output_path: str, delay: float = 0.5):
"""
Read channel URLs from a CSV, look up Channel IDs, write results.
delay: seconds to wait between API calls (rate limiting)
"""
rows = []
with open(input_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
url = row.get("url", "").strip()
channel_id = get_channel_id(url) if url else None
rows.append({
**row,
"channel_id": channel_id or "NOT_FOUND",
})
print(f"Processed: {row.get('name', url)} → {channel_id}")
time.sleep(delay) # Stay within quota
if rows:
fieldnames = list(rows[0].keys())
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f"\nSaved {len(rows)} results to {output_path}")
process_csv("channels.csv", "channels_with_ids.csv")
Channel IDs are permanent — once you have one, you never need to look it up again. Cache aggressively:
import json
from pathlib import Path
CACHE_FILE = Path("channel_id_cache.json")
def load_cache() -> dict:
if CACHE_FILE.exists():
return json.loads(CACHE_FILE.read_text())
return {}
def save_cache(cache: dict):
CACHE_FILE.write_text(json.dumps(cache, indent=2))
def get_channel_id_cached(url: str) -> str | None:
cache = load_cache()
if url in cache:
return cache[url]
channel_id = get_channel_id(url)
if channel_id:
cache[url] = channel_id
save_cache(cache)
return channel_id
For production workloads, replace the JSON file cache with Redis or a database.
The YouTube Data API gives you 10,000 units/day for free. Each channels.list call with part=id costs 1 unit.
For larger volumes:
python
ids = ["UC1...", "UC2...", "UC3..."]
resp = requests.get(BASE_URL, params={
"part": "snippet",
"id": ",".join(ids),
"key": API_KEY
})import time
def get_channel_id_safe(url: str, retries: int = 3) -> str | None:
"""get_channel_id with retry logic for transient errors."""
for attempt in range(retries):
try:
return get_channel_id(url)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 403:
print("Quota exceeded. Wait until midnight Pacific or use a new key.")
return None
if attempt < retries - 1:
time.sleep(2 ** attempt)
except requests.exceptions.ConnectionError:
if attempt < retries - 1:
time.sleep(2 ** attempt)
return None
| Problem | Cause | Fix |
|---|---|---|
items: [] returned |
Handle/username not found | Check URL is correct; verify on channelid.app |
403 quotaExceeded |
Daily quota hit | Cache IDs; use new API key; wait until midnight PT |
/c/ URL returns None |
API cannot resolve /c/ format | Use _get_id_from_page() fallback |
| Rate limit on page scraping | Too many requests to YouTube | Add time.sleep(1) between scrape calls |
| URL Format | Resolution Method | API Cost |
|---|---|---|
/channel/UCxxxxxx |
Extract from URL (regex) | 0 units |
@handle |
channels.list?forHandle= |
1 unit |
/user/username |
channels.list?forUsername= |
1 unit |
/c/customname |
Page scrape + regex | 0 API units |
For a quick one-off lookup without writing code, use channelid.app — paste any URL, get the ID instantly.
Paste any channel URL — @handle, /c/, /channel/, or /user/ format — and get the Channel ID in one click.
Try channelid.app — Free