YouTube Scraper (Videos, Comments, Transcripts)
Pricing
from $8.00 / 1,000 results
YouTube Scraper (Videos, Comments, Transcripts)
Scrape YouTube without an API key: video metadata, comments, and transcripts/captions by video URL, video ID, or keyword search. Uses YouTube's InnerTube endpoints. Export JSON/CSV/Excel.
Pricing
from $8.00 / 1,000 results
Rating
0.0
(0)
Developer
Shahryar
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
YouTube Scraper – Videos, Comments & Transcripts (No API Key)
YouTube Scraper extracts structured data from YouTube without an API key or login: video metadata, comments (with replies), and transcripts/captions — by video URL, video ID, or keyword search. Export to JSON, CSV, or Excel.
This is a no-API-key YouTube comments API, YouTube transcript scraper, and video-metadata scraper in one Actor. Built for content researchers, marketers, data teams, and creators who need YouTube data at scale without the official Data API's quota limits.
What it does
- 🎬 Video metadata – title, description, channel name/ID/URL, view count, approximate like count, publish/upload date, duration, category, tags/keywords, thumbnails, and the list of available caption languages.
- 💬 Comments – author, text, like count, reply count, relative published time, pinned/hearted flags, and an avatar URL. Optionally expand reply threads.
- 📝 Transcripts – full transcript text plus timestamped segments, for manual or auto-generated captions, in your preferred language. Best-effort (see FAQ).
- 🔎 Keyword search – run a YouTube video search and automatically enrich every matching video.
- 🌍 Language control – pick the caption language and prefer human captions over auto-generated ones.
- 📤 Export anywhere – download the dataset as JSON, CSV, or Excel, or pull it via the Apify API.
Why this scraper
- No API key, no login, no quota. Uses YouTube's internal InnerTube endpoints (the same JSON APIs the website calls), reading the live client config from the watch page so the client version is never stale.
- Honest transcripts. Captions are gated by YouTube; this Actor uses the most reliable path available (InnerTube
get_transcriptvia the iOS client, with a timedtext fallback) and is transparent about when a transcript can't be obtained. - Built for reliability. Residential proxy by default, fresh proxy session per video, retries with jitter, and resumable state so a restarted run does not refetch finished videos.
- One Actor, four data types. Every record carries a
typefield (video,comment,transcript,searchResult) so you can split the dataset cleanly. - Pairs with our other scrapers for social/video data (TikTok, Instagram, Reddit, and more) when you need cross-platform coverage.
How it works (and why it's reliable)
-
Seed – fetches the watch page once per video and extracts the live
INNERTUBEconfig plus the initial comments token. -
Metadata – read straight from the player response (no extra request).
-
Transcript – resolves the caption track list (language + manual/auto) from the mobile
playerresponse, then fetches the text in two stages:- Primary: the InnerTube
get_transcript(transcript-panel) endpoint, called with the iOS mobile client. Theparamsis a base64-protobuf built from the video ID + chosen track; this endpoint still returns segment text where the plain caption URL does not. - Fallback: the timedtext caption
baseUrl(fmt=json3). The desktop/web client returns empty caption bodies (exp=xpe/ PoToken gate), so mobile clients are used on purpose, but even mobile caption URLs can be gated on some IPs.
Transcripts are therefore best-effort: when both paths are gated for a given video/IP, no transcript item is emitted (the video and comments are unaffected). A residential proxy materially improves the hit rate.
- Primary: the InnerTube
-
Comments – paginates the InnerTube
nextendpoint, joining the thread structure with theframeworkUpdatesentity payloads to recover the real text/author/likes.
⚠️ Use a residential proxy. YouTube rate-limits datacenter IPs quickly (HTTP 429s and empty responses), especially for comment pagination. The default proxy group is
RESIDENTIALfor this reason — and residential bandwidth is the main cost driver of a run.
Example input
{"videoUrls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],"searchQueries": ["lofi hip hop"],"maxResultsPerSearch": 25,"scrapeVideoMetadata": true,"scrapeTranscript": true,"scrapeComments": true,"maxComments": 100,"includeCommentReplies": false,"commentsSortBy": "top","language": "en","preferManualCaptions": true,"emitSearchResults": false,"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }}
Example output
Each item carries a type field so you can split the dataset by record type. A video that is private, members-only, age-restricted, region-blocked, or not playable is emitted as a type: "skipped" item (with a reason) instead of crashing the run.
Video (type: "video")
{"type": "video","videoId": "dQw4w9WgXcQ","url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ","title": "Rick Astley - Never Gonna Give You Up (Official Music Video)","description": "The official video for \"Never Gonna Give You Up\" by Rick Astley...","channelName": "Rick Astley","channelId": "UCuAXFkgsw1L7xaCfnd5JJOw","channelUrl": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw","viewCount": 1600000000,"likeCount": 17000000,"publishDate": "2009-10-25","uploadDate": "2009-10-25","durationSeconds": 212,"category": "Music","keywords": ["rick astley", "never gonna give you up"],"isLiveContent": false,"isFamilySafe": true,"thumbnails": [{ "url": "https://i.ytimg.com/...", "width": 1280, "height": 720 }],"availableCaptionLanguages": [{ "languageCode": "en", "name": "English", "isAutoGenerated": false }],"commentCountApprox": 2200000,"foundViaQuery": null,"scrapedAt": "2026-06-26T10:00:00.000Z"}
Transcript (type: "transcript")
{"type": "transcript","videoId": "dQw4w9WgXcQ","language": "en","isAutoGenerated": false,"trackName": "English","fullText": "We're no strangers to love ...","segments": [{ "start": 18.5, "dur": 3.2, "text": "We're no strangers to love" }],"scrapedAt": "2026-06-26T10:00:00.000Z"}
Comment (type: "comment")
{"type": "comment","videoId": "dQw4w9WgXcQ","commentId": "UgxKREWxIgDrw8w2e_Z4AaABAg","text": "Still a banger in 2026.","authorName": "@someuser","authorChannelId": "UCabc...","authorChannelUrl": "https://www.youtube.com/channel/UCabc...","authorThumbnail": "https://yt3.ggpht.com/...","likeCount": 1200,"replyCount": 8,"publishedTimeText": "3 weeks ago","isPinned": false,"isHearted": false,"isReply": false,"parentCommentId": null,"scrapedAt": "2026-06-26T10:00:00.000Z"}
Search result (type: "searchResult", only when Also emit raw search results is enabled)
{"type": "searchResult","query": "lofi hip hop","videoId": "jfKfPfyJRdk","url": "https://www.youtube.com/watch?v=jfKfPfyJRdk","title": "lofi hip hop radio","channelName": "Lofi Girl","channelId": "UCSJ4gkVC6NrvII8umztf0Ow","viewCountText": "Streaming","publishedTimeText": null,"lengthText": null,"thumbnails": [{ "url": "https://i.ytimg.com/...", "width": 360, "height": 202 }],"scrapedAt": "2026-06-26T10:00:00.000Z"}
Output fields
Video (type: "video")
| Field | Type | Description |
|---|---|---|
type | string | Always "video". |
videoId | string | 11-character video ID. |
url | string | Canonical watch URL. |
title | string | Video title. |
description | string | Full description text. |
channelName | string | Channel (author) name. |
channelId | string | Channel ID. |
channelUrl | string | Channel URL. |
viewCount | integer | Total views (nullable). |
likeCount | integer | Approximate like count from the watch UI (nullable; YouTube does not expose an exact number). |
publishDate | string | Publish date from microformat (nullable). |
uploadDate | string | Upload date from microformat (nullable). |
durationSeconds | integer | Duration in seconds (nullable). |
category | string | Video category (nullable). |
keywords | array | Tags array (may be empty). |
isLiveContent | boolean | Whether the video is/was live (nullable). |
isFamilySafe | boolean | Family-safe flag (nullable). |
thumbnails | array | Array of { url, width, height }. |
availableCaptionLanguages | array | Array of { languageCode, name, isAutoGenerated }. |
commentCountApprox | integer | Approximate comment count from the header (nullable). |
foundViaQuery | string | The search query that surfaced this video (only present for videos discovered via searchQueries). |
scrapedAt | string | ISO 8601 timestamp. |
Transcript (type: "transcript")
| Field | Type | Description |
|---|---|---|
type | string | Always "transcript". |
videoId | string | Video ID. |
language | string | Caption language code (nullable). |
isAutoGenerated | boolean | true for auto (ASR) captions. |
trackName | string | Human-readable track name (nullable). |
fullText | string | All segments joined into one string. |
segments | array | Array of { start, dur, text } (seconds as floats; start/dur may be null). |
scrapedAt | string | ISO 8601 timestamp. |
Comment (type: "comment")
| Field | Type | Description |
|---|---|---|
type | string | Always "comment". |
videoId | string | Video the comment belongs to. |
commentId | string | Comment ID. |
text | string | Comment body. |
authorName | string | Author handle / display name. |
authorChannelId | string | Author channel ID (nullable). |
authorChannelUrl | string | Author channel URL (nullable). |
authorThumbnail | string | Author avatar URL (nullable). |
likeCount | integer | Like count (defaults to 0 when not shown). |
replyCount | integer | Reply count (defaults to 0). |
publishedTimeText | string | Relative time string (e.g. "3 weeks ago"); YouTube does not expose absolute comment dates. |
isPinned | boolean | Whether the comment is pinned. |
isHearted | boolean | Whether the creator hearted it. |
isReply | boolean | true for replies. |
parentCommentId | string | Parent comment ID (null for top-level). |
scrapedAt | string | ISO 8601 timestamp. |
Search result (type: "searchResult")
| Field | Type | Description |
|---|---|---|
type | string | Always "searchResult". |
query | string | The search query that matched. |
videoId | string | Video ID. |
url | string | Watch URL. |
title | string | Video title. |
channelName | string | Channel name (nullable). |
channelId | string | Channel ID (nullable). |
viewCountText | string | View-count display string from the search card (nullable). |
publishedTimeText | string | Relative publish-time display string (nullable). |
lengthText | string | Duration display string (nullable). |
thumbnails | array | Array of { url, width, height }. |
scrapedAt | string | ISO 8601 timestamp. |
Skipped (type: "skipped")
| Field | Type | Description |
|---|---|---|
type | string | Always "skipped". |
videoId | string | Video ID that could not be scraped. |
url | string | Watch URL. |
reason | string | Why it was skipped (e.g. LOGIN_REQUIRED, AGE_VERIFICATION_REQUIRED, no_video_details, watch_fetch_failed: ...). |
scrapedAt | string | ISO 8601 timestamp. |
Input reference
| Field | Type | Default | Description |
|---|---|---|---|
videoUrls | array | – | YouTube watch URLs (watch?v=, youtu.be/, /shorts/, /embed/, /live/). Normalized to the 11-char ID. |
videoIds | array | – | Raw 11-character video IDs. |
searchQueries | array | – | Keyword search strings (videos only); matches are enriched like direct inputs. |
maxResultsPerSearch | integer | 50 | Max videos per search query (pagination stops once reached). |
scrapeVideoMetadata | boolean | true | Emit type: "video" items. |
scrapeTranscript | boolean | true | Fetch transcript/captions (best-effort). |
scrapeComments | boolean | false | Fetch comments (slowest, most rate-limited path). |
maxComments | integer | 100 | Max top-level comments per video; 0 = unlimited. |
includeCommentReplies | boolean | false | Also expand reply threads. |
commentsSortBy | string | top | "top" or "newest". |
language | string | en | Preferred caption language code; also sets the InnerTube hl locale. |
preferManualCaptions | boolean | true | Prefer human captions over auto-generated. |
emitSearchResults | boolean | false | Also push raw type: "searchResult" items for searches. |
proxyConfiguration | object | RESIDENTIAL | Apify proxy; RESIDENTIAL strongly recommended (and the default). |
Common use cases
- Transcript mining – pull YouTube transcripts for summarization, search, or LLM/RAG pipelines.
- Comment & sentiment analysis – export top or newest comments (and replies) as a YouTube comments API for NLP and analysis.
- Channel & topic research – search a keyword and collect metadata across many videos at once.
- Content monitoring – track view counts, approximate likes, and comment volume over time.
- Competitor & market research – build datasets from competitor channels and export to JSON/CSV/Excel.
FAQ
Do I need a YouTube/Google API key? No. This YouTube scraper uses YouTube's internal InnerTube endpoints — no API key, no OAuth, no quota.
Why are some transcripts missing? Transcripts are best-effort. Not every video has captions, and some only have auto-generated ones. YouTube also gates caption text behind PoToken/IP checks: the Actor first tries the InnerTube get_transcript panel endpoint via the iOS client (which still returns text on mobile clients) and falls back to the timedtext caption URL, but on a gated video/IP combination both can come back empty — in that case the video and comments are still scraped, just without a transcript. A residential proxy improves the YouTube transcript hit rate.
Why are comments slow, and why do they need a proxy? Comments are the slowest, most rate-limited path: the Actor paginates the InnerTube next endpoint one page at a time with jitter to stay under YouTube's limits, so a video with many comments takes proportionally longer. Datacenter IPs get 429s and empty responses quickly, so a residential proxy is strongly recommended when scraping comments at volume. Comments are off by default for this reason.
Why do I need a residential proxy, and what does it cost? Datacenter IPs get rate-limited (429s) and served empty responses, especially during comment pagination, so the default proxy group is RESIDENTIAL. Residential proxy bandwidth is the main cost driver of a run — fetching watch pages, comments, and transcripts all consume residential traffic — so scope your maxComments and maxResultsPerSearch to control spend.
Which languages are supported? Any language YouTube has a caption track for. Set language to a language code (e.g. en, es, de, fr, pt, hi, ja) to pick the caption track and the InnerTube hl locale; if that exact track is missing, the scraper falls back to another track or an auto-generated one. preferManualCaptions controls whether human captions win over ASR.
How are limits and pagination handled? maxResultsPerSearch caps videos per search query, and maxComments caps top-level comments per video (0 = unlimited). Search and comments paginate automatically until the limit is hit or YouTube stops returning a continuation. Runs are resumable: a restarted run skips videos it already finished.
What export formats are available? The dataset can be exported to JSON, CSV, or Excel (also XML, JSONL, RSS, or HTML table) from the Apify Console, or fetched programmatically via the Apify API.
This YouTube scraper pairs well with our other social and video scrapers (TikTok, Instagram, Reddit, and more) when you need cross-platform data.