Scrape YouTube.
No API key needed.

Search videos, browse channels, fetch transcripts, pull comments, and scrape playlists using YouTube's InnerTube API. Sync + async. Python 3.10+.

Python 3.10+ MIT License Sync + Async No API Key
$ pip install tubescrape click to copy
🔍

Search

Search videos, channels, and shorts with filters for date, duration, type, and features.

📺

Channels

Browse videos, streams, shorts, playlists, search within a channel, and get about info.

📄

Transcripts

Fetch transcripts with language selection, translation, and export to SRT, VTT, JSON.

💬

Comments

Get comments with likes, replies, author info, hearted status, and pagination.

🎬

Video Info

Full metadata: likes, chapters, AI summary, people mentioned, subscriber count.

🔒

No API Key

Uses YouTube's InnerTube internal API. No Google account or API key needed.


# Installation

# Core SDK
pip install tubescrape

# With CLI (click + rich)
pip install tubescrape[cli]

# With REST API (FastAPI)
pip install tubescrape[api]

# Everything
pip install tubescrape[all]

Requires Python 3.10+. Only dependency is httpx.

# Quick Start

from tubescrape import YouTube

yt = YouTube()

# Search
results = yt.search('python tutorial')

# Channel videos
videos = yt.get_channel_videos('@lexfridman')

# Video info with likes, chapters, AI summary
info = yt.get_video_info('https://youtube.com/watch?v=dQw4w9WgXcQ')

# Transcript
transcript = yt.get_transcript('dQw4w9WgXcQ')

# Comments with replies
comments = yt.get_comments('dQw4w9WgXcQ', replies=True)

# All methods accept video/channel URLs, IDs, or @handles
Tip: Use yt.close() when done, or use as a context manager: with YouTube() as yt: ...

# Search Filters

Filter search results by type, date, duration, sort order, and features.

# Long videos uploaded today, sorted by views
yt.search('podcast', type='video', duration='long',
         upload_date='today', sort_by='view_count')

# 4K HDR content
yt.search('nature', features=['4k', 'hdr'])

# Search for channels
yt.search('tech', type='channel')
Parameter Options
sort_by relevance, upload_date, view_count, rating
upload_date last_hour, today, this_week, this_month, this_year
type video, channel, playlist, movie
duration short (<4min), medium (4-20min), long (>20min)
features live, 4k, hd, subtitles, cc, creative_commons, 360, vr180, 3d, hdr

# Channel Search Results

When searching with type='channel', results include handle and verified status.

result = yt.search('lex fridman', type='channel')

for ch in result.channels:
    print(ch.title)            # "Lex Fridman"
    print(ch.handle)           # "@lexfridman"
    print(ch.is_verified)      # True
    print(ch.subscriber_count) # "5.01M subscribers"

# Channel Videos

Get uploads from a channel's Videos tab. Accepts channel IDs, @handles, or URLs.

get_channel_videos(channel, max_results=30) -> BrowseResult

result = yt.get_channel_videos('@lexfridman', max_results=5)

print(result.channel)     # "Lex Fridman"
print(result.channel_id)  # "UCSHZKyawb77ixDdsGog4iWA"

for v in result.videos:
    print(v.title, v.duration, v.view_count)
Note: YouTube separates regular uploads from live streams and shorts into different tabs. Use get_channel_streams() for live content and get_channel_shorts() for shorts.

# Channel Streams

Fetch past broadcasts, premieres, and scheduled live streams from the Live tab.

get_channel_streams(channel, max_results=0) -> BrowseResult

streams = yt.get_channel_streams('@CNN', max_results=5)

for v in streams.videos:
    print(v.title, v.is_live)

Returns the same BrowseResult with VideoResult objects. Currently live streams have is_live=True.

# Channel Shorts

Get shorts from a channel's Shorts tab.

get_channel_shorts(channel, max_results=0) -> ShortsResult

shorts = yt.get_channel_shorts('@CNN', max_results=10)

for s in shorts.shorts:
    print(s.title, s.view_count, s.url)

Returns ShortResult objects with video_id, title, view_count, and thumbnail_url.

# Channel Playlists

List playlists from a channel's Playlists tab.

get_channel_playlists(channel) -> ChannelPlaylistsResult

result = yt.get_channel_playlists('@lexfridman')

for p in result.playlists:
    print(p.title, p.video_count, p.url)

# Channel About

Get channel description, country, join date, total views, and external links.

get_channel_about(channel) -> ChannelAbout

about = yt.get_channel_about('@ycombinator')

print(about.title)             # "Y Combinator"
print(about.description)       # Full channel description
print(about.country)           # "United States"
print(about.joined_date)       # "Oct 24, 2013"
print(about.subscriber_count)  # "2.32M subscribers"
print(about.view_count)        # "103,989,970 views"
print(about.video_count)       # "880 videos"

for link in about.links:
    print(link.title, link.url)  # "Apply to YC" "ycombinator.com/apply"

# Video Info

Get full video metadata including likes, chapters, AI summary, and people mentioned.

get_video_info(video, enrich=True) -> VideoInfo

info = yt.get_video_info('https://youtube.com/watch?v=dQw4w9WgXcQ')

# Basic metadata
info.title            # Video title
info.channel          # Channel name
info.description      # Full description
info.view_count       # 119556 (int)
info.duration_seconds # 2940
info.publish_date     # "2026-07-26T13:32:05-07:00"
info.category         # "Science & Technology"
info.keywords         # ["YC", "Y Combinator"]

# Enriched data (from /next endpoint, requires enrich=True)
info.like_count       # "3.8K"
info.comment_count    # 148
info.subscriber_count # "2.32M subscribers"
info.date_text        # "Jul 26, 2026"
info.ai_summary       # YouTube's AI-generated summary

# Chapters
for ch in info.chapters:
    print(ch.time_description, ch.start_seconds, ch.title)

# People mentioned
for p in info.people_mentioned:
    print(p.name, p.description)
Performance: Set enrich=False for a single API call with basic metadata only (no likes, chapters, or AI summary). Default enrich=True uses 2 API calls.

# Comments

Fetch top-level comments with author info, likes, and optional replies.

get_comments(video, max_results=20, replies=False) -> CommentsResult

# Top-level comments only
result = yt.get_comments('dQw4w9WgXcQ', max_results=10)

print(result.comment_count)  # Total comments on video

for c in result.comments:
    print(c.author)            # "@username"
    print(c.text)              # Comment text
    print(c.like_count)        # 66
    print(c.reply_count)       # 2
    print(c.published_text)    # "2 days ago"
    print(c.is_hearted)        # True (creator hearted)
    print(c.is_creator)        # True (channel owner)

# With replies (1 extra API call per comment with replies)
result = yt.get_comments('dQw4w9WgXcQ', replies=True)

for c in result.comments:
    for r in c.replies:
        print(r.author, r.text)

# Get ALL comments
all_comments = yt.get_comments('dQw4w9WgXcQ', max_results=0)
Note: YouTube comments are 2 levels deep. Replies to replies appear as flat level-1 replies with an @mention. Setting replies=True triggers one extra API call per comment that has replies.

# Transcripts

Fetch video transcripts with language selection, translation, and export to multiple formats.

get_transcript(video, languages=None, timestamps=True, translate_to=None) -> Transcript

# Basic transcript
t = yt.get_transcript('dQw4w9WgXcQ')
print(t.text)  # Full text joined

# With segments (timestamps)
for s in t.segments:
    print(s.start, s.duration, s.text)

# Specific language
t = yt.get_transcript('video_id', languages=['es', 'en'])

# Translate to another language
t = yt.get_transcript('video_id', translate_to='fr')

# Plain text (no timestamps)
t = yt.get_transcript('video_id', timestamps=False)

list_transcripts(video) -> list[TranscriptListEntry]

tracks = yt.list_transcripts('dQw4w9WgXcQ')

for t in tracks:
    print(t.language, t.language_code, t.is_generated, t.is_translatable)

Formatting & Saving

# Format as SRT, VTT, JSON, or text
srt = YouTube.format_transcript(transcript, 'srt')
vtt = YouTube.format_transcript(transcript, 'vtt')

# Save to file (format inferred from extension)
transcript.save('subtitles.srt')
transcript.save('output.json')
transcript.save('captions.vtt')

# Playlists

Fetch videos from any YouTube playlist with full pagination.

get_playlist(playlist, max_results=0) -> PlaylistResult

result = yt.get_playlist('PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf')

# Also accepts full URLs
result = yt.get_playlist('https://youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf')

print(result.title)    # Playlist title
print(result.channel)  # Playlist owner

for v in result.videos:
    print(v.title, v.channel, v.duration)

# Async Support

Every method has an async counterpart prefixed with a.

import asyncio
from tubescrape import YouTube

async def main():
    async with YouTube() as yt:
        result = await yt.asearch('python')
        info = await yt.aget_video_info('dQw4w9WgXcQ')
        transcript = await yt.aget_transcript('dQw4w9WgXcQ')
        comments = await yt.aget_comments('dQw4w9WgXcQ')

asyncio.run(main())
Sync Async
search() asearch()
get_channel_videos() aget_channel_videos()
get_channel_streams() aget_channel_streams()
get_channel_shorts() aget_channel_shorts()
get_channel_playlists() aget_channel_playlists()
get_channel_about() aget_channel_about()
search_channel() asearch_channel()
get_video_info() aget_video_info()
get_comments() aget_comments()
get_transcript() aget_transcript()
list_transcripts() alist_transcripts()
get_playlist() aget_playlist()

# Proxies

Configure proxies for scraping at scale. Supports proxy rotation and separate transcript proxies.

# Single proxy
yt = YouTube(proxy='http://user:pass@host:port')

# Proxy rotation (round-robin)
yt = YouTube(proxies=[
    'http://proxy1:8080',
    'http://proxy2:8080',
    'http://proxy3:8080',
])

# Separate residential proxies for transcripts
# (YouTube's player endpoint is stricter about datacenter IPs)
yt = YouTube(
    proxies=['http://dc-proxy:8080'],               # search/browse
    transcript_proxies=['http://resi-proxy:8080'],  # transcripts
)
Parameter Description
proxy Single proxy URL for all requests
proxies List of proxy URLs for rotation
timeout Request timeout in seconds (default: 30)
max_retries Max retry attempts on failure (default: 3)
cookies Additional cookies dict for all requests
transcript_proxy Residential proxy for transcript endpoints
transcript_proxies List of residential proxies for transcript rotation

# URL Parsing

Extract IDs from any YouTube URL format. All methods accept URLs, IDs, or @handles directly, but these utilities are available if needed.

from tubescrape import YouTube

# Video ID from any format
YouTube.extract_video_id('https://youtube.com/watch?v=dQw4w9WgXcQ')  # 'dQw4w9WgXcQ'
YouTube.extract_video_id('https://youtu.be/dQw4w9WgXcQ')           # 'dQw4w9WgXcQ'
YouTube.extract_video_id('dQw4w9WgXcQ')                          # 'dQw4w9WgXcQ'

# Channel ID
YouTube.extract_channel_id('https://youtube.com/@lexfridman')      # '@lexfridman'
YouTube.extract_channel_id('UCxxxxxx')                           # 'UCxxxxxx'

# Playlist ID
YouTube.extract_playlist_id('https://youtube.com/playlist?list=PLxxx')  # 'PLxxx'

# Pagination

All list methods support max_results. Set to 0 to get everything.

Method Default Get all
search() 20 max_results=0
get_channel_videos() 30 max_results=0
get_channel_streams() 0 (all) default
get_channel_shorts() 0 (all) default
get_comments() 20 max_results=0
get_playlist() 0 (all) default
search_channel() 0 (all) default

# Serialization

Every model has a to_dict() method for JSON serialization. Output is sparse: fields with empty or default values are excluded.

import json

info = yt.get_video_info('dQw4w9WgXcQ')
print(json.dumps(info.to_dict(), indent=2))

comments = yt.get_comments('dQw4w9WgXcQ')
print(json.dumps(comments.to_dict(), indent=2))