YouTube Channel Scraper — All Videos, Transcripts, Comments avatar

YouTube Channel Scraper — All Videos, Transcripts, Comments

Pricing

from $0.50 / 1,000 videos

Go to Apify Store
YouTube Channel Scraper — All Videos, Transcripts, Comments

YouTube Channel Scraper — All Videos, Transcripts, Comments

Scrape every video from a YouTube channel, playlist or search — long videos, Shorts and live streams. Transcripts, comments and replies. Exact views and RFC-3339 dates. CSV export. No API key, no quota. Failed, filtered and cached rows are free.

Pricing

from $0.50 / 1,000 videos

Rating

0.0

(0)

Developer

Lorenzo

Lorenzo

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

Paste a YouTube channel. Get every video it has published, as a spreadsheet. Long videos, Shorts and live streams. No API key, no quota.

What you get back

video_typetitleviewspublished_atduration_s
videoGoogle Pixel 11/Pro/Fold Impressions: It Is What It Is31387802026-08-12T07:00:35-07:00673
shortThis ZOOM is Insane!35673932026-07-20T13:39:08-07:0066
stream5 New Phone Updates + Giveaway Update!9406082017-11-29T09:15:12-08:002501

Real rows from a real run. Exact counts, not 3.1M. Full timestamps, not 7 days ago. Both survive sorting.


What a row actually looks like

One video, with transcripts, timed segments and comments all switched on. Real output from a real run — long fields trimmed with , nothing else changed.

{
"_row_type": "video",
"video_id": "o4SSoURPODY",
"video_type": "video",
"title": "Google Pixel 11/Pro/Fold Impressions: It Is What It Is",
"channel": "Marques Brownlee",
"channel_id": "UCBJycsmduvYEL83R_U4JriQ",
"views": "3148445",
"likes": 84617,
"published_at": "2026-08-12T07:00:35-07:00",
"category": "Science & Technology",
"duration_s": "673",
"description": "Every year, a new Pixel, and new hopes and dreams... …",
"keywords": ["Pixel 11", "Pixel 11 Pro", "Pixel 11 Pro Fold", "…"],
"thumbnail": "https://i.ytimg.com/vi_webp/o4SSoURPODY/maxresdefault.webp",
"subscriber_count": "21.1M subscribers",
"is_live": false,
"is_private": false,
"is_crawlable": true,
"transcript": "[music] >> You know, I've been using Pixel phones for a long time. …",
"transcript_segments": [
{ "start_s": 2.619, "duration_s": 2.741, "text": "[music]" }, "…"
],
"transcript_cue_count": 331,
"transcript_language": "en",
"transcript_is_generated": true,
"transcript_status": "OK",
"_source_url": "https://www.youtube.com/watch?v=o4SSoURPODY",
"_cached": false,
"_error_kind": null
}

A comment from the same run — its own row, not a nested field:

{
"_row_type": "comment",
"video_id": "o4SSoURPODY",
"comment_id": "Ugz7VRjqhsV-60jGjwR4AaABAg",
"comment_text": "Adding a physical RGB LED to the back of the phone just to …",
"comment_author": "@Mosesplusofficial",
"comment_author_channel_id": "UCr3n8BfGuDIzhO8ALIxMe4w",
"comment_author_is_verified": false,
"comment_published_text": "8 days ago",
"comment_like_count_text": "13K",
"comment_reply_count_text": "200",
"comment_is_reply": false
}

Three things that make this different

🟢 Failures are typed, never blank

Subtitles off returns NO_CAPTIONS. A broken fetch returns FETCH_FAILED. Different values, so you always know which happened.

🟢 Transcripts and comments in the same run

One actor, one pass. Transcript text, timed segments, comments and replies — alongside the metadata, not in a separate job.

🟢 Failed, filtered and cached rows cost nothing

Charged only when a row has data. A deleted video is free. So is one your filter removed, or one served from cache.


How do I scrape all videos from a YouTube channel?

{ "inputs": ["https://www.youtube.com/@mkbhd"], "maxResultsPerSource": 500 }

@handle, a channel URL, or a /channel/UC… ID all work.

You get all three tabs. Long videos, Shorts and live streams sit in separate tabs that do not overlap — one channel measured 30, 48 and 5. video_type says which is which, and the tabs are interleaved so asking for 30 returns a mix. channelTabs narrows it.

Channel rows also carry subscriber_count and channel_description, free — same request that lists the videos.

What happens when something fails?

You get a typed reason in the row, never an empty cell.

_error_kindmeans
REJECTED_INPUTnot a recognised channel, playlist, video or search
RESOLVE_FAILEDthe listing could not be read
NO_VIDEOSit was read and contained none
FETCH_FAILEDthe video request failed
NO_DATAit loaded but carried no video details

Blocked requests retry automatically on a fresh IP. In a 36-video run that turned 3 blocks into 0 failures.

How do I get YouTube transcripts in bulk?

{ "inputs": ["https://www.youtube.com/@veritasium"],
"includeTranscript": true, "transcriptLanguages": ["en", "es"] }

Each row gains the transcript as text, its language, whether it was auto-generated, and how many timed cues it has.

Timed cues are a separate switch: includeTranscriptSegments. Turn it on and each row also carries transcript_segments — one entry per cue with start_s, duration_s and text — for citing a moment in the video. It costs nothing extra, because the cues arrive in the same request as the text. It is off by default because every export format flattens them: a 1,000-cue video becomes transcript_segments/0/text/999/text, and a spreadsheet of a few videos turns into thousands of columns. transcript_cue_count ships either way.

transcriptLanguages is a preference order and human-written subtitles beat auto-generated ones. Subtitles off returns NO_CAPTIONS, free.

includeLikes adds a like count from one small extra request, not charged separately.

How do I export a YouTube playlist to CSV?

{ "inputs": ["https://www.youtube.com/playlist?list=PLbpi6ZahtOH6Blw3RGYpWkSByi_T7Rygb"],
"maxResultsPerSource": 100 }

Then hit Export — CSV, JSON, Excel, or an API endpoint. Playlists return 100 videos per request, so multiples of 100 go furthest.

How do I get YouTube comments with replies?

{ "inputs": ["https://www.youtube.com/@mkbhd"], "includeComments": true,
"maxCommentsPerVideo": 100, "maxRepliesPerComment": 5 }

Comments arrive as their own rows, not extra columns_row_type is video or comment. Folding 100 comments into a video row would export as 600 unusable columns.

Replies are rows too (comment_is_reply: true), capped because each comment with replies costs an extra request.

How do I search YouTube and export the results?

{ "inputs": ["drone review", "sourdough starter"] }

One quirk: a bare 11-character word is read as a video ID, because that is exactly how long a YouTube ID is — helicopters is eleven characters. Write search:helicopters to force a search.

Can I filter, or work in another language?

inputeffect
publishedAfter / publishedBeforeYYYY-MM-DD, inclusive
durationMinSecondsset 61 to exclude Shorts
durationMaxSecondsupper bound in seconds
languageen, de, ja, es — text YouTube generates
regionUS, DE, GB — how results are ranked

Filters run after each video is read, so they are exact — and filtered-out videos are never charged.

subscriber_count follows the language as YouTube wrote it: 21.1M subscribers, 21,1 Mio. Abonnenten, チャンネル登録者数 2110万人. Titles, descriptions and category are unaffected.

What people use this for

📈 Track a competitor's channel every week

  1. Run with the competitor's channel and maxResultsPerSource: 50, maxAgeHours: 0 so view counts are fresh.
  2. Schedule it weekly in the Apify Console.
  3. Join runs on video_id and diff views to get per-video growth, and published_at to get posting cadence.

Set maxAgeHours: 0 here — the cache is a time window, and stale view counts would flatten exactly the trend you are measuring.

🤖 Build an LLM corpus from a channel's transcripts

  1. Run with the channel, includeTranscript: true, maxResultsPerSource: 500.
  2. Export as JSON and use transcript for plain text. Add includeTranscriptSegments: true when you need timestamps to cite back to a moment in the video — it is free, but it makes the rows much wider.
  3. Filter on transcript_status == "OK"NO_CAPTIONS means that video never had subtitles, and it cost you nothing.

transcript_is_generated tells you whether a human wrote it. Auto-generated captions carry more errors, which matters if you are grounding an answer on them.

💬 Find what an audience actually asks about

  1. Run with the channel, includeComments: true, maxCommentsPerVideo: 100, and maxRepliesPerComment: 5 to catch the threads under the popular ones.
  2. Filter rows to _row_type == "comment".
  3. Group by video_id, sort by comment_like_count_text, read the top of each.

Useful for support-content planning, sponsorship research, and finding the question a whole audience keeps asking that nobody has answered yet.

What fields do I get?

fieldexample
video_id · video_typedQw4w9WgXcQ · video, short, stream
title · channel · channel_idRick Astley - Never Gonna Give You Up · Rick Astley · UCuAXFkgsw1L7xaCfnd5JJOw
views · likes1806183998 · 136841
published_at · duration_s2009-10-24T23:57:33-07:00 · 213
category · keywords · thumbnail · descriptionMusic · array · highest-res URL · full text
subscriber_count · channel_descriptionfrom channel inputs
is_live · is_private · is_crawlablebooleans
every row_row_type _input _source_url _site _cached _cached_at _error_kind _error
with transcriptstranscript transcript_cue_count transcript_language transcript_is_generated transcript_status
with includeTranscriptSegmentstranscript_segments — one entry per cue, start_s duration_s text
comment rowscomment_id comment_text comment_author comment_author_channel_id comment_author_is_verified comment_author_is_creator comment_published_text comment_like_count_text comment_reply_count_text comment_is_reply

Three comment fields end in _text because YouTube publishes them rounded and relative — 13K, 7 days ago — and they pass through as given. 13K is not expanded to 13000: it could be 13,499, and inventing digits hands you a wrong number that looks precise.

Not included: no sort order, no video or audio downloads, no total comment count per video.

Does it re-fetch videos I already pulled?

Any video fetched in the last 24 hours comes back from stored state — and is not charged. Marked _cached: true with _cached_at.

This is a time window, not change detection. It checks how long ago a video was read, not whether it changed — so set maxAgeHours to 0 when sorting on views or measuring growth. The window is per actor: yesterday's run counts for today's.

Cached rows still follow this run's settings. Turn transcripts on and a cached video gets its transcript fetched and charged; turn them off and the row carries no transcript fields at all, whatever an earlier run stored. The cache never adds a field you did not ask for, and never withholds one you did.

What does it cost?

you getyou pay
a video row$0.50 / 1,000
a transcript$10.00 / 1,000
a comment or reply$0.20 / 1,000
a failed, filtered or cached rownothing

Transcripts and comments are billed separately — each is a separate request to YouTube, not an extra field.

Send everything in one run, not one run each: fixed per-run cost is ~86% of a single-video run and ~6% at a hundred. Requests run 8 at a time, one per second each; concurrency adjusts that between 1 and 16.

Will it quietly break?

Every run emits a schema fingerprint — which fields resolved, which came back empty, the row count — compared against a stored baseline. A field that starts returning empty is caught as drift, not discovered months later.

Scrapers rarely break loudly. They thin out.

Terms

You are responsible for your use of the data you collect, including under YouTube's Terms of Service and any applicable data protection law.


More from aquixlabs

Other actors from this account will be listed here.