YouTube Shorts Scraper
Pricing
from $2.90 / 1,000 shorts
YouTube Shorts Scraper
Scrape YouTube Shorts from channels, search queries, and direct URLs. Extract video metadata, engagement metrics, subtitles, translations, and detailed channel information.
Pricing
from $2.90 / 1,000 shorts
Rating
0.0
(0)
Developer
Upadhyay Labs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 hours ago
Last modified
Categories
Security
Limited permissionsShare
YouTube Shorts Scraper
Extract structured data from YouTube Shorts at scale — titles, view counts, likes, durations, hashtags, upload dates, captions, and full channel statistics — from any channel, search query, or direct Shorts URL. No browser required, no YouTube API key, no daily quota.
Try it instantly: paste
mkbhdinto the Channels field and click Start.
What does YouTube Shorts Scraper do?
YouTube Shorts Scraper collects metadata from public YouTube Shorts without a browser or a YouTube Data API key. It reads the same public data YouTube's own web pages render and paginates through channel Shorts tabs and search results — the same mechanism the "load more" button uses on the site.
Each output row contains:
- Video metadata: title, URL, thumbnail, view count, likes, duration, upload date, description, hashtags, links, comment count, age-restriction status, paid-promotion flag, members-only flag, collaborators
- Subtitles / captions: full SRT text for configured languages (auto-generated or user-submitted tracks)
- Channel details: name, handle, subscriber count, verification badge, avatar, banner, description, join date, total views and videos, country, social links
- Provenance: which input triggered this result, what URL was actually scraped, position in the listing, timestamp
Supported input sources:
| Source | Example |
|---|---|
| Channel handle | mkbhd, @NASA |
| Channel ID | UCBcRF18a7Qf58cCRy5xuWwQ |
| Channel URL | https://www.youtube.com/@mkbhd |
| Search query | "satisfying ASMR" |
| Direct Short URL | https://www.youtube.com/shorts/VIDEO_ID |
Why use YouTube Shorts Scraper?
Building this on the Apify platform instead of a custom script gets you:
- No YouTube Data API key or quota — the official API caps Shorts-relevant metadata and burns daily quota fast; this Actor reads the same public data YouTube's own web client renders, with no key and no 10,000-unit/day ceiling.
- Lightweight and fast — no headless browser required, so runs finish 5–10× faster and use a fraction of the memory browser-based scrapers need for the same volume.
- Built-in proxy rotation & session management — residential Apify Proxy integration and automatic session retirement on blocks, so you don't hand-roll IP rotation.
- Pay only for results — Pay-Per-Event pricing charges per Short actually delivered, not per minute of compute.
- Scheduling, webhooks, and API access — run on a cron schedule, trigger a webhook on completion, or call it from your own code via the Apify API or JS/Python clients.
- Structured, typed output — every field has a documented type and a defined
nullmeaning (never a guess), so downstream pipelines don't have to special-case missing data.
Use cases
- Creator analytics — track posting frequency, engagement trends, and format evolution across any creator's Shorts library
- Market research — benchmark your channel's like-to-view ratio and comment rate against competitors in the same niche
- Influencer discovery — find active Shorts creators by topic, then filter by subscriber count and engagement rate
- Content monitoring — watch a brand's or competitor's Shorts feed for new uploads, hashtags, or paid promotions
- Caption and transcription pipelines — pull auto-generated or user-submitted SRT captions at scale for translation, summarization, or keyword extraction
- SEO and hashtag research — identify which hashtags appear most on high-performing Shorts in a niche
- Academic and social-media research — build datasets of short-form video engagement across channels or topics
How to scrape YouTube Shorts
1. Run on the Apify platform (no setup needed)
- Open the Actor on Apify Store and click Try for free.
- In the Input tab, add at least one Channel, Search query, or Direct URL.
- Adjust limits and enrichment options (see Input below).
- Click Start and wait for the run to finish.
- Download results from the Dataset tab in JSON, CSV, or Excel.
2. Run via the Apify API
Start a run and download results programmatically:
# Start a runcurl -X POST \"https://api.apify.com/v2/acts/YOUR_ACTOR_ID/runs?token=YOUR_API_TOKEN" \-H "Content-Type: application/json" \-d '{"channels": ["mkbhd"],"maxResultsShorts": 50,"extendedVideoDetails": true,"extendedChannelDetails": true}'
See the Apify API docs for full run management, webhooks, and dataset export endpoints.
3. Run via Apify JavaScript or Python client
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });const run = await client.actor('YOUR_ACTOR_ID').call({channels: ['mkbhd', 'nasa'],maxResultsShorts: 100,extendedVideoDetails: true,extendedChannelDetails: true,subtitleLanguages: ['en'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
from apify_client import ApifyClientclient = ApifyClient("YOUR_API_TOKEN")run = client.actor("YOUR_ACTOR_ID").call(run_input={"channels": ["mkbhd", "nasa"],"maxResultsShorts": 100,"extendedVideoDetails": True,"extendedChannelDetails": True,"subtitleLanguages": ["en"],})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item)
How much will it cost to scrape YouTube Shorts?
This Actor uses Pay-Per-Event (PPE) pricing — you pay a flat, predictable fee for each Short actually written to the dataset, never for platform compute time, proxy bandwidth, or failed/retried requests.
| Event | Charged? |
|---|---|
| Short successfully scraped and saved | ✅ Yes — one event per Short |
| Error / placeholder row (no Shorts found, unavailable video, empty search) | ❌ No |
Cost control: set maxItems or maxResultsShorts to cap output. The run also stops automatically once your configured budget limit (ACTOR_MAX_TOTAL_CHARGE_USD) is reached — you are never billed beyond what you approved.
See the Pricing tab on this Actor's Store page for the current per-event rate.
Input
At least one of channels, searchQueries, or startUrls is required. All other fields are optional.
Sources
| Field | Type | Default | Description |
|---|---|---|---|
channels | string[] | — | Channel handles, @handles, channel IDs, or channel/Shorts URLs. Examples: "mkbhd", "@NASA", "UCBcRF18a7Qf58cCRy5xuWwQ", "https://www.youtube.com/@mkbhd" |
searchQueries | string[] | [] | Plain-text search terms. Results come from YouTube's Shorts search tab. |
startUrls | RequestSource[] | [] | Direct https://www.youtube.com/shorts/VIDEO_ID links scraped individually, bypassing channel discovery. |
Accepted channel formats
| Input | Resolves to |
|---|---|
"nasa" / "@NASA" | /@NASA/shorts |
"UCBcRF18a7Qf58cCRy5xuWwQ" | /channel/UCBcRF18a7Qf58cCRy5xuWwQ/shorts |
"https://www.youtube.com/@NASA/videos" | /@NASA/shorts (tab switched automatically) |
"https://www.youtube.com/c/nasa" | /c/nasa/shorts |
Duplicate inputs (e.g. "nasa" and "@NASA") are automatically deduplicated by resolved URL.
Limits & filtering
| Field | Type | Default | Description |
|---|---|---|---|
maxResultsShorts | integer | 25 | Maximum Shorts to collect per channel or search query. Range: 1–20,000. |
maxItems | integer | 0 | Global cap across the entire run. 0 = unlimited (bounded only by maxResultsShorts per source). |
oldestPostDate | string | — | Stop paginating a source once Shorts older than this date are encountered. Accepts ISO dates ("2024-01-01") or relative values ("7 days", "2 days ago"). When set, sortChannelShortsBy is automatically forced to NEWEST. |
sortChannelShortsBy | string | "NEWEST" | Order for channel Shorts pages: NEWEST (most recent first), POPULAR (most viewed first), OLDEST (oldest first). Ignored for search queries. |
Data enrichment
| Field | Type | Default | Description |
|---|---|---|---|
extendedChannelDetails | boolean | true | Fetches the channel About page for description, join date, location, total views, total videos, avatar, and banner. One extra request per unique channel. |
extendedVideoDetails | boolean | true | Visits each Short's watch page for exact like count, comment count, exact upload date, description, subtitle tracks, collaborators, and paid-promotion flag. ~2× slower but significantly richer data. |
subtitleLanguages | string[] | ["en"] | BCP-47 language codes for which to download and populate full SRT caption text. Other tracks appear in output with srt: null (no extra request made). Set [] to skip all SRT downloads. Only applies when extendedVideoDetails is true. |
translationLanguage | string | "" | BCP-47 code (e.g. "es", "fr"). When set, re-fetches the watch page with ?hl=<lang> to get a localized title and description. translatedTitle and translatedText are null when no genuine translation exists. One extra request per video. Only applies when extendedVideoDetails is true. |
Performance & proxy
| Field | Type | Default | Description |
|---|---|---|---|
proxyConfiguration | object | Apify RESIDENTIAL | Proxy settings. Residential proxies are strongly recommended — YouTube throttles datacenter IPs quickly. |
maxRequestRetries | integer | 6 | Maximum retries per failed request before recording an error row. Range: 0–20. |
maxConcurrency | integer | 15 | Parallel requests. Higher = faster, but YouTube throttles aggressive concurrency even through residential proxies. Avoid exceeding ~20 without also widening your proxy pool. |
Example input — MKBHD's latest 50 Shorts with English captions
{"channels": ["mkbhd"],"maxResultsShorts": 50,"sortChannelShortsBy": "NEWEST","extendedChannelDetails": true,"extendedVideoDetails": true,"subtitleLanguages": ["en"],"proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"]}}
Output
One dataset row per Short. Below is a real sample record from MKBHD's channel (mkbhd), with extendedVideoDetails: true and extendedChannelDetails: true:
{"title": "This can SEE Sound!","type": "shorts","id": "ai1gIWacf0I","url": "https://www.youtube.com/shorts/ai1gIWacf0I","thumbnailUrl": "https://i.ytimg.com/vi/ai1gIWacf0I/maxresdefault.jpg","viewCount": 2213234,"date": "2026-10-27T20:14:55.000Z","likes": 63749,"durationSeconds": 90,"duration": "00:01:30","likesToViewsRatio": 0.0288,"commentsCount": 1294,"commentsTurnedOff": false,"text": "This is an acoustic imaging camera","descriptionLinks": [],"hashtags": [],"subtitles": [{"language": "English (auto-generated)","languageCode": "en","type": "asr","url": "https://www.youtube.com/api/timedtext?v=ai1gIWacf0I&...","isTranslatable": true,"srt": null}],"isMembersOnly": null,"isAgeRestricted": false,"isMonetized": null,"isPaidContent": false,"collaborators": [],"location": null,"translatedTitle": null,"translatedText": null,"aiVideoDescription": null,"aiVideoSummary": null,"transcriptionUrl": null,"channelName": "Marques Brownlee","channelUrl": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ","channelId": "UCBJycsmduvYEL83R_U4JriQ","channelUsername": "mkbhd","numberOfSubscribers": 21400000,"isChannelVerified": true,"channelAvatarUrl": "https://yt3.googleusercontent.com/...","channelBannerUrl": "https://yt3.googleusercontent.com/...","channelDescription": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics...","channelJoinedDate": "Mar 21, 2008","channelTotalViews": 5847901236,"channelTotalVideos": 1863,"channelLocation": "United States","channelDescriptionLinks": [{ "text": "Twitter", "url": "http://twitter.com/MKBHD" },{ "text": "Instagram", "url": "http://instagram.com/MKBHD" }],"aboutChannelInfo": {"channelName": "Marques Brownlee","channelUrl": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ","channelId": "UCBJycsmduvYEL83R_U4JriQ","channelUsername": "mkbhd","channelAvatarUrl": "https://yt3.googleusercontent.com/...","channelBannerUrl": "https://yt3.googleusercontent.com/...","channelDescription": "MKBHD: Quality Tech Videos | YouTuber | Geek...","channelJoinedDate": "Mar 21, 2008","channelTotalViews": 5847901236,"channelTotalVideos": 1863,"channelLocation": "United States","channelDescriptionLinks": [{ "text": "Twitter", "url": "http://twitter.com/MKBHD" }],"numberOfSubscribers": 21400000},"inputChannelUrl": "https://www.youtube.com/@mkbhd","input": "mkbhd","fromYTUrl": "https://www.youtube.com/@mkbhd/shorts","fromChannelListPage": "shorts","order": 0,"scrapedAt": "2026-10-30T16:26:56.338Z"}
Output field reference
Video fields
| Field | Type | Notes |
|---|---|---|
title | string|null | Short's title |
translatedTitle | string|null | Populated only when translationLanguage is set and YouTube has a genuine translation; null otherwise |
translatedText | string|null | Same gate as translatedTitle |
type | string | Always "shorts" |
id | string | YouTube video ID |
url | string | https://www.youtube.com/shorts/{id} |
thumbnailUrl | string|null | maxresdefault.jpg (1280×720) from YouTube CDN |
viewCount | integer|null | Total view count |
date | string|null | ISO 8601 UTC upload timestamp; requires extendedVideoDetails: true |
likes | integer|null | Like count; requires extendedVideoDetails: true |
durationSeconds | integer|null | Duration in whole seconds; requires extendedVideoDetails: true |
duration | string|null | HH:MM:SS formatted duration; requires extendedVideoDetails: true |
likesToViewsRatio | number|null | round(likes / viewCount, 4); null when either value is missing or viewCount is 0 |
commentsCount | integer|null | From engagement panel or InnerTube /next; null when indeterminate (not the same as comments being disabled) |
commentsTurnedOff | boolean|null | true = explicitly disabled (confirmed signal), false = explicitly enabled (confirmed signal), null = could not determine |
text | string|null | Short's description |
descriptionLinks | array | Links extracted from the description |
hashtags | array | #tags extracted from title and description |
subtitles | array|null | Caption track objects; null when no tracks exist. Each object always includes an srt key. |
isMembersOnly | boolean|null | null when no members-only badge is detectable |
isAgeRestricted | boolean|null | true = explicit age gate; false = isFamilySafe confirmed; null = no reliable signal (common on Shorts) |
isMonetized | null | Always null — YouTube does not expose monetisation status in public data. Kept for schema compatibility. |
isPaidContent | boolean | true only when YouTube's "Includes paid promotion" disclosure overlay is present; requires extendedVideoDetails: true |
collaborators | array | @-mentions extracted from the description |
location | string|null | This video's location tag (not the channel's country); null for virtually all Shorts |
aiVideoDescription | null | Not implemented. Kept for schema compatibility. |
aiVideoSummary | null | Not implemented. Kept for schema compatibility. |
transcriptionUrl | null | Not implemented. Use subtitles[] for caption data. |
Subtitle tracks — subtitles[]
Each object in the subtitles array:
| Field | Type | Notes |
|---|---|---|
language | string | Human-readable language name (e.g. "English", "English (auto-generated)") |
languageCode | string | BCP-47 code (e.g. "en") |
type | string | "user_generated" or "asr" (auto-generated) |
url | string | YouTube timedtext API URL — always present |
isTranslatable | boolean | Whether YouTube offers translation of this track |
srt | string|null | Full SRT caption text when this track's language code matches subtitleLanguages; null otherwise (no extra request made) |
SRT format: sequential 1-based index, HH:MM:SS,mmm --> HH:MM:SS,mmm timestamps, decoded text (no HTML entities).
Channel fields (top-level, always flat)
| Field | Type | Notes |
|---|---|---|
channelName | string|null | Channel display name |
channelUrl | string|null | Canonical /channel/UCxxxxxx URL |
channelId | string|null | UCxxxxxx identifier |
channelUsername | string|null | Handle without @ |
numberOfSubscribers | integer|null | Subscriber count |
isChannelVerified | boolean|null | Verified badge presence |
channelAvatarUrl | string|null | Channel profile image URL |
channelBannerUrl | string|null | Channel banner image URL |
channelDescription | string|null | Requires extendedChannelDetails: true |
channelJoinedDate | string|null | Requires extendedChannelDetails: true |
channelTotalViews | integer|null | Requires extendedChannelDetails: true |
channelTotalVideos | integer|null | Requires extendedChannelDetails: true |
channelLocation | string|null | Channel's declared country; requires extendedChannelDetails: true |
channelDescriptionLinks | array | Social links from the channel About page; requires extendedChannelDetails: true |
aboutChannelInfo | object|null | Self-contained nested object with all channel fields above. null when extendedChannelDetails: false or fetch failed. |
Provenance fields
| Field | Notes |
|---|---|
inputChannelUrl | Resolved @handle URL (e.g. https://www.youtube.com/@mkbhd). null for search sources. |
input | The raw value exactly as given in the input, unresolved. |
fromYTUrl | Actual URL scraped to produce this record. |
fromChannelListPage | "shorts" for channel sources; null for search queries. |
order | 0-based position in the source listing. |
scrapedAt | ISO 8601 UTC timestamp of when this record was saved. |
Error rows
When an input cannot be processed, the Actor pushes a structured error row to the dataset instead of crashing the run. Error rows are not charged as events.
{"url": "https://www.youtube.com/@nonexistentchannel","input": "nonexistentchannel","error": "CHANNEL_DOES_NOT_EXIST","note": "Channel handle resolved to a 404.","scrapedAt": "2026-10-30T16:26:56.338Z"}
Error codes
| Error code | Cause |
|---|---|
CHANNEL_DOES_NOT_EXIST | The handle or URL does not resolve to a valid channel |
CHANNEL_HAS_NO_SHORTS | Channel exists and is accessible, but has no public Shorts |
NOT_FOUND | HTTP 404 — page does not exist |
PARSE_FAILED | Page loaded but ytInitialData JSON could not be read (YouTube layout change) |
VIDEO_UNAVAILABLE | Video is deleted, private, or region-blocked |
AGE_RESTRICTED | Video requires a logged-in account to view |
NO_RESULTS | Search query returned no Shorts |
DATE_FILTER_TOO_STRICT | Shorts exist on the channel but none are newer than oldestPostDate |
REQUEST_FAILED | All retries exhausted (network error, persistent blocking) |
Integrations
YouTube Shorts Scraper connects to the tools you already use — no extra code required.
Zapier
Trigger a Zapier Zap when a run finishes and automatically push Shorts data to Google Sheets, Airtable, Slack, or any of Zapier's 5,000+ apps. See Apify + Zapier integration.
Make (formerly Integromat)
Use the Apify Make module to start runs on a schedule and route results into your Make scenarios. See Apify + Make integration.
Google Sheets
Export any completed dataset directly to a Google Sheet from the Apify Console in one click, or automate it via the Apify Google Sheets integration.
Webhooks
Configure a webhook on the Actor run to POST the dataset URL to your own endpoint the moment the run succeeds. Go to Actor → Settings → Webhooks in the Apify Console.
Apify API
Every run, dataset, and key-value store is accessible via the Apify REST API v2. Paginate results, stream NDJSON, or poll run status programmatically.
MCP Server (AI agents)
This Actor is callable as a tool by any MCP-compatible AI agent (Claude, Cursor, etc.) via the Apify MCP Server. No additional setup — the Actor appears as a native tool once MCP is connected.
Scheduled runs
Set the Actor to run on a daily, weekly, or custom cron schedule from Actor → Settings → Schedules in the Apify Console. Combined with webhooks or Google Sheets export, this creates a fully automated YouTube Shorts monitoring pipeline.
Limitations
- Subtitle SRT may be
nulleven for a language listed insubtitleLanguages. YouTube's PoToken (proof-of-origin token) requirement can block automated caption retrieval on individual videos — this is a platform-wide restriction affecting all automated scrapers and cannot be reliably bypassed. The track'surlfield is always still returned regardless. locationisnullfor virtually all Shorts. It only populates for content derived from live broadcasts that carry a location tag.isMonetizedis alwaysnull. YouTube does not expose monetisation status in public InnerTube data.commentsCountmay benulleven when comments are enabled. This happens when the engagement panel header omits the count — it is not the same ascommentsTurnedOff: true.commentsTurnedOffis three-state:trueonly on a confirmed explicit disabled signal;falseon a confirmed enabled signal;nullwhen neither signal is found.isAgeRestrictedisnullfor most Shorts because YouTube omits theisFamilySafeflag on Shorts pages. It is notfalseby default.- CSV / Excel export — nested objects (
aboutChannelInfo) and arrays (subtitles,collaborators,descriptionLinks,channelDescriptionLinks) are JSON-serialized into a single column when exported via the Apify Console. This is standard Apify flattening behavior. Use JSON or JSONL export for full fidelity.
FAQ
Do I need a YouTube API key or Google Cloud project?
No. YouTube Shorts Scraper reads the same public data YouTube's own web pages display — no API key, no OAuth, no daily quota.
Is it legal to scrape YouTube Shorts?
This Actor only extracts data that is already publicly visible on YouTube — titles, view counts, captions, and channel stats the uploader chose to publish. It does not access private data, bypass logins, or scrape anything behind authentication. See the disclaimer below for your own compliance responsibilities.
Why is commentsTurnedOff sometimes null instead of true or false?
YouTube doesn't always expose an explicit "comments enabled/disabled" signal on the Shorts player response. Rather than guess, this Actor returns null whenever neither a confirmed-enabled nor confirmed-disabled signal is found — a false positive here (wrongly reporting comments as off) is worse for downstream automation than an honest "unknown."
Why is srt sometimes null even though I set subtitleLanguages?
YouTube intermittently gates its caption/transcript endpoint behind a PoToken (proof-of-origin token) requirement — a platform-wide restriction, not an Actor bug. When that happens for a given run, the subtitle track's metadata (language, URL, type) is still returned; only the downloaded SRT text is null. See Limitations.
How many Shorts can I scrape per run?
There's no hard limit from the Actor itself — set maxResultsShorts (per source) and maxItems (global cap) as high as you need. In practice, run size is bounded by your proxy pool and your Pay-Per-Event budget (ACTOR_MAX_TOTAL_CHARGE_USD), both of which you control from the input and run settings.
Can I scrape Shorts by hashtag or topic instead of a channel?
Yes — use searchQueries with your hashtag or keyword (e.g. "#satisfying" or "AI news"). Results come from YouTube's Shorts-filtered search, the same listing the Shorts search tab shows.
Does this Actor work without residential proxies?
It can run with any Apify Proxy group or your own proxy URLs, but YouTube throttles and blocks datacenter IPs aggressively. Residential proxies are strongly recommended for reliable runs at any meaningful volume.
Disclaimer
This Actor is intended for ethical, legitimate data collection and does not extract any private user data such as emails, exact personal locations, or anything not already published by the channel owner or uploader — it only extracts what is publicly visible on YouTube. You are responsible for how you use the extracted data: personal data (even when publicly posted) may be protected under GDPR in the EU and under similar regulations elsewhere. If you intend to process or store personal data at scale, consult your own legal counsel to confirm your use case is compliant.
Support
- Bug report or feature request: open an issue on the Issues tab of this Actor's Store page.
- Custom scraping solution: contact the developer through their Apify profile.
Made with ☕ by upadhyay.labs