A YouTube scraper powered by yt-dlp - available in both Python and Node.js. No API key required.
The scraper wraps yt-dlp CLI to extract YouTube metadata without downloading videos.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Your Code │ ───► │ yt-dlp CLI │ ───► │ YouTube │
│ (scraper) │ ◄─── │ (JSON out) │ ◄─── │ Servers │
└─────────────┘ └─────────────┘ └─────────────┘
Flow:
- You call a method (e.g.,
get_video(url)) - Scraper runs:
yt-dlp --dump-json --no-download "URL" - yt-dlp fetches data from YouTube and returns raw JSON
- Scraper transforms it into a clean, unified format
youtube-scraper/
├── python/
│ ├── scraper.py
│ └── requirements.txt
├── nodejs/
│ ├── scraper.js
│ └── package.json
└── README.md
| Feature | Python | Node.js | Description |
|---|---|---|---|
| Video metadata | ✅ | ✅ | Get title, views, likes, duration, thumbnail, etc. |
| Search | ✅ | ✅ | Search YouTube and get video results |
| Channel videos | ✅ | ✅ | Get all videos from a channel |
| Playlists | ✅ | ✅ | Get all videos in a playlist |
| Download | ✅ | ✅ | Download video or audio (MP3) |
| Transcripts | ✅ | ✅ | Get video subtitles/captions |
| Comments | ✅ | ✅ | Get video comments |
| JSON Export | ✅ | ✅ | Export results to JSON file |
| No API key | ✅ | ✅ | Works without YouTube API credentials |
# Install yt-dlp (required)
pip install yt-dlp
# For Node.js, also install dependencies
cd nodejs && npm installcd python
# Get video metadata
python scraper.py video "https://youtube.com/watch?v=VIDEO_ID"
# Search YouTube
python scraper.py search "python tutorial" -n 10
# Get channel videos
python scraper.py channel "@channelname" -n 20
# Get playlist
python scraper.py playlist "https://youtube.com/playlist?list=PLAYLIST_ID"
# Get transcript/subtitles
python scraper.py transcript "VIDEO_URL" -l en
# Get comments
python scraper.py comments "VIDEO_URL" -n 100
# Download video
python scraper.py download "VIDEO_URL"
# Download audio only (MP3)
python scraper.py download "VIDEO_URL" --audio
# Export to JSON file
python scraper.py video "VIDEO_URL" -o output.jsoncd nodejs
# Get video metadata
node scraper.js video "https://youtube.com/watch?v=VIDEO_ID"
# Search YouTube
node scraper.js search "python tutorial" --max 10
# Get channel videos
node scraper.js channel "@channelname" --max 20
# Get playlist
node scraper.js playlist "https://youtube.com/playlist?list=PLAYLIST_ID"
# Get transcript/subtitles
node scraper.js transcript "VIDEO_URL" --lang en
# Get comments
node scraper.js comments "VIDEO_URL" --max 100
# Download video
node scraper.js download "VIDEO_URL"
# Download audio only (MP3)
node scraper.js download "VIDEO_URL" --audio
# Export to JSON file
node scraper.js video "VIDEO_URL" --output output.jsonfrom scraper import YTDLPScraper
scraper = YTDLPScraper()
# Get video metadata
video = scraper.get_video("https://youtube.com/watch?v=VIDEO_ID")
print(video['title'], video['views'])
# Search YouTube
results = scraper.search("python tutorial", max_results=10)
for video in results:
print(video['title'])
# Get channel videos
videos = scraper.get_channel("@channelname", max_videos=20)
# Get playlist
playlist = scraper.get_playlist("PLAYLIST_URL")
print(f"Playlist: {playlist['title']} ({playlist['video_count']} videos)")
# Get transcript
transcript = scraper.get_transcript("VIDEO_URL", lang="en")
for segment in transcript:
print(f"[{segment['start']:.1f}s] {segment['text']}")
# Get comments
comments = scraper.get_comments("VIDEO_URL", max_comments=100)
for comment in comments:
print(f"@{comment['author']}: {comment['text']}")
# Download
scraper.download("VIDEO_URL", audio_only=True)const { YTDLPScraper } = require('./scraper');
const scraper = new YTDLPScraper();
// Get video metadata
const video = await scraper.getVideo('https://youtube.com/watch?v=VIDEO_ID');
console.log(video.title, video.views);
// Search YouTube
const results = await scraper.search('python tutorial', 10);
results.forEach(v => console.log(v.title));
// Get channel videos
const videos = await scraper.getChannel('@channelname', 20);
// Get playlist
const playlist = await scraper.getPlaylist('PLAYLIST_URL');
console.log(`Playlist: ${playlist.title} (${playlist.video_count} videos)`);
// Get transcript
const transcript = await scraper.getTranscript('VIDEO_URL', 'en');
transcript.forEach(s => console.log(`[${s.start}s] ${s.text}`));
// Get comments
const comments = await scraper.getComments('VIDEO_URL', 100);
comments.forEach(c => console.log(`@${c.author}: ${c.text}`));
// Download
await scraper.download('VIDEO_URL', { audioOnly: true });
// Export to JSON
scraper.exportJson(results, 'results.json');All methods return a unified data format - consistent across Python and Node.js.
{
"video_id": "dQw4w9WgXcQ",
"title": "Rick Astley - Never Gonna Give You Up",
"description": "The official video for...",
"channel_name": "Rick Astley",
"channel_id": "UCuAXFkgsw1L7xaCfnd5JJOw",
"channel_url": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw",
"views": 1500000000,
"likes": 15000000,
"comments_count": 3000000,
"duration_seconds": 212,
"duration_string": "3:32",
"upload_date": "20091025",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"tags": ["rick astley", "never gonna give you up", "80s"],
"is_live": false,
"is_short": false,
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}[
{
"video_id": "abc123",
"title": "Video Title",
"channel_name": "Channel Name",
"channel_id": "UCxxxx",
"views": 10000,
"duration_seconds": 180,
"duration_string": "3:00",
"thumbnail_url": "https://i.ytimg.com/vi/abc123/hqdefault.jpg",
"published_time": "20230101",
"url": "https://www.youtube.com/watch?v=abc123"
}
]{
"playlist_id": "PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf",
"title": "My Playlist",
"description": "A collection of videos",
"channel_name": "Channel Name",
"video_count": 25,
"videos": [
{
"index": 1,
"video_id": "abc123",
"title": "First Video",
"channel_name": "Channel",
"duration_seconds": 240,
"duration_string": "4:00",
"thumbnail_url": "https://...",
"url": "https://www.youtube.com/watch?v=abc123"
}
],
"url": "https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf"
}[
{
"start": 0.0,
"duration": 2.5,
"text": "Hello and welcome"
},
{
"start": 2.5,
"duration": 3.0,
"text": "to this video"
}
][
{
"author": "Username",
"author_id": "UCxxxx",
"author_url": "https://www.youtube.com/channel/UCxxxx",
"text": "Great video!",
"likes": 150,
"published_time": "2 months ago",
"reply_count": 5,
"is_reply": false
}
]| Method | Parameters | Returns |
|---|---|---|
get_video(url) |
Video URL | Video metadata dict |
search(query, max_results=20) |
Search query, limit | List of video dicts |
get_channel(channel, max_videos=30) |
@username or URL, limit | List of video dicts |
get_playlist(url) |
Playlist URL | Playlist dict with videos |
get_transcript(url, lang="en") |
Video URL, language code | List of transcript segments |
get_comments(url, max_comments=100) |
Video URL, limit | List of comment dicts |
download(url, output, audio_only) |
Video URL, filename, audio flag | Boolean success |
| Method | Parameters | Returns |
|---|---|---|
getVideo(url) |
Video URL | Promise |
search(query, maxResults=20) |
Search query, limit | Promise<Video[]> |
getChannel(channel, maxVideos=30) |
@username or URL, limit | Promise<Video[]> |
getPlaylist(url) |
Playlist URL | Promise |
getTranscript(url, lang="en") |
Video URL, language code | Promise<Transcript[]> |
getComments(url, maxComments=100) |
Video URL, limit | Promise<Comment[]> |
download(url, options) |
Video URL, {output, audioOnly} | Promise |
exportJson(data, filename) |
Data object, filepath | void |
- Channel URLs: Accepts both
@usernameformat and full URLs - Shorts detection: Automatically detects YouTube Shorts via URL path
- Duration formatting: Converts seconds to
MM:SSorHH:MM:SSautomatically - Rate limiting: yt-dlp handles rate limiting internally
- Transcripts: Falls back to auto-generated captions if manual subtitles unavailable
- Comments: May take time to fetch on videos with many comments
MIT