# YouTube Playlist Scraper - Videos, Views & Order, No API Key (`neverempty/youtube-playlist-scraper`) Actor

For playlist exports, new-upload alerts and course or podcast tracking: YouTube playlist videos as rows with position, title, URL, channel, length, views and age, plus the playlist's title, owner and total views. First 100 videos. No API key or quota. Monitoring returns only new videos.

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

## Pricing

from $5.84 / 1,000 playlist video row returneds

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

## YouTube Playlist Scraper - Videos, Views & Order, No API Key

Paste YouTube playlist URLs and get **every video on the playlist's page in playlist order**: position, video id and URL, title, channel, length, view count and how long ago it was published - plus, on every row, **the playlist's title, owner channel, video count and total views**. No YouTube Data API key, no quota, no sign-in.

Turn on **monitoring mode**, schedule the Actor, and a run returns **only the videos that were added since the last run** - handy for a channel's uploads playlist (`UU...`), a course, a podcast or a curated music list.

**Coverage, stated plainly:** the playlist page YouTube serves without signing in lists **at most the first 100 videos** (measured on 2026-09-21: a 183-video playlist and a 1,848-video uploads playlist both showed exactly 100). YouTube loads the rest through its internal API (`/youtubei/`), which YouTube's robots.txt disallows, so this Actor does not read it. For a longer playlist you get the first 100 videos, and a free `first-page-only` row tells you how many YouTube lists in total.

### What you can use it for

- **Playlist exports**: turn a playlist into a spreadsheet with titles, channels, lengths and views, in the order the playlist shows them.
- **New-upload alerts**: monitor a channel's uploads playlist (`UU` + the channel id without `UC`) and get a row for each new video.
- **Course and podcast tracking**: see when a lesson or an episode is added to a playlist you follow.
- **Music and curation research**: see which videos and channels a curated playlist features, with their view counts.

### How it works

1. Paste playlist URLs (`https://www.youtube.com/playlist?list=PL...`), watch URLs that contain `&list=`, or bare playlist ids (`PL...`, `UU...`, `OLAK5uy_...`).
2. For each playlist the Actor opens the public playlist page (`https://www.youtube.com/playlist?list=<id>&hl=en&gl=US`) and checks that the page is **the playlist you asked for** (its canonical playlist id). A video is only counted if YouTube links it to that playlist.
3. It reads the playlist header (title, owner, video count, views) and every video card on the page, in order. The position is YouTube's own index for the card, the same number you see in a browser (unavailable videos YouTube hides are not counted).
4. Private, deleted and unviewable playlists come back as a free row with YouTube's own message.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `playlists` | example playlist | Playlist URLs, watch URLs with `&list=`, or playlist ids, one per line (commas also separate entries, and so do spaces between entries that are all playlists). A repeated playlist is read and charged once; the repeat gets a free `duplicate` row. Up to 500 per run. |
| `maxVideosPerPlaylist` | `100` | With monitoring off, return at most this many videos per playlist, from position 1. 100 is the most the page shows. |
| `maxResults` | `1000` | With monitoring off, stop after this many video rows in total; a free row says what was not returned. In monitoring mode it does not cut new videos. |
| `monitoringMode` | `false` | Remember which videos were returned for each playlist and return only videos not returned before. |
| `resetMonitoringState` | `false` | Forget the remembered videos of the playlists in this run's list and start a fresh baseline. Turn it off again after one run. |
| `useProxy` | `true` | Retry a page through a proxy (datacenter first, then residential) only if YouTube answers it with a bot check or an error. |

```json
{
    "playlists": [
        "https://www.youtube.com/playlist?list=PLBCF2DAC6FFB574DE",
        "UUBJycsmduvYEL83R_U4JriQ"
    ],
    "monitoringMode": false
}
```

If you leave `playlists` out of the input with monitoring off, the example playlist above is read (its URL appears in `inputPlaylist` and the run log says so). In monitoring mode `playlists` is required, so a schedule never pays to watch the example. An empty list is rejected with a free `invalid-input` row.

### Output

One row per video. This row was read on 2026-09-21 (UTC) from the example playlist (13 videos listed by YouTube, 10 shown, 3 hidden by YouTube as unavailable):

```json
{
    "source": "youtube-playlist",
    "status": "ok",
    "inputPlaylist": "PLBCF2DAC6FFB574DE",
    "playlistId": "PLBCF2DAC6FFB574DE",
    "playlistUrl": "https://www.youtube.com/playlist?list=PLBCF2DAC6FFB574DE",
    "playlistTitle": "Google Search Stories",
    "playlistOwnerName": "Google Search Stories",
    "playlistOwnerChannelId": "UCvceBgMIpKb4zK1ss-Sh90w",
    "playlistOwnerUrl": "https://www.youtube.com/@SearchStories",
    "playlistVideoCount": 13,
    "playlistViewCount": 102161,
    "hiddenUnavailableVideos": 3,
    "videosOnFirstPage": 10,
    "onlyFirstPageReturned": false,
    "position": 1,
    "videoId": "GvgqDSnpRQM",
    "videoUrl": "https://www.youtube.com/watch?v=GvgqDSnpRQM",
    "title": "Andrew Willis, Skatepark Engineer",
    "channelName": "Google Search Stories",
    "channelId": "UCvceBgMIpKb4zK1ss-Sh90w",
    "channelUrl": "https://www.youtube.com/@SearchStories",
    "durationText": "1:30",
    "lengthSeconds": 90,
    "viewCountText": "2.6M views",
    "viewCount": 2600000,
    "viewCountIsRounded": true,
    "publishedTimeText": "12 years ago",
    "badges": [],
    "thumbnailUrl": "https://i.ytimg.com/vi/GvgqDSnpRQM/hqdefault.jpg?...",
    "playlistDescription": "Searches can become stories. Some are inspiring, some change the way we see the world, and some just make us laugh. Here are a few of our favorites.",
    "newSinceLastRun": null,
    "scrapedAt": "2026-09-21T05:37:56.052Z"
}
```

What the columns mean, and their limits:

- **`position`** is the video's place in the playlist as YouTube shows it (1 = first). Videos YouTube hides as unavailable are not counted, so positions run 1, 2, 3... over the visible videos.
- **`viewCount`** on a video is **rounded by YouTube** on the playlist page (`761M views`, `2.6M views`); `viewCountIsRounded` says so, and `viewCountText` keeps YouTube's text. The playlist's **`playlistViewCount`** is shown in full by YouTube and is not rounded. YouTube sometimes serves a compact version of the page with no view counts and short ages (`2y ago`); seen on 2 of 28 page loads we checked on 2026-09-21. When more than half of the videos come without a view count, the Actor reads the page once more and keeps the version with more view counts; if both lack them, `viewCount` is `null` (never 0) and the run log says so.
- **`publishedTimeText`** is YouTube's relative text (`2 years ago`); the playlist page has no exact date.
- **`channelName` / `channelId`**: a video made by several channels shows as `Marques Brownlee and Auto Focus` with no single channel link, so `channelId` is `null` there rather than guessed.
- **`playlistVideoCount`** is YouTube's count, which includes hidden unavailable videos; `hiddenUnavailableVideos` is YouTube's own "N unavailable videos are hidden" number.
- **`onlyFirstPageReturned`** is `true` when the playlist has more videos than its page shows (more than 100).
- **`newSinceLastRun`**: in monitoring mode `true` for a video added since an earlier run, `false` on the first monitoring run of a playlist (the baseline); `null` with monitoring off.

#### Free rows

These rows have a `note` explaining why, and are never charged:

| `status` | When |
| --- | --- |
| `playlist-unavailable` | YouTube did not show the playlist; `youtubeMessage` has YouTube's own words (for example "The playlist does not exist." or "This playlist type is unviewable." for mixes). Private playlists are reported the same way. |
| `no-visible-videos` | The playlist exists but its page shows no video (for example all of them are unavailable). |
| `first-page-only` | The playlist has more videos than its page shows; the note says how many YouTube lists. With monitoring on, it is written only on a playlist's first monitoring run. |
| `different-playlist-returned` | YouTube answered with another playlist than the one asked for. |
| `blocked` / `unreadable` | Bot check, consent page, error or no answer after retries. Nothing is guessed. |
| `invalid-input` / `duplicate` | Not a playlist URL or id (personal lists like Watch later and Liked videos included), or a repeat in the list. |
| `no-new-videos` | Monitoring mode: no playlist checked had a video that was not returned before. |
| `budget-reached` / `not-checked` | The run's maximum total charge, `maxResults` or the 500-playlist limit stopped the run; the note says what was left out. |

### Monitoring mode

- The first monitoring run of a playlist returns every video on its page once, as the baseline (`newSinceLastRun: false`), charged at the row price. If the run's maximum total charge stops the baseline part-way, the next run finishes it and those rows are still marked `newSinceLastRun: false`. After the baseline, runs return only videos that were not returned before for that playlist (`newSinceLastRun: true`).
- Only videos that were actually delivered are remembered. If the run's maximum total charge stops delivery part-way, the rest are not remembered and come back on the next run.
- **Only the first page is visible.** Playlists that show the newest video first (channel uploads `UU...`, most news and podcast lists) work well. A playlist that adds new videos **at the end** and already shows 100 videos will not reveal them.
- A video removed from a playlist and added back is not returned again.
- Videos are remembered per playlist, in a store of your Apify account. Do not put the same playlist in two schedules that can run at the same time: Apify's key-value store has no atomic update, so two overlapping runs can overwrite each other's records (the Actor re-reads and merges before writing, which narrows but does not remove this).

### Pricing

- **$8 per 1,000 video rows** (`playlist-video-returned`, $0.008 per row returned) on Apify's free plan, less on paid plans (down to $5.84 per 1,000).
- **Monitoring mode only: $0.30 per 1,000 playlist checks** (`playlist-checked`), charged for every playlist that is read, whether it has new videos or not. Example: 20 playlists every hour = 14,400 checks a month = $4.32, plus $0.008 per new video returned. **The first monitoring run of a playlist returns every video on its page (up to 100) as the baseline, at the row price**: up to $0.80 per playlist once, so 20 channel-uploads playlists cost up to $16 on the first run.
- Free rows (unavailable playlists, bot checks, invalid input, notes) are never charged.
- The Actor respects the run's **maximum total charge**: it stops before a row that would go over it, and in monitoring mode it only reads a playlist if the check and at least one video row still fit. A free `budget-reached` row says what was left out.

### Notes

- Only public YouTube pages are read, through paths YouTube's robots.txt allows (`/playlist`). No sign-in, no cookies, no API key.
- Titles and descriptions are returned as YouTube shows them; email addresses and phone numbers inside them are replaced with `[email removed]` / `[phone removed]`.
- YouTube can change its page at any time. When a page cannot be understood, the Actor returns a free `unreadable` row instead of an empty or guessed result.

# Actor input Schema

## `playlists` (type: `array`):

Playlists to read: https://www.youtube.com/playlist?list=PL... (with or without the scheme), a watch URL that contains \&list=, or the playlist id itself (PL..., UU... for a channel's uploads, OLAK5uy\_... for an album). One per line; commas and spaces also separate entries. A repeated playlist is read and charged once and the repeat gets a free 'duplicate' row. Private playlists, mixes (RD...) and personal lists (Watch later, Liked videos) cannot be read and come back as free rows. If you leave this field out with monitoring off, one example playlist is read; with monitoring on it is required. An empty list is rejected. Up to 500 playlists per run.

## `maxVideosPerPlaylist` (type: `integer`):

With monitoring off, at most this many videos are returned from each playlist, starting from position 1. The page YouTube serves without signing in shows at most the first 100 videos of a playlist, so 100 is the highest useful value. In monitoring mode it does not apply: every new video on the first page is returned.

## `maxResults` (type: `integer`):

With monitoring off, reading stops once this many charged video rows have been returned across all playlists, and a free row says how many videos and playlists were not returned because of it. In monitoring mode it does not limit the new videos returned: every playlist in the list is checked, as far as the run's maximum total charge allows.

## `monitoringMode` (type: `boolean`):

Off = every run returns the videos on each playlist's first page, charged per row. On = the Actor remembers which videos it returned for each playlist and, on later runs, returns only videos on the first page that it has not returned before for that playlist. The first run returns every video on the first page once to set the baseline. Only the first page (up to 100 videos) is visible: a video added at the end of a playlist that already shows 100 videos is not seen. **In monitoring mode every playlist that is read costs $0.30 per 1,000 checks, new videos or not** (unavailable playlists, bot checks and invalid input are free), plus the row price for the new videos returned. Example: 20 playlists every hour = 14,400 checks a month = $4.32. The first run of each playlist also pays the row price for its baseline (up to 100 videos = up to $0.80 per playlist, once). The Actor reads only as many playlists as the run's maximum total charge can pay for with a check and one video row each. Videos are remembered per playlist; do not put the same playlist in two schedules that can run at the same time.

## `resetMonitoringState` (type: `boolean`):

Clears the remembered videos of every playlist in this run's list, so this run (in monitoring mode) returns every video on their first page again as a first check. Playlists that are not in the list keep what was remembered for them. Turn it off again after one run: left on in a schedule, every run returns every video and charges the row price for it.

## `useProxy` (type: `boolean`):

Every page is requested directly first. Only if YouTube answers a page with a bot check, an error or no response is that page retried through a proxy session (datacenter first, then residential); the next page is requested directly again. With it off, that answer is retried directly and then returned as a free 'blocked' row, and no proxy is paid for.

## Actor input object example

```json
{
  "playlists": [
    "https://www.youtube.com/playlist?list=PLBCF2DAC6FFB574DE"
  ],
  "maxVideosPerPlaylist": 100,
  "maxResults": 1000,
  "monitoringMode": false,
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

## `results` (type: `string`):

One row per video on the first page of each playlist (up to 100): the video's position in the playlist, id and URL, title, channel name and id, length, view count as YouTube shows it (rounded, e.g. 761M views) and how long ago it was published, the thumbnail, and on every row the playlist's title, owner channel, video count and total views. In monitoring mode only videos that were not returned before for that playlist. Unavailable or private playlists, bot checks, invalid input and videos beyond the first page come back as free rows that say why.

# 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 = {
    "playlists": [
        "https://www.youtube.com/playlist?list=PLBCF2DAC6FFB574DE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/youtube-playlist-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 = { "playlists": ["https://www.youtube.com/playlist?list=PLBCF2DAC6FFB574DE"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/youtube-playlist-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 '{
  "playlists": [
    "https://www.youtube.com/playlist?list=PLBCF2DAC6FFB574DE"
  ]
}' |
apify call neverempty/youtube-playlist-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/youtube-playlist-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/xr4qmj2AFopF2ghBi/builds/6K06LhMOoqXFDCP4b/openapi.json
