YouTube Video Scraper & YouTube Shorts Scraper: Search, Channel
Pricing
from $2.00 / 1,000 videos
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
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
3 days ago
Last modified
Categories
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_offtells 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'snotesays 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.
deepSearchthen walks YouTube's three duration bands to their own ends, every video once: 348 distinct videos forminecraftin 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
newsresults with a US reader. Each search is read from a connection in the run'scountry(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):
| field | meaning |
|---|---|
row_type | video (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). |
status | A 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). |
note | Why, 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_id | YouTube's 11-character video id; the de-duplication key (one row and one charge per video a run). |
url | The video on YouTube: https://www.youtube.com/watch?v= |
title | The creator's original title (the same under every language). |
title_localized | The title in the asked language when the creator translated it and it differs from the original; null otherwise. |
video_type | video, 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_short | YouTube'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_seconds | Length in seconds; null for a live or scheduled stream. |
duration | Length as YouTube writes it: 5:50, 1:14:56. |
view_count | Exact 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_text | The view count as the watch page or the card writes it ("8,449 views", "445 watching now", "1.2K views"). |
likes | Exact likes from the record (the like button rounds them); null when the creator hides them. |
comment_count | The 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_off | true when the watch page says "Comments are turned off"; false when a count was read; null when neither is shown. |
published_at | The publish date and time, UTC (the record's ISO date with its offset, converted). |
uploaded_at | The upload date and time, UTC (the record's own; equal to published_at for most videos). |
published_text | The 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. |
description | The full description (the record's own text, longer than the watch page's). |
description_snippet | The search card's description snippet, where the video came from a search. |
tags | The creator's keywords (tags) on the record. |
hashtags | The hashtags above the title and the #tags written in the description, in order, each once. |
chapters | The chapters: title, start in seconds and as written ("1:06"). |
description_links | Every link in the description with its real URL (YouTube's redirect unwrapped) and its text. |
category | YouTube's category ("Science & Technology"). |
location | The place the creator set above the title ("UNITED KINGDOM"), when one is shown. |
license | The license row of the watch page ("Creative Commons Attribution license (reuse allowed)"), when the video states one. |
thumbnail_url | The largest thumbnail the record lists (up to 1920 × 1080). |
thumbnails | Every thumbnail size the record lists: url, width, height. |
badges | The card's and the watch page's badges ("4K", "CC", "New", "LIVE", "Members only"). |
channel_id | The channel's UC… id (a collaboration's primary channel). |
channel_name | The channel's name. |
channel_url | The channel on YouTube (its @handle URL, else /channel/ |
channel_handle | The channel's @handle. |
channel_subscribers | The subscriber count as YouTube rounds it ("34.6K subscribers" = 34,600; YouTube shows no exact count logged out); read in English only. |
channel_subscribers_text | The subscriber count as YouTube writes it. |
channel_verified | The channel's verified badge (an artist's too). |
channel_avatar_url | The channel's avatar (null on a collaboration, whose dialog names several channels). |
collaborators | A collaboration's channels: channel id, name, @handle, subscribers text. |
is_live | true while the video is a live stream on air. |
live_status | none, live (on air), upcoming (scheduled) or was_live (a past stream). |
scheduled_start_at | A scheduled stream's or premiere's start, UTC. |
is_members_only | A video for the channel's paying members (served with its record, flagged). |
age_restricted | YouTube asks viewers to confirm their age (the record is served, flagged). |
is_family_safe | YouTube's family-safe flag. |
is_unlisted | YouTube's unlisted flag. |
unavailable_country_codes | The countries the video is blocked in: YouTube's 249 country codes minus the record's available list (empty for most videos). |
ads_shown | includeMonetization: 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_promotion | includeMonetization: the creator declared a paid promotion (YouTube's "Includes paid promotion" label); null when not asked. |
views_per_hour | The exact views over the hours since publication, at read_at (derived). |
engagement_rate | (likes + comments) / views (derived; null without views or likes). |
channel_country | includeChannelDetails: the channel's country (ISO code) from its About. |
channel_joined_date | includeChannelDetails: the channel's joined date. |
channel_video_count | includeChannelDetails: the channel's exact video count. |
channel_view_count | includeChannelDetails: the channel's exact total views. |
details_read | true: the record and watch page were read (charged as a video); false: the list's card alone (charged as a card row). |
input | The input as you gave it (the keywords, URL, id, @handle or #tag). |
input_type | search, channel_videos, channel_shorts, channel_streams, playlist, hashtag, video, channel (a channel that could not be read), invalid. |
rank | The video's place in its list, in YouTube's order (the first appearance; repeats are dropped). |
page | The list page the video was on (inside its slice for a search). |
slice | A search's walk the video came from: main, or a duration slice of deepSearch (duration:under4, duration:4to20, duration:over20). |
source_url | The YouTube page the row was read from (the search with its filters, the channel tab, the playlist). |
country | The country the search was read from (the run's country). |
language | The interface language asked (hl). |
read_at | When the run read this row (UTC). |
verdict | A 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_detail | The verdict's reason in words, with the counts. |
videos_delivered | A summary row: the videos delivered for this list. |
videos_found | A summary row: the distinct videos the list showed (delivered, skipped or dropped). |
pages | A summary row: the list pages read. |
estimated_results | A search's summary row: YouTube's own estimatedResults on page 1 (an estimate, not what a walk can reach). |
list_count | A playlist's summary row: the count in its header (it counts hidden videos too). |
list_count_text | The list header's count as written ("253 videos", "298 videos", "47M videos • 5.6M channels"). |
list_title | A playlist's or hashtag's title. |
slices | A search's summary row: each walk (main, the duration slices) with its pages, cards and how it ended. |
duplicates_skipped | A summary row: repeats inside the list and videos another input of the run took (each delivered once). |
known_skipped | A summary row: videos an earlier run of the onlyNew memory delivered (skipped free). |
filtered_out | A summary row: videos dropped by oldestPostDate or skipVideoIds (free). |
failed_details | A 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.
| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
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 freeinvalidrow. - How many videos —
maxResultscaps each search, playlist, hashtag and channel Videos tab (50 by default);maxResultsShortsandmaxResultStreamsread a channel's Shorts and Live tabs (0 by default: not read);maxItemscaps 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). WithdeepSearch(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 whenincludeDetailsis on (the default;details_readtrue, 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'sestimated_resultsfor a search, the header'slist_countfor a playlist,slicesfor a deep search, the repeats and skips, theverdictwith itsverdict_detail, and thestatus. - 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 exactpublished_at. On a channel, newest first is the tab's own order, andoldestPostDatestops the walk at the first older video. - Scheduled runs: only the new videos — Save a task with
onlyNew(and astateStoreNameto 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 inmaxResults, so a channel's task reads its newest N and delivers only the ones not seen before, never older videos to fill the count.skipVideoIdsskips a list of your own.
Input configuration
| field | type | default | what it does |
|---|---|---|---|
searchQueries | array | [] | 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. |
startUrls | array | [] | 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. |
maxResults | integer | 50 | How 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). |
maxResultsShorts | integer | 0 | How 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. |
maxResultStreams | integer | 0 | How many of each channel's Live tab (past streams, live now, scheduled) to read. 0 = the Live tab is not read. |
searchType | video / 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. |
uploadDate | any / 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. |
duration | any / under4 / 4to20 / over20 | "any" | YouTube's duration filter. |
features | array | [] | YouTube's feature filters, each applied by YouTube (several narrow the search together). |
sortBy | relevance / 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. |
deepSearch | boolean | true | A 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. |
country | US / 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. |
language | string | "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. |
channelSortBy | newest / 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. |
oldestPostDate | string | YYYY-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. | |
includeDetails | boolean | true | On (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. |
includeMonetization | boolean | false | One 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). |
includeChannelDetails | boolean | false | Each channel's About once per run: country, joined date, exact video and view counts, on every row of its videos. |
onlyNew | boolean | false | Remember 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. |
stateStoreName | string | "" | The name of the onlyNew memory, so several tasks can share one or keep theirs apart. Empty: a name derived from the inputs. |
skipVideoIds | array | [] | 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. |
maxItems | integer | 0 | The 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. |
maxConcurrency | integer | 2 | How 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). |
proxyUrl | string | Empty (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
ERRORholds 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 ApifyClientclient = 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
This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.