# YouTube Shorts Scraper: Channels, Hashtags & Search (`alexmorain/youtube-shorts-scraper`) Actor

Scrape YouTube Shorts from channels, hashtags and search queries in one run: exact views and likes, comments count, publish date, duration, hashtags and channel stats. Duplicates removed before billing, new-Shorts-only monitoring with webhook. No browser.

- **URL**: https://apify.com/alexmorain/youtube-shorts-scraper.md
- **Developed by:** [Alexis Morain](https://apify.com/alexmorain) (community)
- **Categories:** Social media, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 shorts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

This Actor turns [YouTube Shorts](https://www.youtube.com/shorts) into structured data. Give it
channels, hashtags, search queries or Short URLs, and it returns every Short with its **exact
view count, exact like count**, comment count, publish date, description, hashtags, and the
channel with its subscriber count. With extended details on (the default), each Short also gets
its **duration and exact publish time**.

It talks to YouTube over plain HTTP, the way the YouTube app does. No browser, no login, no
proxy to pay for. 1,000 Shorts from 20 channels, all details included, take about a minute.
Pricing is pay per event: **$2.00 per 1,000 Shorts** at the entry price, **from $1.20 / 1,000
on Gold**, date filter included; $3.00 per 1,000 with duration and exact time ($1.80 on Gold);
and $0.002 per run.

The YouTube Data API has no Shorts filter and caps you at 10,000 quota units a day. This Actor
is a YouTube Shorts API without the quota: a list of sources in, JSON out, scheduled or called
from your code.

***

### What does YouTube Shorts Scraper do?

It reads four kinds of sources, and you can mix them in one run:

- **Channels**: the Shorts tab of any channel, newest, most popular or oldest first. There is
  no hidden ceiling: the NBA channel holds more than 14,000 Shorts and the Actor pages through
  all of them if you ask.
- **Hashtags**: the Shorts feed YouTube shows on `youtube.com/hashtag/<tag>/shorts`.
- **Search queries**: YouTube search with the Shorts filter on, optionally limited to today,
  this week, this month or this year.
- **Short URLs**: single Shorts, to refresh the counters of videos you already track.

A Short found by two sources is returned once and charged once. YouTube repeats Shorts as you
scroll a search (28% to 44% repeats on the 8 pages we measured); the Actor drops them before
they reach your dataset.

### Why use this YouTube Shorts scraper?

- **Creator and competitor tracking.** Schedule it daily on a list of channels with **New
  Shorts only**: each run returns only Shorts never returned before, and a webhook pings
  Slack, Zapier, Make or n8n when a tracked channel posts.
- **Influencer discovery.** Search a niche or a hashtag, then sort by views, likes or
  subscribers to find the creators who carry it.
- **Trend research.** What a hashtag or a keyword shows this week, in the country you pick.
  YouTube ranks searches per country: the same query in the US and in Brazil shares about a
  third of its first results.
- **Datasets for analysis and AI.** Titles, descriptions, hashtags and engagement for
  thousands of Shorts, ready for a spreadsheet, a BI tool or a model.

What you get that a grid scrape misses:

- **Exact counters.** The Shorts grid shows "29M views". The Actor opens each Short and
  returns 29,104,377. Likes come exact too, read from the like button itself.
- **Every source type.** Channels, hashtags, searches and URLs in the same run, with
  `inputType` and `input` on each row so you know which source found it.
- **Free date filter.** `Published on or after` costs nothing extra. On a channel read newest
  first, the Actor stops paging as soon as it reaches older Shorts, so a daily check is fast.
- **It tells you what it could not get.** A channel without Shorts, a hashtag that does not
  exist or a search that returns nothing gets one error row with the reason, never charged.

### What data can I extract from YouTube Shorts?

| Field | Description |
|---|---|
| `id`, `url`, `title` | Short ID, URL and title |
| `description`, `hashtags` | Full description and the hashtags in it, without `#` |
| `viewCount` | Exact view count |
| `likeCount`, `likeCountExact` | Like count, and whether it is exact |
| `commentCount`, `commentCountText` | Comment count as YouTube rounds it (1.2K becomes 1200) |
| `publishedDate` | Publish day, `YYYY-MM-DD` |
| `publishedAt`, `durationSeconds`, `duration` | Exact publish time and duration (extended details) |
| `category`, `isFamilySafe` | YouTube category and family-safe flag (extended details) |
| `thumbnailUrl` | Vertical thumbnail |
| `channelId`, `channelName`, `channelHandle`, `channelUrl` | The channel |
| `channelSubscribers`, `channelVerified`, `channelAvatarUrl` | Subscribers, badge and avatar |
| `inputType`, `input`, `position` | The source that found the Short and its rank in that source |

### How to scrape YouTube Shorts

1. Add one or more **channels** (`@MrBeast`, a channel URL or ID), **hashtags** (`cooking`),
   **search queries** (`air fryer recipe`) or **Short URLs**.
2. Set **Max Shorts per source**: 50 by default, counted from the top of each feed.
3. Optionally pick a **channel order**, a **Published on or after** date or span (`7 days`),
   and a **country** for searches and hashtags.
4. Leave **Duration and exact publish time** on if you need them, turn it off for a cheaper
   run.
5. Press **Start**, then download the dataset as JSON, CSV, Excel or HTML.

### How much does it cost to scrape YouTube Shorts?

| Event | Free | Bronze | Silver | Gold and up |
|---|---|---|---|---|
| Short | $2.00 / 1,000 | $1.70 / 1,000 | $1.40 / 1,000 | $1.20 / 1,000 |
| Detailed Short (instead of Short) | $3.00 / 1,000 | $2.50 / 1,000 | $2.10 / 1,000 | $1.80 / 1,000 |
| Actor start, per GB of memory | $0.002 | $0.002 | $0.002 | $0.002 |

The 50 latest Shorts of 20 channels (1,000 Shorts) cost $2.00 on the Free plan, or $3.00 with
duration and exact time. With the default 1 GB of memory the start event is charged once.

A Short is charged as a Detailed Short only when its extended details came back. If YouTube
refuses that extra request, the Short keeps every other field and pays the normal price.
Duplicates, error rows, Shorts YouTube refused to return, and Shorts already returned in New
Shorts only mode are free. The Actor stops cleanly at the maximum charge you set in the run
options.

### Input

See the **Input** tab for every option. The ones worth knowing:

- **Max Shorts per source** applies to each channel, hashtag and search separately. **Max
  Shorts in total** caps the whole run.
- **Published on or after** takes a date (`2026-10-01`) or a span (`7 days`, `1 day` meaning
  today). Days follow the US Pacific calendar, like YouTube itself.
- **Search: uploaded within** is YouTube's own filter. Sorting a Shorts search by date or by
  views is not offered: we tested both, the first does not sort and the second returns
  nothing.
- **Mode: New Shorts only** keeps a memory per source in your Apify account. **Memory name**
  lets you keep separate memories for the same channels, or start over.
- **Webhook URL** receives a JSON summary (`runId`, `datasetId`, `mode`, `shorts`, and the
  first 100 `items`) when a run returns at least one Short.

Example, a daily feed of new Shorts from three channels and a hashtag:

```json
{
    "channels": ["@examplechannel", "https://www.youtube.com/@anotherchannel"],
    "hashtags": ["skincare"],
    "maxResultsPerSource": 30,
    "oldestPostDate": "2 days",
    "mode": "new-only",
    "webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX"
}
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
[
    {
        "id": "AbCdEfGhIjK",
        "url": "https://www.youtube.com/shorts/AbCdEfGhIjK",
        "title": "Can you pass this quiz?",
        "description": "#quiz #challenge",
        "hashtags": ["quiz", "challenge"],
        "viewCount": 74027067,
        "likeCount": 1919661,
        "likeCountExact": true,
        "commentCount": 66000,
        "commentCountText": "66K",
        "commentsTurnedOff": null,
        "publishedDate": "2026-08-11",
        "publishedAt": "2026-08-11T16:00:06Z",
        "durationSeconds": 74,
        "duration": "00:01:14",
        "thumbnailUrl": "https://i.ytimg.com/vi/AbCdEfGhIjK/frame0.jpg",
        "category": "Entertainment",
        "isFamilySafe": true,
        "channelId": "UCabcdefghijklmnopqrstuv",
        "channelName": "Example Channel",
        "channelHandle": "examplechannel",
        "channelUrl": "https://www.youtube.com/@examplechannel",
        "channelSubscribers": 1500000,
        "channelSubscribersText": "1.5M subscribers",
        "channelVerified": true,
        "channelAvatarUrl": "https://yt3.ggpht.com/example-avatar=s88-c-k-c0x00ffffff-no-rj",
        "inputType": "channel",
        "input": "@examplechannel",
        "position": 1,
        "scrapedAt": "2026-10-19T15:36:20Z"
    },
    {
        "inputType": "search",
        "input": "a query with no shorts",
        "error": "NO_RESULTS",
        "message": "YouTube returned no Shorts for this source."
    }
]
```

### Tips

- **Monitoring:** schedule the same input with **New Shorts only** and a date span a little
  longer than the schedule interval (`2 days` for a daily run). A run that finds nothing new
  costs $0.002.
- **Cheaper, faster runs:** turn off **Duration and exact publish time** when counts and the
  publish day are enough. 1,000 Shorts from 12 sources then take about 65 seconds.
- **More from a search:** YouTube runs dry after roughly 100 to 200 unique Shorts per query.
  Split a broad query into several narrower ones, or run it in several countries.
- **Memory:** keep the default 1 GB. Half a gigabyte works but runs about 2.5 times slower.
- **From code:** call the Actor through the [Apify API](https://docs.apify.com/api/v2) or the
  Python and JavaScript clients; the **API** tab has ready-to-paste snippets.

### FAQ

#### Why is `likeCount` empty for some Shorts?

The creator hid the like count. YouTube shows "N/A" and the Actor returns `null` rather than a
guess. When YouTube shows only a rounded figure, `likeCount` holds it and `likeCountExact` is
`false`.

#### Why is `channelSubscribers` empty for a channel?

The channel hides its subscriber count, or YouTube does not show it to visitors from the US,
where the Actor runs. We saw it on 1 channel out of 304 in our tests.

#### Why is `commentsTurnedOff` usually null?

YouTube's Shorts page does not say whether comments are off or simply not counted, so the
field is `true` only when YouTube says so explicitly. An empty `commentCount` is common on
channels made for kids, where comments are off.

#### Which time zone are dates in?

`publishedDate` is the day YouTube displays, on the US Pacific calendar. `publishedAt` is the
exact time in UTC, available with extended details.

#### Is it legal to scrape YouTube Shorts?

Our Actors are ethical and do not extract any private user data, such as email addresses,
gender, or location. They only extract what the user has chosen to share publicly. We
therefore believe that our Actors, when used for ethical purposes by Apify users, are safe.
However, you should be aware that your results could contain personal data. Personal data is
protected by the GDPR in the European Union and by other regulations around the world. You
should not scrape personal data unless you have a legitimate reason to do so. If you're unsure
whether your reason is legitimate, consult your lawyers.

#### Something does not work

Open an issue in the **Issues** tab with the run ID. Error rows in the dataset (`error`,
`message`) and the run's status message usually say what went wrong.

# Actor input Schema

## `channels` (type: `array`):

YouTube channels to read Shorts from. One per line, in any form: <code>@MrBeast</code>, <code>MrBeast</code>, <code>https://www.youtube.com/@MrBeast/shorts</code>, <code>https://www.youtube.com/channel/UC...</code>, an old <code>/c/</code> or <code>/user/</code> URL, or the channel ID itself.<br><br>A channel without a Shorts tab, or a handle that does not exist, gets one error row in the dataset (field <code>error</code>), never charged, and the run goes on with the other sources.

## `hashtags` (type: `array`):

Hashtags whose Shorts feed you want, with or without <code>#</code>: <code>cooking</code>, <code>#asmr</code>, or a <code>https://www.youtube.com/hashtag/...</code> link. The feed is the one YouTube shows on <code>youtube.com/hashtag/\<tag>/shorts</code>, ranked by YouTube, not by date.

## `searchQueries` (type: `array`):

Keywords to search, with YouTube's Shorts filter on, so only Shorts come back. One query per line.<br><br>YouTube serves the same Short several times as you scroll a search: on 8 pages we measured 28 to 44 % repeats. The Actor drops them before they reach the dataset, so you are never charged twice. A search also runs dry after roughly 100 to 200 unique Shorts, whatever the maximum you set.

## `shortUrls` (type: `array`):

Individual Shorts to fetch: <code>https://www.youtube.com/shorts/\<id></code>, <code>youtu.be/\<id></code>, <code>watch?v=\<id></code> or the 11-character ID. Useful to refresh the counters of Shorts you already track.

## `maxResultsPerSource` (type: `integer`):

How many Shorts to take from each channel, hashtag or search, counted from the top of its feed.<br><br>In <b>New Shorts only</b> mode it is how deep each run looks: with 50, every run checks the 50 latest Shorts of each channel and returns only those it never returned before.

## `maxItems` (type: `integer`):

Stop the whole run after this many Shorts, all sources together. Leave empty for no total limit. The run's maximum charge, set in the run options, also stops it cleanly.

## `sortChannelShortsBy` (type: `string`):

Which end of a channel's Shorts tab to start from, the three buttons at the top of the tab. Applies to channels only.

## `oldestPostDate` (type: `string`):

Keep only Shorts published on or after this date, or within a span such as <code>7 days</code> (<code>1 day</code> means today). No extra charge for using it: you pay for the Shorts returned, nothing else.<br><br>On a channel read latest first, the Actor stops paging as soon as it reaches older Shorts. The comparison is by day, as YouTube displays it (US Pacific calendar).

## `searchUploadedWithin` (type: `string`):

YouTube's own upload-date filter, applied by YouTube to search queries. Use it with <b>Published on or after</b> for an exact cut-off.<br><br>Not offered on purpose: sorting a Shorts search by upload date or by views. We tested both on 18 October 2026: the first does not sort, the second returns nothing.

## `country` (type: `string`):

Two-letter country code (<code>US</code>, <code>FR</code>, <code>BR</code>, <code>JP</code>...) used for searches and hashtags. YouTube ranks them per country: the same search in the US and in Brazil shares about a third of its first results. Channels are not affected.

## `extendedDetails` (type: `boolean`):

Adds the duration, the exact publish time (UTC, to the second), the category and the family-safe flag. It takes one extra small request per Short.<br><br>A Short that comes with these details is charged as a <b>Detailed Short</b> ($3.00 per 1,000 instead of $2.00 at the entry price). If YouTube refuses that request for a Short, these fields stay empty, every other field is still filled, and the Short is charged at the normal price. Turn it off for a faster, cheaper run when you only need counts and dates.

## `mode` (type: `string`):

<b>All Shorts</b> returns every Short within your limits, on every run.<br><br><b>New Shorts only</b> keeps a memory of the Shorts previous runs returned, per source, and returns only the ones never returned before. Schedule it daily on a list of channels or hashtags and you get a feed of new Shorts: a Short already returned is not fetched again and not charged. The first run returns everything, since nothing has been seen yet. Adding a channel to the list does not reset the memory of the others.

## `registryKey` (type: `string`):

Only used by <b>New Shorts only</b>. Leave empty and each source keeps its own memory, found again by any run that has the same source. Set a name to keep separate memories for the same channels (two clients, two projects), or a new name to start over.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs a JSON summary to this URL at the end of the run, <b>only when at least one Short was returned</b>: <code>runId</code>, <code>datasetId</code>, <code>mode</code>, <code>shorts</code> (count) and <code>items</code>, the first 100 Shorts with ID, URL, title, channel, views, likes and date. With <b>New Shorts only</b> on a schedule, that is a ping to Slack, Zapier, Make or n8n when a tracked channel posts.<br><br>Stored encrypted, since a webhook URL often contains a token. An unreachable webhook is logged and never fails the run.

## `proxyConfiguration` (type: `object`):

Leave this off. Requests go to YouTube directly; if YouTube starts refusing them, the Actor moves on its own to Apify datacenter proxies, and the optional duration lookup always uses a residential proxy. Both are at no cost to you.

## Actor input object example

```json
{
  "channels": [
    "@NASA"
  ],
  "maxResultsPerSource": 10,
  "sortChannelShortsBy": "NEWEST",
  "searchUploadedWithin": "any",
  "country": "US",
  "extendedDetails": true,
  "mode": "all",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with every Short collected in this run

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "channels": [
        "@NASA"
    ],
    "maxResultsPerSource": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexmorain/youtube-shorts-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "channels": ["@NASA"],
    "maxResultsPerSource": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("alexmorain/youtube-shorts-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "channels": [
    "@NASA"
  ],
  "maxResultsPerSource": 10
}' |
apify call alexmorain/youtube-shorts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alexmorain/youtube-shorts-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/YHb8LexZBIgTgvpxJ/builds/kcxrYQibvdy7iP7E0/openapi.json
