# YouTube Playlist Scraper — Videos, Views & Changes (`northbell/youtube-playlist-scraper`) Actor

Scrape YouTube playlists: every video with title, channel, length, views and age, plus the playlist's own title, owner, video count and last update. Run it again or on a schedule to see which videos were added, removed or moved since the last run. Links or IDs, up to 5,000 videos each. No login.

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

## Pricing

from $0.40 / 1,000 videos

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 & Changes

Give it a list of YouTube playlists — `youtube.com/playlist?list=…` links, any link that contains `list=…`, or playlist IDs — and get **every video in playlist order: position, title, channel, length, views and age**, plus one summary row per playlist (owner, video count, total views, last update). **$0.50 per 1,000 videos**, no login, no API key.

Run it again — or put it on a daily schedule — and the rows also tell you **what changed since the last run**: which videos are new (`isNewSinceLastRun`), which were removed (`removedSinceLastRun` on the summary row) and how far each video moved up or down (`positionChange`). YouTube only shows you today's list; this keeps the previous one for you.

### What you get

One free `playlist` summary row per playlist, then one `playlist-video` row per video.

| Field | What it is |
|---|---|
| `playlistId`, `url`, `title`, `input` | the playlist, and the link or ID you gave |
| `ownerChannelName`, `ownerChannelId`, `ownerChannelHandle` | the channel that owns the playlist (`ownerChannelHandle` is e.g. `@freecodecamp`) |
| `videoCount` | the number of videos YouTube shows on the playlist page (`2,331 videos` → `2331`) |
| `views`, `viewsText` | on the summary row: the playlist's own view count as YouTube shows it |
| `lastUpdatedText` | YouTube's wording: `Last updated on Apr 17, 2023` or `Updated today` |
| `videosRead`, `complete` | how many videos were read, and whether that is the whole playlist (`false` when the run stopped at *Max videos per playlist*, when YouTube stopped answering, or when YouTube returned fewer videos than the playlist page counts) |
| `hiddenUnavailableVideos` | how many videos YouTube hides from the list because they are private or deleted (`0` when none) |
| `addedSinceLastRun`, `removedSinceLastRun`, `previousRunAt` | on the summary row: videos that are new since your previous run (`videoId`, `title`, `position`) and videos that are gone (`videoId`, `title`); `null` on the first run |
| `playlistId`, `playlistTitle`, `position` | on a video row: its playlist and its place in it (1 = first) |
| `videoId`, `url`, `title` | the video |
| `channelName`, `channelId`, `channelHandle` | the channel that published the video (gaps are filled from the playlist's owner and the other rows of the same channel) |
| `duration`, `durationSeconds` | length as shown (`3:26:43`) and in seconds |
| `views`, `viewsText` | on a video row: views as the playlist shows them — **rounded by YouTube** above 999 (`1.4M views` → `1400000`) |
| `publishedAgo` | YouTube's relative wording (`4 years ago`) |
| `membersOnly` | `true` for a members-only video (YouTube shows no view count for these) |
| `isNewSinceLastRun`, `previousPosition`, `positionChange` | on a video row: `true` if the video was not in the previous run's list; where it was; and `previousPosition − position`, so **positive = moved up** (`null` on the first run) |
| `observedAt`, `type` | when it was read, and `playlist`, `playlist-video` or `error` |

### Use it for

- **Catalog and course exports** — every video of a course, series or channel list with length, views and age in one table, ready for Sheets or your BI tool
- **Monitoring new uploads** — schedule it daily on a competitor's playlist or a channel's Uploads list and filter `isNewSinceLastRun = true` to get just the new videos
- **Playlist upkeep** — `removedSinceLastRun` and `hiddenUnavailableVideos` show videos that were taken out, deleted or made private
- **Feeding other tools** — pass the `videoId`s to a video-level scraper for exact views and likes

### Input

```json
{ "playlists": ["https://www.youtube.com/playlist?list=PLWKjhJtqVAbleDe3_ZA8h3AO2rXar-q2V", "PLBCF2DAC6FFB574DE"], "maxVideosPerPlaylist": 200 }
```

The previous list of each playlist is remembered in a named key-value store in your Apify account, so the second run of the same Actor already shows changes. Schedule it (Apify → Schedules) for a history. Up to 200 playlists per run and 5,000 videos per playlist; a long playlist takes one request per 100 videos.

To list **all uploads of a channel**, use its Uploads playlist: take the channel ID (`UCxxxxxxxx…`) and change the first two letters to `UU` (`UUxxxxxxxx…`). `UULF…` lists only its long videos, `UULV…` only its live streams and `UUSH…` only its Shorts (title and views only, see Notes).

### Pricing

**$0.50 per 1,000 videos**, plus $0.01 per run. The playlist summary rows and the error rows (private, deleted or mistyped playlists) are free. The $0.01 run-start fee is the one charge every run pays, even one that returns nothing.

### Notes

- Public data only: what anyone sees on a playlist page without signing in. Video descriptions and comments are not read, and email addresses are removed from titles and channel names.
- **Private playlists, Liked videos, Watch later and YouTube Mixes (IDs starting with `RD`) cannot be read** and come back as a free error row.
- YouTube hides private and deleted videos from a playlist, so `videosRead` can be lower than `videoCount`; `hiddenUnavailableVideos` says how many are hidden. `position` counts the videos that are listed (1 = first), so it has no gaps.
- `views` is empty when YouTube shows no view count for the video, which is what it does for members-only videos (`membersOnly: true`). `duration` and `durationSeconds` are empty when YouTube shows no length on the thumbnail. `channelHandle` is empty when YouTube does not show the channel's `@handle` anywhere in the playlist page or no other row of that channel has it.
- **A channel's Shorts list (`UUSH…`) is laid out differently by YouTube**: YouTube returns at most the first 100 of them, so `complete` is `false` when the list is longer, and each row has only `videoId`, `title` and `views`. `channelName`, `channelId`, `channelHandle`, `duration` and `publishedAgo` are empty there (the owner is on the summary row).
- A video made by several channels together shows them as "Collaborators" on YouTube. The channel fields describe the first one listed, which is the channel that uploaded it.
- `views` on the summary row is the number YouTube shows for the playlist page itself. For a channel's Uploads list that is not the channel's total views.
- **Changes since the last run:** a video is new (`isNewSinceLastRun: true`) when it was not in the list the previous run read. `removedSinceLastRun` is only filled when this run read the whole playlist (`complete: true`), because videos below the limit may still be there. When the previous run stopped at the limit, videos deeper than it read get `isNewSinceLastRun: null` instead of a guess; within that depth, a video that was moved up from below the previous limit looks new. Titles in `removedSinceLastRun` are the first 120 characters. Raise *Max videos per playlist* to read everything.
- If YouTube shows videos on a playlist page but none can be read from it (for example after a layout change), you get a free error row instead of an empty "complete" list.
- If YouTube stops answering in the middle of a long playlist, you keep the videos already read, get a free error row saying where it stopped, and the stored list from the previous run stays as it was.
- If YouTube stops answering or asks for a sign-in several times in a row, the Actor stops waiting and returns the remaining playlists as free error rows; run it again later.

To get **exact views and likes** for single videos, see [YouTube Video Stats Tracker](https://apify.com/northbell/youtube-video-stats-tracker). To track a **whole channel** (subscribers, total views, upload rhythm), see [YouTube Channel Stats & Growth Tracker](https://apify.com/northbell/youtube-channel-growth-tracker).

### For AI agents

`{"playlists": ["PLWKjhJtqVAbleDe3_ZA8h3AO2rXar-q2V"], "maxVideosPerPlaylist": 50}` returns one `playlist` row (title, owner, `videoCount`) followed by one `playlist-video` row per video with `position`, `title`, `channelName`, `duration` and `views`. Rows have `type`: `playlist`, `playlist-video` or `error`. Pass several playlists in one call to pay the run fee once.

# Actor input Schema

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

One playlist per line: a youtube.com/playlist?list=… link, any link that contains list=… (a video watched inside a playlist works too), or the playlist ID (starts with PL, or UU for a channel's uploads). Up to 200 per run. Private playlists and YouTube Mixes (RD…) cannot be read.

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

Read at most this many videos from each playlist, from the top. YouTube returns 100 at a time, so a long playlist needs more requests. Changes since the last run (removed videos in particular) are only complete when the whole playlist was read.

## Actor input object example

```json
{
  "playlists": [
    "https://www.youtube.com/playlist?list=PLWKjhJtqVAbleDe3_ZA8h3AO2rXar-q2V"
  ],
  "maxVideosPerPlaylist": 200
}
```

# Actor output Schema

## `playlists` (type: `string`):

Playlist summaries and every video with its position, channel, length, views and age, plus what changed since the last 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 = {
    "playlists": [
        "https://www.youtube.com/playlist?list=PLWKjhJtqVAbleDe3_ZA8h3AO2rXar-q2V"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/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=PLWKjhJtqVAbleDe3_ZA8h3AO2rXar-q2V"] }

# Run the Actor and wait for it to finish
run = client.actor("northbell/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=PLWKjhJtqVAbleDe3_ZA8h3AO2rXar-q2V"
  ]
}' |
apify call northbell/youtube-playlist-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,northbell/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/18ca6iUheObzQaoc7/builds/TEZbcV1SMLYC2hfyk/openapi.json
