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

GitHub

YouTube 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-api uses 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 @HANDLE with the channel handle from the URL the user provided
  • Replace N with 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.