youtube-transcript
Fetch YouTube video transcripts using youtube-transcript-api with residential proxy (DataImpulse) to bypass YouTube's cloud IP blocks. Use when the user wants to get a transcript/captions from a YouTu
SKILL.md.claude/skills/youtube-transcript/SKILL.md
GitHubYouTube Transcript Fetcher
Fetches transcripts from YouTube videos. Works from cloud environments (Vercel, Codespaces, AWS, etc.) via residential proxy.
Key Facts
- YouTube blocks requests from cloud IPs (AWS, Azure, GCP, Vercel) without proxy
youtube-transcript-apiuses undocumented YouTube endpoints (same as youtubetotranscript.com)- DataImpulse proxy rotates IPs automatically per-request — no manual rotation needed
- Requires video to have captions (auto-generated by YouTube counts)
Setup
pip install youtube-transcript-api
Credentials in .env:
DATAIMPULSE_USER=12d41802bcf94c896c47
DATAIMPULSE_PASS=5c09f4d353af7366
DATAIMPULSE_HOST=gw.dataimpulse.com:823
Usage
Python (basic)
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig
import os
proxy_url = f"http://{os.environ['DATAIMPULSE_USER']}:{os.environ['DATAIMPULSE_PASS']}@{os.environ['DATAIMPULSE_HOST']}"
ytt = YouTubeTranscriptApi(
proxy_config=GenericProxyConfig(
http_url=proxy_url,
https_url=proxy_url,
)
)
transcript = ytt.fetch('VIDEO_ID') # or full URL — extract ID first
text = ' '.join([s.text for s in transcript])
Extract video ID from URL
import re
def extract_video_id(url: str) -> str:
patterns = [
r'youtu\.be/([^?&]+)',
r'youtube\.com/watch\?v=([^&]+)',
r'youtube\.com/embed/([^?&]+)',
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
return match.group(1)
return url # assume it's already a video ID
video_id = extract_video_id('https://youtu.be/PknH_XtVL_E')
With language selection
# List available transcript languages
transcript_list = ytt.list('VIDEO_ID')
for t in transcript_list:
print(t.language, t.language_code, t.is_generated)
# Fetch specific language (fallback to auto-generated)
transcript = ytt.fetch('VIDEO_ID', languages=['pl', 'en'])
Node.js alternative (if needed)
npm install youtube-caption-extractor
import { getSubtitles } from 'youtube-caption-extractor'
const transcript = await getSubtitles({ videoID: 'PknH_XtVL_E', lang: 'en' })
const text = transcript.map(s => s.text).join(' ')
Note: Node.js version may also need a proxy; Python version is more reliable.
Proxy Details
| Field | Value |
|---|---|
| Provider | DataImpulse (dataimpulse.com) |
| Server | gw.dataimpulse.com:823 |
| Type | Residential rotating |
| Rotation | Automatic per-request |
| Auth | Username/password |
Error Handling
from youtube_transcript_api import (
TranscriptsDisabled,
NoTranscriptFound,
VideoUnavailable,
RequestBlocked,
)
try:
transcript = ytt.fetch(video_id)
except TranscriptsDisabled:
print("No captions available for this video")
except NoTranscriptFound:
print("No transcript in requested language")
except VideoUnavailable:
print("Video is private or deleted")
except RequestBlocked:
print("Still blocked — check proxy credentials")
Channel Bulk Download
When the user says something like "download transcripts for the 20 most recent videos from this channel", use this workflow.
Step 1 — Install yt-dlp (if not present)
pip install yt-dlp -q
Step 2 — Get video IDs from channel
yt-dlp --flat-playlist --print "%(id)s %(title)s" \
"https://www.youtube.com/@HANDLE/videos" \
--playlist-end N 2>/dev/null
- Replace
@HANDLEwith the channel handle from the URL the user provided - Replace
Nwith the number of videos requested - Works with any channel URL format:
@handle,/channel/UCxxx,/c/name
Step 3 — Fetch all transcripts
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig
import json, time
DATAIMPULSE_USER = "12d41802bcf94c896c47"
DATAIMPULSE_PASS = "5c09f4d353af7366"
DATAIMPULSE_HOST = "gw.dataimpulse.com:823"
proxy_url = f"http://{DATAIMPULSE_USER}:{DATAIMPULSE_PASS}@{DATAIMPULSE_HOST}"
ytt = YouTubeTranscriptApi(
proxy_config=GenericProxyConfig(http_url=proxy_url, https_url=proxy_url)
)
videos = [
# list of (video_id, title) tuples from Step 2
]
results = []
for vid_id, title in videos:
try:
fetched = ytt.fetch(vid_id)
text = " ".join([t.text for t in fetched])
results.append({"id": vid_id, "title": title, "url": f"https://youtu.be/{vid_id}", "transcript": text, "words": len(text.split())})
print(f"✓ {title[:65]} ({len(text.split()):,} words)")
except Exception as e:
results.append({"id": vid_id, "title": title, "url": f"https://youtu.be/{vid_id}", "transcript": None, "error": str(e)[:100]})
print(f"✗ {title[:65]} — {str(e)[:80]}")
time.sleep(0.5)
output_file = "HANDLE-transcripts.json" # use channel handle in filename
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
succeeded = sum(1 for r in results if r["transcript"])
total_words = sum(r.get("words", 0) for r in results)
print(f"\nDone. {succeeded}/{len(results)} succeeded. {total_words:,} total words → {output_file}")
Output format
Saved as HANDLE-transcripts.json (e.g. calebulku-transcripts.json) in the working directory:
[
{
"id": "PknH_XtVL_E",
"title": "How To Rank Fast | Alex Hormozi Gyms Are Doing Local SEO Wrong",
"url": "https://youtu.be/PknH_XtVL_E",
"transcript": "full transcript text...",
"words": 4516
}
]
Tested
- Channel:
@calebulku(394 videos), 20 videos, 20/20 succeeded, 67,090 words - Environment: GitHub Codespaces (Azure) — cloud IP bypassed via DataImpulse proxy
- Date: 2026-03-02
Single Video — Tested
Confirmed working from GitHub Codespaces (Azure) with DataImpulse proxy on 2026-03-02.
Video tested: PknH_XtVL_E — returned 645 segments, 24,585 chars.