YouTube Video Scraper & YouTube Shorts Scraper: Search, Channel avatar

YouTube Video Scraper & YouTube Shorts Scraper: Search, Channel

Pricing

from $2.00 / 1,000 videos

Go to Apify Store
YouTube Video Scraper & YouTube Shorts Scraper: Search, Channel

YouTube Video Scraper & YouTube Shorts Scraper: Search, Channel

YouTube scraper and YouTube Data API alternative for video details and metadata: keyword search with YouTube's filters, channel videos, Shorts and streams, playlists, hashtags, video URLs. Each video's exact views, likes, comment count, publish date, description, tags, chapters, channel. No login.

Pricing

from $2.00 / 1,000 videos

Rating

0.0

(0)

Developer

Hyperbach

Hyperbach

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

3 days ago

Last modified

Share

YouTube videos and Shorts as data, logged out: keywords (with YouTube's own search filters), channels (the Videos, Shorts and Live tabs), playlists, hashtags and video or Short URLs in; one row per video out with its public record — the creator's original title, the exact views and likes, the exact comment count ("3,285", where the page rounds it to "3.2K"), the publish date to the second, the duration, the full description with every link unwrapped, tags, hashtags, chapters, the category, the Shorts flag from YouTube's own field, live, scheduled and members-only status, age restriction, the countries the video is blocked in, and the channel with its id, @handle, subscribers, verified badge and avatar. Each row says how it was found: the input, its rank in YouTube's order, the page, the search slice, the country the search was read from and the read time. Every search, channel tab, playlist and hashtag gets a free summary row with what was read and a verdict: COMPLETE, TRUNCATED (YouTube's own limit), CAPPED (your limit) or PARTIAL. Past the ≈ 500 results YouTube shows for one search, deepSearch reads its duration slices, every video once. On request: ads shown and paid promotion, and each channel's About (country, joined date, exact video and view counts). A value that names nothing, a video or channel that does not exist: free rows that say why. No login, no YouTube account, no cookie, no API key, proxies handled for you.

Why this YouTube video scraper

  • Every number exact, or null with the reason. Views and likes come from the video's record (exact: 8,449 views and 138 likes, where the page shows "8.4K"); the comment count from the comments panel, or from the comments header where the panel rounds it ("3.2K" on the page, 3,285 here; 28,899 for a "28K"); comments_turned_off tells 0 comments from comments turned off; the publish date is the record's own time, never computed from "3 years ago". The lane's most frequent complaint is a null, rounded or stale count (29 buyer issues on one theme): here a count is null only where YouTube shows none (a live chat, a members-only video), and the row's note says so.
  • Depth with a proof. A YouTube search ends at about 500 results (230–350 distinct videos after YouTube's own repeats): the summary row says so (TRUNCATED, with YouTube's estimate) instead of looking complete. deepSearch then walks YouTube's three duration bands to their own ends, every video once: 348 distinct videos for minecraft in one walk, 991 with the slices on our test. A channel tab and a playlist are read to their own end (a 253-video playlist's 249 listed videos: its header counts hidden ones too, and the row says that).
  • YouTube's own filters, only the ones that work. Upload date (hour, today, week, month, year), duration, type (videos, Shorts only, or everything), 11 features (live, 4K, HD, subtitles, Creative Commons, 360°, VR180, 3D, HDR, location, purchased) and the view-count sort go into YouTube's own request. YouTube ignores its upload-date and rating sorts logged out (measured on every form): they are not offered, and a search URL that asks for them says so on its summary row. A channel reads newest first or in YouTube's own Popular and Oldest orders.
  • The country you ask for. YouTube shows each country a different search: a reader in GB, DE, IN, JP or BR shares 0–5 of the first 20 news results with a US reader. Each search is read from a connection in the run's country (56 countries), and every row carries the country it was read from.
  • Shorts as Shorts. The Shorts flag is YouTube's own field on the record (8 of 8 Shorts true, 12 of 12 videos false on our test), never a 60-second rule (a 77-second Short is a Short). A channel's Shorts tab, the Shorts-only search and the Shorts shelf of a mixed search each say so on the row.
  • Honest rows and billing. One row and one charge per video a run, whatever inputs and pages repeat it (YouTube's own search repeats 13–20 % of its results inside one walk). A video or channel that does not exist, a private video, a value that is no YouTube URL: a free row with the reason, and the run goes on. Every list gets a free summary row with the counts and the verdict, so a short dataset is never silent.
  • Fast, plain HTTP. YouTube's own JSON answers, masked to the fields a row needs: no browser, no cookie, no API key. On our test 48 videos with the record, the comment count, the ads call and the channel's About took 15 seconds, 450 card rows of a search 30 seconds.

Who it's for

  • Marketers and agencies — every video of a keyword in a country, with exact views, likes and comment counts, the publish date and the channel's subscribers, to rank creators and topics; a channel's newest uploads on a schedule with onlyNew.
  • Researchers and analysts — a search past YouTube's first 500 results by duration slices, every video once, with a verdict per search; the description, tags, hashtags, chapters and links for content analysis; the countries a video is blocked in.
  • Creators and channel managers — a competitor's Videos, Shorts and Live tabs in their own or YouTube's Popular order, with views per hour and the engagement rate on every row; Shorts told from videos by YouTube's own flag.
  • Developers and data teams — typed rows keyed by video_id with the input, rank and slice on each, a free summary row per list, video URLs in every form (watch, youtu.be, shorts, live, embed), a run summary to check a pipeline against.

Quick start

The first 10 videos of a keyword with the full record (the leader's default job)

{
"searchQueries": [
"Crawlee"
],
"maxResults": 10
}

A channel's 30 newest videos and 10 newest Shorts

{
"startUrls": [
"https://www.youtube.com/@Apify"
],
"maxResults": 30,
"maxResultsShorts": 10
}

This week's Shorts for a keyword, read from Germany

{
"searchQueries": [
"minecraft"
],
"searchType": "shorts",
"uploadDate": "week",
"country": "DE",
"maxResults": 50
}

Video and Short URLs, with ads shown and paid promotion

{
"startUrls": [
"https://www.youtube.com/watch?v=yTRHomGg9uQ",
"https://youtube.com/shorts/E-PACJ9ekkY",
"4gEoh3sk2AE"
],
"includeMonetization": true
}

1,000 videos of a broad keyword, past one search walk, cards only

{
"searchQueries": [
"minecraft"
],
"maxResults": 1000,
"includeDetails": false
}

Output

One row per video (and one free summary row per search, channel tab, playlist or hashtag):

fieldmeaning
row_typevideo (one video: charged as a video with its record, as a card row without) or summary (the free summary of a search, a channel tab, a playlist or a hashtag, with the walk's verdict).
statusA video row: ok (charged) or the free reason it holds no video — not_found (YouTube has no such video; the player and the watch page agree), private, unavailable (removed or blocked; the source's own reason in note), failed (refused past the retries), invalid (the value names no video, channel, playlist, hashtag or search), not_read (maxItems, the charge limit or the time limit came first), duplicate (another input of the run delivered it), skipped (a video input in skipVideoIds or delivered by an earlier onlyNew run). A summary row: ok, no_results, not_found, failed, not_read, duplicate (the same channel as another input, read once).
noteWhy, in words: the reason of a free row, or what a charged row lacks and why (a live stream shows a chat, not a comment count; the watch page was not read).
video_idYouTube's 11-character video id; the de-duplication key (one row and one charge per video a run).
urlThe video on YouTube: https://www.youtube.com/watch?v=
titleThe creator's original title (the same under every language).
title_localizedThe title in the asked language when the creator translated it and it differs from the original; null otherwise.
video_typevideo, short or stream (a live, past or scheduled stream). From the record; on a card row from the list it came from (null on a video-search card, which cannot tell a Short).
is_shortYouTube's own Shorts flag (isShortsEligible), never a duration rule (a 77-second video can be a Short); on a card row the list it came from.
duration_secondsLength in seconds; null for a live or scheduled stream.
durationLength as YouTube writes it: 5:50, 1:14:56.
view_countExact views (the record's viewCount; a live stream's total so far); on a card row the card's exact count when it shows one ("8,449 views"), null for a rounded one.
view_count_textThe view count as the watch page or the card writes it ("8,449 views", "445 watching now", "1.2K views").
likesExact likes from the record (the like button rounds them); null when the creator hides them.
comment_countThe exact comment count: the comments panel's number, or the comments header ("3,285 Comments") where the panel rounds it ("3.2K") or shows none; null for comments turned off, a live chat or a members-only video, with the reason in note.
comments_turned_offtrue when the watch page says "Comments are turned off"; false when a count was read; null when neither is shown.
published_atThe publish date and time, UTC (the record's ISO date with its offset, converted).
uploaded_atThe upload date and time, UTC (the record's own; equal to published_at for most videos).
published_textThe date as the list shows it ("3 years ago", "Streamed 2 weeks ago") or the watch page's date text; never computed into a date.
descriptionThe full description (the record's own text, longer than the watch page's).
description_snippetThe search card's description snippet, where the video came from a search.
tagsThe creator's keywords (tags) on the record.
hashtagsThe hashtags above the title and the #tags written in the description, in order, each once.
chaptersThe chapters: title, start in seconds and as written ("1:06").
description_linksEvery link in the description with its real URL (YouTube's redirect unwrapped) and its text.
categoryYouTube's category ("Science & Technology").
locationThe place the creator set above the title ("UNITED KINGDOM"), when one is shown.
licenseThe license row of the watch page ("Creative Commons Attribution license (reuse allowed)"), when the video states one.
thumbnail_urlThe largest thumbnail the record lists (up to 1920 × 1080).
thumbnailsEvery thumbnail size the record lists: url, width, height.
badgesThe card's and the watch page's badges ("4K", "CC", "New", "LIVE", "Members only").
channel_idThe channel's UC… id (a collaboration's primary channel).
channel_nameThe channel's name.
channel_urlThe channel on YouTube (its @handle URL, else /channel/
channel_handleThe channel's @handle.
channel_subscribersThe subscriber count as YouTube rounds it ("34.6K subscribers" = 34,600; YouTube shows no exact count logged out); read in English only.
channel_subscribers_textThe subscriber count as YouTube writes it.
channel_verifiedThe channel's verified badge (an artist's too).
channel_avatar_urlThe channel's avatar (null on a collaboration, whose dialog names several channels).
collaboratorsA collaboration's channels: channel id, name, @handle, subscribers text.
is_livetrue while the video is a live stream on air.
live_statusnone, live (on air), upcoming (scheduled) or was_live (a past stream).
scheduled_start_atA scheduled stream's or premiere's start, UTC.
is_members_onlyA video for the channel's paying members (served with its record, flagged).
age_restrictedYouTube asks viewers to confirm their age (the record is served, flagged).
is_family_safeYouTube's family-safe flag.
is_unlistedYouTube's unlisted flag.
unavailable_country_codesThe countries the video is blocked in: YouTube's 249 country codes minus the record's available list (empty for most videos).
ads_shownincludeMonetization: YouTube served ad slots to a logged-out viewer of this video (a signal, not the channel's monetization status); null when not asked or not playable logged out.
has_paid_promotionincludeMonetization: the creator declared a paid promotion (YouTube's "Includes paid promotion" label); null when not asked.
views_per_hourThe exact views over the hours since publication, at read_at (derived).
engagement_rate(likes + comments) / views (derived; null without views or likes).
channel_countryincludeChannelDetails: the channel's country (ISO code) from its About.
channel_joined_dateincludeChannelDetails: the channel's joined date.
channel_video_countincludeChannelDetails: the channel's exact video count.
channel_view_countincludeChannelDetails: the channel's exact total views.
details_readtrue: the record and watch page were read (charged as a video); false: the list's card alone (charged as a card row).
inputThe input as you gave it (the keywords, URL, id, @handle or #tag).
input_typesearch, channel_videos, channel_shorts, channel_streams, playlist, hashtag, video, channel (a channel that could not be read), invalid.
rankThe video's place in its list, in YouTube's order (the first appearance; repeats are dropped).
pageThe list page the video was on (inside its slice for a search).
sliceA search's walk the video came from: main, or a duration slice of deepSearch (duration:under4, duration:4to20, duration:over20).
source_urlThe YouTube page the row was read from (the search with its filters, the channel tab, the playlist).
countryThe country the search was read from (the run's country).
languageThe interface language asked (hl).
read_atWhen the run read this row (UTC).
verdictA summary row: COMPLETE (the list's own end was reached), TRUNCATED (YouTube's own limit: a search ends at about 500 results, a hashtag shows one page), CAPPED (your maxResults or maxItems), PARTIAL (a refusal or the time limit stopped it).
verdict_detailThe verdict's reason in words, with the counts.
videos_deliveredA summary row: the videos delivered for this list.
videos_foundA summary row: the distinct videos the list showed (delivered, skipped or dropped).
pagesA summary row: the list pages read.
estimated_resultsA search's summary row: YouTube's own estimatedResults on page 1 (an estimate, not what a walk can reach).
list_countA playlist's summary row: the count in its header (it counts hidden videos too).
list_count_textThe list header's count as written ("253 videos", "298 videos", "47M videos • 5.6M channels").
list_titleA playlist's or hashtag's title.
slicesA search's summary row: each walk (main, the duration slices) with its pages, cards and how it ended.
duplicates_skippedA summary row: repeats inside the list and videos another input of the run took (each delivered once).
known_skippedA summary row: videos an earlier run of the onlyNew memory delivered (skipped free).
filtered_outA summary row: videos dropped by oldestPostDate or skipVideoIds (free).
failed_detailsA summary row: listed videos whose record could not be read (free rows).

Example record:

{
"row_type": "video",
"status": "ok",
"note": null,
"video_id": "yTRHomGg9uQ",
"url": "https://www.youtube.com/watch?v=yTRHomGg9uQ",
"title": "Building an Amazon Scraper in Node.js with Crawlee",
"title_localized": null,
"video_type": "video",
"is_short": false,
"duration_seconds": 350,
"duration": "5:50",
"view_count": 8449,
"view_count_text": "8,449 views",
"likes": 138,
"comment_count": 18,
"comments_turned_off": false,
"published_at": "2022-09-08T13:37:02Z",
"uploaded_at": "2022-09-08T13:37:02Z",
"published_text": "Sep 8, 2022",
"description": "Visit https://crawlee.dev/ to get started.\n\nIn this video, you will get acquainted with Crawlee while building an Amazon scraper using CheerioCrawler.\n\n🧑‍💻Follow along with the wr …(truncated for display)",
"description_snippet": null,
"tags": [
"web scraping platform",
"web automation",
"scrapers",
"Apify",
"web crawling",
"web scraping",
"data extraction",
"best web scraping tool",
"API",
"how to extract data from any website",
"web scraping tutorial",
"web scrape",
"data collection tool",
"RPA",
"web integration",
"how to turn website into API",
"JSON"
],
"hashtags": [],
"chapters": [
{
"title": "Introduction",
"start_seconds": 0,
"start": "0:00"
},
{
"title": "Investigation",
"start_seconds": 17,
"start": "0:17"
},
{
"title": "Project template configuration",
"start_seconds": 66,
"start": "1:06"
},
{
"title": "Product Result List Handler",
"start_seconds": 148,
"start": "2:28"
},
{
"title": "Product Page Handler",
"start_seconds": 228,
"start": "3:48"
},
{
"title": "Offers Page Handler",
"start_seconds": 265,
"start": "4:25"
},
{
"title": "Dataset",
"start_seconds": 297,
"start": "4:57"
},
{
"title": "Tell us what you want to see next!",
"start_seconds": 335,
"start": "5:35"
}
],
"description_links": [
{
"url": "https://crawlee.dev/",
"text": "https://crawlee.dev/"
},
{
"url": "https://developers.apify.com/academy/web-scraping-for-beginners/challenge",
"text": "https://developers.apify.com/academy/..."
},
{
"url": "https://github.com/apify/crawlee",
"text": "https://github.com/apify/crawlee"
},
{
"url": "https://discord.gg/qkMS6pU4cF",
"text": "/ discord"
}
],
"category": "Science & Technology",
"location": null,
"license": null,
"thumbnail_url": "https://i.ytimg.com/vi_webp/yTRHomGg9uQ/maxresdefault.webp",
"thumbnails": [
{
"url": "https://i.ytimg.com/vi/yTRHomGg9uQ/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCYcg1KtoF80C9oR2geNfpe5n46PA",
"width": 168,
"height": 94
},
{
"url": "https://i.ytimg.com/vi/yTRHomGg9uQ/hqdefault.jpg?sqp=-oaymwEmCMQBEG5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLAWWoLosjZptdtBKPk1IBc-HqDJCQ",
"width": 196,
"height": 110
},
{
"url": "https://i.ytimg.com/vi/yTRHomGg9uQ/hqdefault.jpg?sqp=-oaymwEnCPYBEIoBSFryq4qpAxkIARUAAIhCGAHYAQHiAQoIGBACGAY4AUAB&rs=AOn4CLCAu3-acPqXn6b6pOrai3WYQb1zGg",
"width": 246,
"height": 138
},
{
"url": "https://i.ytimg.com/vi/yTRHomGg9uQ/hqdefault.jpg?sqp=-oaymwEnCNACELwBSFryq4qpAxkIARUAAIhCGAHYAQHiAQoIGBACGAY4AUAB&rs=AOn4CLCS2hH24BiwWIH1BlYjyL3hbgg-8A",
"width": 336,
"height": 188
},
{
"url": "https://i.ytimg.com/vi_webp/yTRHomGg9uQ/maxresdefault.webp",
"width": 1920,
"height": 1080
}
],
"badges": [],
"channel_id": "UCTgwcoeGGKmZ3zzCXN2qo_A",
"channel_name": "Apify",
"channel_url": "https://www.youtube.com/@Apify",
"channel_handle": "@Apify",
"channel_subscribers": 34600,
"channel_subscribers_text": "34.6K subscribers",
"channel_verified": false,
"channel_avatar_url": "https://yt3.ggpht.com/m4tS7WA15DeUqAi90tn0NnshLvW7liMNpAGAN5I78UNQKTPDcLHw_0dc1GXeW75_ccDiuqUEmQ=s176-c-k-c0x00ffffff-no-rj",
"collaborators": null,
"is_live": false,
"live_status": "none",
"scheduled_start_at": null,
"is_members_only": false,
"age_restricted": false,
"is_family_safe": true,
"is_unlisted": false,
"unavailable_country_codes": [],
"ads_shown": false,
"has_paid_promotion": false,
"views_per_hour": 0.23,
"engagement_rate": 0.01846,
"channel_country": "CZ",
"channel_joined_date": "2017-01-04",
"channel_video_count": 298,
"channel_view_count": 2445222,
"details_read": true,
"input": "https://www.youtube.com/watch?v=yTRHomGg9uQ",
"input_type": "video",
"rank": null,
"page": null,
"slice": null,
"source_url": "https://www.youtube.com/watch?v=yTRHomGg9uQ",
"country": "US",
"language": "en",
"read_at": "2026-10-20T07:56:00Z",
"verdict": null,
"verdict_detail": null,
"videos_delivered": null,
"videos_found": null,
"pages": null,
"estimated_results": null,
"list_count": null,
"list_count_text": null,
"list_title": null,
"slices": null,
"duplicates_skipped": null,
"known_skipped": null,
"filtered_out": null,
"failed_details": null
}

Pricing

Pay for the videos a run delivers, plus one run start; no platform usage is billed to you. The row prices fall with your Apify plan.

eventunitFreeStarterScaleBusiness
Video (video)per 1,000$4.00$3.00$2.60$2.00
Card row (lists only) (card)per 1,000$1.30$1.00$0.80$0.50
Run start (start)per run$0.00161$0.00161$0.00161$0.00161

A video with its record read (exact views, likes and comment count, the publish date, the full description, tags, chapters and the channel) is $4.00 per 1,000 on the Free plan ($0.004 a video) and $3.00 on Starter. A Short is a video row at the same price. A card row (a list's card alone, includeDetails off) is $1.30 per 1,000 on Free. Each run that delivers a charged row also pays the run start, $0.00161 once; a run that delivers no charged row pays nothing at all. So the 10 videos of one search cost $0.04161 on the Free plan and $0.03161 on Starter; 1,000 videos $4.00161 and $3.00161; several searches and channels in one run share one start. YouTube's filters, oldestPostDate, country, language and the ads-shown flag (includeMonetization) cost nothing extra: a video a cut-off drops is dropped before it is charged. Free: every summary row (the list's counts and verdict; duplicate when a channel is given twice), a video YouTube does not have (not_found), a private or removed video (private, unavailable), a value that names nothing (invalid), a video another input of the run delivered (duplicate), a video in skipVideoIds or delivered by an earlier onlyNew run (skipped), an input left unread at the cap or the time limit (not_read), a record that could not be read (failed). A video is charged once a run whatever inputs overlap; a run that stops early (a timeout, an abort) is charged for the rows it delivered. maxItems counts the charged rows. Before the first request the log names the price of each row at your plan, the run start and the most the run can cost: maxResults (default 50) caps each search, playlist, hashtag and channel Videos tab, maxResultsShorts and maxResultStreams a channel's Shorts and Live tabs, so the bound is these caps × the inputs. A search often ends earlier, at YouTube's own end: one walk ends at about 500 results, and deepSearch (on by default) walks three duration slices more, so one ordinary keyword gave 1,409 videos on our test with maxResults 10,000; set maxResults, maxItems or a charge limit for a lower maximum. Enterprise plans have their own rates — the Actor's Pricing tab shows the price for your plan.

Usage patterns

  • What to give — searchQueries: keywords, one search each, or YouTube search URLs (their own filters apply). startUrls: anything else on YouTube — a video or Short (watch, youtu.be, shorts, live, embed links, or an 11-character id), a channel (URL, @handle or UC… id), a playlist (URL or PL… id), a hashtag (#tag or /hashtag/ URL), a search URL. Lists of both in one run; a value repeated is read once; a value that names nothing is a free invalid row.
  • How many videos — maxResults caps each search, playlist, hashtag and channel Videos tab (50 by default); maxResultsShorts and maxResultStreams read a channel's Shorts and Live tabs (0 by default: not read); maxItems caps the charged rows of the whole run. A list is read in YouTube's order to the cap or to its own end; its summary row says which (CAPPED, COMPLETE) or that YouTube's own limit came first (TRUNCATED). With deepSearch (on by default) a search that ends before the cap goes on into the three duration slices.
  • What a row is — row_type = video: one video in its list's order (rank), with the record when includeDetails is on (the default; details_read true, charged as a video) or the card alone when it is off (id, title, duration, the view text, the relative date, the channel; charged as a card row). row_type = summary: the free summary of one list — videos_delivered, videos_found, pages, YouTube's estimated_results for a search, the header's list_count for a playlist, slices for a deep search, the repeats and skips, the verdict with its verdict_detail, and the status.
  • Recent videos — YouTube's search has no working sort by upload date logged out (it answers the relevance order to every form of the date sort): use uploadDate (hour, today, week, month, year), and every detailed row carries its exact published_at. On a channel, newest first is the tab's own order, and oldestPostDate stops the walk at the first older video.
  • Scheduled runs: only the new videos — Save a task with onlyNew (and a stateStoreName to name the memory) and schedule it: a video delivered by an earlier run of that memory is skipped free. A remembered video still takes its place in maxResults, so a channel's task reads its newest N and delivers only the ones not seen before, never older videos to fill the count. skipVideoIds skips a list of your own.

Input configuration

fieldtypedefaultwhat it does
searchQueriesarray[]Keywords to search on YouTube, one search each (at most 200), read in YouTube's own order with the filters below. A YouTube search URL (https://www.youtube.com/results?search_query=…&sp=…) runs with its own filters. A channel, playlist, hashtag or video URL pasted here is read as what it is.
startUrlsarray[]Videos and Shorts (watch, youtu.be, shorts, live and embed links, or an 11-character id), channels (a channel URL, an @handle or a UC… id: its Videos tab, and its Shorts and Live tabs when maxResultsShorts or maxResultStreams is set; a URL that names a tab, such as /@name/shorts or /@name/streams, reads that tab alone, up to its own field or else maxResults; one channel given in several forms is read once), playlists (a playlist URL or a PL… id), hashtags (a #tag or a /hashtag/ URL) and search URLs (at most 2,000). A value that names none of these is a free invalid row; the run goes on.
maxResultsinteger50How many videos each search, playlist and hashtag delivers at most, in YouTube's order, and how many of a channel's Videos tab. 0 reads no search, playlist or hashtag and no channel Videos tab (set maxResultsShorts or maxResultStreams to read a channel's other tabs only).
maxResultsShortsinteger0How many of each channel's Shorts tab to read, newest first (or in channelSortBy's order). 0 = the Shorts tab is not read. For a keyword search of Shorts set searchType to shorts.
maxResultStreamsinteger0How many of each channel's Live tab (past streams, live now, scheduled) to read. 0 = the Live tab is not read.
searchTypevideo / shorts / all"video"Videos (YouTube's video filter), Shorts only (YouTube's Shorts filter: every row is a Short), or YouTube's unfiltered results (videos plus the Shorts shelf, each row typed). On a broad keyword most of these rows can be Shorts from the shelf (17 of 20 on our test), each charged as a video row.
uploadDateany / hour / today / week / month / year"any"YouTube's upload-date filter. YouTube's search has no working sort by upload date (it answers the relevance order), so this is the way to read recent videos; every detailed row carries its exact published_at. YouTube's own windows are a little wider than their names: 'This week' reached 7.4 days back and 'This month' 30.6 days on our test. For an exact cut-off day, set oldestPostDate.
durationany / under4 / 4to20 / over20"any"YouTube's duration filter.
featuresarray[]YouTube's feature filters, each applied by YouTube (several narrow the search together).
sortByrelevance / views"relevance"Relevance (YouTube's default) or view count. YouTube ignores its upload-date and rating sorts logged out (measured on every form), so they are not offered; use uploadDate for recent videos.
deepSearchbooleantrueA YouTube search ends after about 500 results (230–350 distinct videos). When maxResults asks for more, the search goes on into YouTube's three duration bands (under 4, 4–20, over 20 minutes), each walked to its own end, every video once (348 → 991 distinct videos for 'minecraft' on our test). A search with its own duration filter is not sliced. Off: one walk.
countryUS / GB / CA / AU / NZ / IE / IN / PK / BD / PH / ID / MY / SG / TH / VN / JP / KR / TW / HK / DE / AT / CH / FR / BE / NL / ES / PT / IT / PL / CZ / RO / HU / GR / SE / NO / DK / FI / TR / UA / IL / AE / SA / EG / MA / NG / KE / ZA / BR / MX / AR / CO / CL / PE / LK / KZ / NP"US"YouTube shows a different search to each country (a GB, DE, IN, JP or BR reader shares 0–5 of the first 20 'news' results with a US reader): each search is read from a connection in this country, and every row says the country it was read from. Channels, playlists and video records are the same everywhere.
languagestring"en"YouTube's interface language (hl), such as en, de, pt-BR: the search's texts and the watch page's title (title_localized, when the creator translated it) follow it. The title is always the creator's original. Counts are read as numbers in English; in another language the subscriber text is kept as YouTube writes it.
channelSortBynewest / popular / oldest"newest"The order a channel's Videos, Shorts and Live tabs are read in: newest first (the tab's own order), or YouTube's own Popular and Oldest orders.
oldestPostDatestringYYYY-MM-DD. A video published before this day is dropped free; a channel tab read newest first stops at its first older video. The card's relative date ("3 weeks ago") decides where it is sure, the record's exact date otherwise; with includeDetails off, a card with no sure date (every Shorts card) is dated by one record call before it is delivered, so no older card is charged.
includeDetailsbooleantrueOn (recommended): every listed video's record and watch page — exact views and likes, the exact comment count, the ISO publish date, the full description with its links, tags, hashtags, chapters, category, the channel's subscribers, live and members-only status, age restriction, the Shorts flag. Off: the list's cards alone (id, title, duration, the view text, the relative date, the channel), faster and priced as a card row. A video URL is always read with its record.
includeMonetizationbooleanfalseOne more call per video: ads_shown (YouTube served ad slots to a logged-out viewer of this video) and has_paid_promotion (the creator declared a paid promotion). A signal, not the channel's monetization status; null for a video that is not playable logged out (members-only, age-restricted, scheduled).
includeChannelDetailsbooleanfalseEach channel's About once per run: country, joined date, exact video and view counts, on every row of its videos.
onlyNewbooleanfalseRemember every video delivered under this memory (stateStoreName, or a name derived from the inputs) and skip it free next time (a list's video, or a video input as a free skipped row): a scheduled run of a channel delivers only its new uploads. A remembered video still takes its place in maxResults, so a run reads the newest N and never walks past them to older videos.
stateStoreNamestring""The name of the onlyNew memory, so several tasks can share one or keep theirs apart. Empty: a name derived from the inputs.
skipVideoIdsarray[]Video ids or URLs you already have (at most 10,000): a listed video among them is skipped free and does not count against maxResults; a video input among them is a free skipped row.
maxItemsinteger0The most charged rows the run delivers over every input (summary rows and free rows do not count). 0 = no limit beyond maxResults. The log names the most the run can cost before the first request.
maxConcurrencyinteger2How many residential connections read video records at once (4 requests each). 2 by default. More connections spread YouTube's per-connection budget over more exits for a run of thousands of videos; they do not raise the rate (1, 2 and 4 gave 441–522 rows a minute on our test: the watch pages share the list connection).
proxyUrlstringEmpty (recommended): YouTube is read through the Actor's own connections at no extra cost to you. Your own proxy URL (scheme://user:pass@host:port) carries every call instead; a video record needs a residential IP (YouTube refuses it to datacenter IPs).

Errors

A run does not fail because of your input. When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends Succeeded, charges nothing, and says what to change:

  • the status message starts with INPUT REJECTED;
  • the dataset holds one row, {"error": true, "code": "…", "message": "…"}, and no results;
  • the key-value store record ERROR holds the same object.

From code, check error on the first row before reading results.

A search, channel tab, playlist or hashtag with no video is not an error: it gets one free summary row with status no_results (or not_found for a channel or playlist that does not exist), and the run goes on with the others. A run that ends Failed is a fault on our side, never your input; it charges nothing, and we are alerted.

FAQ

How many videos does a search return?

As many as you ask (maxResults), up to what YouTube shows logged out: one search walk ends at about 500 results, of which YouTube itself repeats 13–20 % (230–350 distinct videos on our tests). With deepSearch the search goes on into YouTube's three duration slices, each to its own end: 991 distinct videos for minecraft on our test, and 1,409 for an ordinary keyword (sourdough bread recipe, four walks each to its own end, 336 s). YouTube's estimatedResults (millions for a broad keyword) is an estimate no reader can reach; the summary row shows it next to what was read. Narrower keywords, an upload-date window or several searches reach more.

Are the views, likes and comment counts exact?

Yes, where YouTube has them: views and likes come from the video's record (the page rounds them), the comment count from the comments panel or, where the panel rounds it, from the comments header (an extra call of ≈ 150 bytes). A count is null only where YouTube shows none — a live stream's chat, a members-only video, comments turned off (comments_turned_off true) — and note says which. The subscriber count is the one YouTube rounds itself ("34.6K"); no logged-out page shows it exact.

Does the country matter?

For searches, yes: YouTube shows each country a different set (a GB, DE, IN, JP or BR reader shares 0–5 of the first 20 news results with a US one; a niche keyword is nearly the same everywhere). country picks where each search is read from; every row says it. A channel's tabs, a playlist and a video's record are the same everywhere.

Can it read transcripts, comments or download videos?

No: this Actor reads each video's public record. For transcripts use our YouTube Transcript Scraper; comment texts and video files are not read. Dislikes are not on YouTube logged out (the numbers some tools show come from a third-party estimate).

What is ads_shown?

With includeMonetization, one more call per video asks YouTube's player as a logged-out viewer would: ads_shown is true when YouTube served ad slots for the video, and has_paid_promotion when the creator declared a paid promotion. It is a signal of what a viewer gets, not the channel's monetization status, which YouTube does not publish; members-only, age-restricted and scheduled videos are not playable logged out, so it is null on them.

Does it need a YouTube account, cookies or an API key?

No. Everything is read from YouTube's public pages' own data, logged out, with a plain HTTP client; nothing of yours is sent to YouTube. No YouTube Data API key and its quota.

Which status is which?

A video row's status is ok (charged), not_found (YouTube has no video with that id: two reads agree), private, unavailable (removed or blocked; YouTube's own reason in note), failed (refused past the retries; read it again later), invalid (the value names nothing on YouTube), not_read (maxItems, the charge limit or the run's time limit came first), duplicate (another input of the run delivered it) or skipped (a video input in skipVideoIds, or one an earlier onlyNew run delivered). A summary row's status is ok, no_results, not_found (a channel or playlist that does not exist), failed, not_read or duplicate (the same channel as another input of the run, read once). Only ok video rows are charged.

Integration

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/youtube-video-scraper').call({"searchQueries": ["Crawlee"], "maxResults": 10});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Python

from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/youtube-video-scraper').call(run_input={'searchQueries': ['Crawlee'], 'maxResults': 10})
items = client.dataset(run['defaultDatasetId']).list_items().items

CLI

$apify call hyperbach/youtube-video-scraper --input '{"searchQueries": ["Crawlee"], "maxResults": 10}'

REST

curl -X POST "https://api.apify.com/v2/acts/hyperbach~youtube-video-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' -d '{"searchQueries": ["Crawlee"], "maxResults": 10}'

Support

apify@hyperbach.com

This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.