# Spotify Scraper (`mlg14/spotify-scraper`) Actor

Extract public Spotify artist, album, track, playlist, show, and episode information from direct URLs, including track lists, artwork, durations, and playback flags.

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

## Pricing

$1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Spotify Scraper

Scrape Spotify artists, albums, tracks, playlists, podcast shows, and episodes from public URLs. Export Spotify data to JSON, CSV, or Excel for catalog research, playlist comparisons, and recurring checks. This Spotify API alternative reads public embed data and does not require an account or developer credentials.

The current actor accepts direct URLs and Spotify URIs. Keyword search is unavailable because the anonymous search page does not expose results and the catalog search endpoint requires authorization. Each supported URL produces one dataset record.

### What data can you extract from Spotify?

The output has consistent keys across entity types. Fields that do not apply are null or empty arrays. This makes it possible to combine music and podcast records without changing columns between runs.

| Field | Description | Example |
| --- | --- | --- |
| `type` | Requested entity type | `track` |
| `id` | Stable Spotify identifier | `5XeFesFbtLpXzIVDNQP22n` |
| `uri` | Spotify URI | `spotify:track:5XeFesFbtLpXzIVDNQP22n` |
| `name` | Entity title or name | `I Wanna Be Yours` |
| `url` | Canonical public URL | Track URL |
| `imageUrl` | Best available image | Artwork URL |
| `artists` | Track artists or album artist | `Arctic Monkeys` |
| `artist` | Album artist | `Arctic Monkeys` |
| `artistIds` | Track artist identifiers | Array of IDs |
| `owner` | Public playlist owner label | `Spotify` |
| `publisher` | Show publisher when available | Currently null |
| `description` | Public description when available | Text or null |
| `releaseDate` | ISO release timestamp | `2013-09-09T00:00:00Z` |
| `duration` | Track or episode length in milliseconds | `183956` |
| `durationFormatted` | Readable length | `3:03` |
| `isExplicit` | Explicit content flag | `false` |
| `isPlayable` | Playback state in request market | `true` |
| `hasVideo` | Video availability | `false` |
| `audioPreviewUrl` | Public audio preview when supplied | URL or null |
| `albumName` | Parent album when supplied | Currently null |
| `albumArt` | Track artwork | Artwork URL |
| `playCount` | Public play count when supplied | Currently null |
| `monthlyListeners` | Artist monthly listeners when supplied | Currently null |
| `followers` | Artist follower count when supplied | Currently null |
| `worldRank` | Artist global rank when supplied | Currently null |
| `biography` | Artist biography when supplied | Currently null |
| `genres` | Genre labels | Empty array in current embed |
| `topTracks` | Tracks exposed by an artist embed | Up to ten objects |
| `relatedArtists` | Related artists when supplied | Currently empty |
| `externalLinks` | Other public links when supplied | Currently empty |
| `trackCount` | Number of tracks visible in embed | `12` |
| `tracks` | Album or playlist track objects | Array of objects |
| `showName` | Episode's parent show | Show title |
| `showId` | Parent show identifier | Spotify ID |
| `showUrl` | Parent show URL | Show URL |
| `scrapedAt` | UTC extraction time | ISO timestamp |

Each object inside `topTracks` or `tracks` can include `trackId`, `title`, `artists`, `duration`, `durationFormatted`, `isExplicit`, `isPlayable`, `audioPreviewUrl`, and `url`. These child values come from the same public embed as their parent. A child track is part of the parent record, not a separate dataset row. Add its URL to the input if you want an individual record.

#### What each URL type returns

An artist URL returns the public profile name, image, and ten displayed top tracks. Child tracks have IDs, titles, artist labels, durations, explicit flags, playback flags, and preview URLs when exposed. The public embed does not include monthly listeners, follower counts, rank, biography, genres, or related artists. Those values remain empty rather than being estimated.

An album URL returns its title, artist label, artwork, and the track list exposed by its embed. The child track URLs can seed a later run. A track URL returns its song title, artist IDs, release timestamp, duration, explicit flag, playback state, preview link, and artwork. The track embed does not name its parent album even when it shows album artwork.

A playlist URL returns its title, owner label, artwork, available description, and embedded track list. The tested editorial playlist exposed 50 tracks. The actor reports the visible count; it does not claim that a visible slice equals the full playlist if more tracks exist. Playlist children can be compared across dated snapshots.

A show URL currently exposes the show's name and artwork through an embed centered on a recent episode. The episode timestamp and playback flag are not attributed to the show. An episode URL returns the episode title, parent show name and URL, release time, duration, explicit and video flags, playback state, and any preview. Current public embeds do not provide a reliable show publisher or long description.

### How to scrape Spotify

1. Copy a public artist, album, track, playlist, show, or episode URL. A Spotify URI for the same entity also works.
2. Paste one or more links into **Spotify URLs**. Different types can appear in the same run.
3. Set **Maximum records** to cap the number of unique parent records. The default is 50.
4. Start the actor and inspect the dataset. Each accepted URL creates one parent record.
5. Download JSON, CSV, or Excel. JSON retains nested track arrays most directly.

The actor recognizes standard `open.spotify.com` links, localized `intl-xx` paths, and `spotify:type:id` URIs. Query strings on share links are ignored when identifying the item. Duplicate type and ID pairs are emitted once. Short redirect links are not accepted; open one and copy the resulting full public URL.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | String | `urls` | Direct URL mode. Keyword search is currently unavailable. |
| `urls` | String array | Required | Public entity URLs or Spotify URIs. |
| `maxResults` | Integer | `50` | Maximum unique parent records, from 1 to 1,000. |
| `proxyConfiguration` | Object | Automatic | Proxy settings for public page requests. |

A small mixed run:

```json
{
  "mode": "urls",
  "urls": [
    "https://open.spotify.com/artist/06HL4z0CvFAxyc27GXpf02",
    "https://open.spotify.com/album/78bpIziExqiI9qztvNFlQu",
    "https://open.spotify.com/track/5XeFesFbtLpXzIVDNQP22n",
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
    "https://open.spotify.com/show/2MAi0BvDc6GTFvKFPXnkCL",
    "https://open.spotify.com/episode/45lhw2Adbrsw0xSCOgIeg3"
  ],
  "maxResults": 6
}
```

For a long input list, place the most important URLs first. Processing follows input order and stops at the maximum or the run's charge limit. The cap applies to parent records, not the number of tracks nested inside an artist, album, or playlist record. A single album can therefore contribute one dataset row and many child tracks.

### Output example

This track record came from the 30-item golden run on 26 September 2026. The excerpt omits empty fields for readability; the actual dataset record has the stable schema above.

```json
{
  "type": "track",
  "id": "5XeFesFbtLpXzIVDNQP22n",
  "uri": "spotify:track:5XeFesFbtLpXzIVDNQP22n",
  "name": "I Wanna Be Yours",
  "url": "https://open.spotify.com/track/5XeFesFbtLpXzIVDNQP22n",
  "artists": "Arctic Monkeys",
  "artistIds": ["7Ln80lUS6He07XvHI8qqHH"],
  "releaseDate": "2013-09-09T00:00:00Z",
  "duration": 183956,
  "durationFormatted": "3:03",
  "isExplicit": false,
  "isPlayable": true,
  "hasVideo": false,
  "audioPreviewUrl": "https://p.scdn.co/mp3-preview/07de30bde8363d9ff78b72339245c151f67af451",
  "albumArt": "https://image-cdn-ak.spotifycdn.com/image/ab67616d0000b2734ae1c4c5c45aabe565499163",
  "scrapedAt": "2026-09-26T12:19:14.836430Z"
}
```

The golden input had an artist, album, track, playlist, show, episode, and 24 additional tracks taken from that public playlist. It returned 30 records. Every record had a type, ID, name, URL, and image; playback flags applied to 29 of 30 records, with the show flag left empty because its embed represents an episode. This verifies those public URLs at the time of the run; it does not guarantee that all optional fields appear for every catalog item or region.

### Use cases

- **Playlist comparison:** Save the visible track list daily and compare `trackId` values and order to spot additions, removals, and rotations.
- **Catalog checks:** Run a stored list of track URLs on a schedule. Track public playability, explicit flags, preview availability, and artwork changes.
- **Release inventory:** Collect an album's visible tracks. Follow child URLs in a second run when individual track timestamps or artist identifiers are needed.
- **Podcast indexing:** Store episode titles, parent show links, lengths, release timestamps, and video flags in a searchable internal catalog.
- **Artist pages:** Collect public profile images and the top tracks shown in an embed. Refresh periodically to see changes in the displayed selection.
- **Data joins:** Use `id` and `uri` to join exports to an existing music or podcast database without relying on mutable titles.

### How much does it cost to scrape Spotify?

The current result-event price is **$0.001 per parent record**, or **$1 per 1,000 results**. Nested tracks inside an artist, album, or playlist record are part of that parent item. Platform usage and proxy charges may apply separately. A private 30-result verification run showed $0.005 in owner-side usage; that figure is not a quote for another account's total charge.

| Scenario | Parent records | Result-event calculation |
| --- | ---: | ---: |
| One artist and one playlist | 2 | $0.002 |
| Thirty mixed URLs | 30 | $0.030 |
| One thousand direct URLs | 1,000 | $1.000 |

These examples use the configured item price and exclude separate usage or proxy fees. The actor checks the run's charge limit when it pushes each record. `maxResults` provides another cap. The output count can be below the input count if URLs repeat, fail, are unavailable, or fall beyond the cap. Track arrays within a row do not increase the parent record count.

### Tips for best results

Use the full public URL copied from an entity page. The type appears between the host and the 22-character ID. If you already store Spotify URIs, submit them directly. Keep one intended entity per line. For a short share link, open it and copy the resulting `open.spotify.com` URL.

For track-level exports, submit track URLs. Album and playlist URLs return one row with a nested `tracks` array, which is useful for comparing the collection as a whole. When a spreadsheet needs one row per track, extract the child URLs and run them as individual inputs. The child objects provide a seed list, while direct track records expose more individual metadata.

Put valuable URLs first. A run can stop at `maxResults`, at a charge limit, or after failures. Duplicate URLs do not consume output slots. Split large catalogs into manageable batches so that one unavailable item does not obscure which inputs were successful. Keep a copy of the input list next to the resulting dataset for later audits.

Use IDs rather than titles for comparisons. Titles, images, owner labels, and displayed track order can change. A canonical ID is a better join key for historical snapshots. Keep `scrapedAt` when comparing runs because availability and public presentation are time dependent. `isPlayable` reflects the market seen by that request, not universal availability.

### Limits

Keyword discovery is currently unavailable. The anonymous search page loads a generic player shell without results. The anonymous token route returned 403, and direct catalog query requests returned 401 in testing. The actor accepts URL mode only rather than returning incomplete search data.

Public embeds omit popularity fields available elsewhere on the site. In the current response, `monthlyListeners`, `followers`, `worldRank`, `playCount`, `biography`, `genres`, `relatedArtists`, and `externalLinks` cannot be filled reliably. The keys remain in the schema for a stable export shape, with null or empty values. Do not use this actor for listener or play-count monitoring at present.

Embeds expose only a slice of some collections. Artist embeds showed ten tracks; the tested playlist embed showed 50. `trackCount` means the number exposed in the embed, not necessarily the catalog total. Album and playlist release dates may be absent. Show embeds may center on a recent episode without show-level publisher or description. Preview URLs are optional and may change.

Availability depends on Spotify's public service and the request route. The actor starts with a datacenter proxy and rotates to a residential route after repeated blocks. Some URLs can still be removed, private, region restricted, or temporarily inaccessible. Failed URLs are logged and processing continues. If every URL fails, the run ends with an error instead of an empty success.

### Automated workflows

A recurring playlist task can use a fixed URL, download each run's JSON, and compare child `trackId` values. A catalog task can take stored track URIs and record changes in playability or artwork. Preserve the run date: every export is a snapshot of what the public embed exposed at that moment.

Example requests for a connected workflow:

> Collect the visible tracks from these three playlists and report which track IDs differ from yesterday's snapshot.

> Fetch these episode URLs and return release date, duration, video flag, and parent show URL for each.

The actor supplies the structured records. A downstream scheduler, database, or notification flow can consume the default dataset. Maintain the original URL list with the output so missing records can be distinguished from genuine catalog changes.

### FAQ

#### Is this legal to use?

The actor reads public pages available without an account. Review Spotify's terms and applicable law for your intended use. Respect privacy rules and avoid personal-data misuse. It does not access private playlists, account data, or audio files.

#### Do I need an account or developer credentials?

No credentials are needed for supported direct URLs. The absence of credentials is also why keyword search and authorization-gated catalog fields are unavailable.

#### Do I need proxies?

The actor uses its configured proxy by default. It starts with a datacenter route and escalates only after repeated blocks. Availability varies by region and time; inspect the run log when diagnosing a failed URL.

#### How fast is a run?

The 30-item verification run completed in about 42 seconds after it started. Build and queue time are separate. Actual speed depends on input mix, target responses, retries, and platform capacity.

#### Can I schedule runs to monitor changes?

Yes. Run the same input on a schedule and compare records by `type` and `id`. Retain `scrapedAt` and the dataset from each run to distinguish a real change from a temporary fetch failure.

#### Can I export to a spreadsheet?

Yes. CSV and Excel exports work for top-level fields. JSON is clearest for nested `topTracks` and `tracks` arrays. To make a row per child track, transform those arrays or submit their URLs to another run.

#### Why are some fields empty?

The public embed does not supply every field for every entity. Some fields are type-specific, while others require authorization or are unavailable in a market. Empty fields are null or empty arrays rather than inferred from unrelated pages.

### Integrations

Read the default dataset through the platform API or download it after a run. Scheduled runs, webhooks, spreadsheet imports, and workflow services can use the same records. Use stable IDs for joins, inspect logs for individual URL failures, and retain dated datasets when building a history.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

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

Use direct URL scraping. Keyword search is temporarily unavailable because the public catalog endpoint rejects anonymous requests.

## `urls` (type: `array`):

Public artist, album, track, playlist, show, or episode URLs or Spotify URIs. Each URL produces one record.

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

Maximum number of URL records emitted in this run.

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

Proxy settings for fetching public pages. Automatic proxy rotation is used by default.

## Actor input object example

```json
{
  "mode": "urls",
  "urls": [
    "https://open.spotify.com/artist/06HL4z0CvFAxyc27GXpf02",
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
  ],
  "maxResults": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

Structured records in the default dataset.

# 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 = {
    "mode": "urls",
    "urls": [
        "https://open.spotify.com/artist/06HL4z0CvFAxyc27GXpf02",
        "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
    ],
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/spotify-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 = {
    "mode": "urls",
    "urls": [
        "https://open.spotify.com/artist/06HL4z0CvFAxyc27GXpf02",
        "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
    ],
    "maxResults": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/spotify-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 '{
  "mode": "urls",
  "urls": [
    "https://open.spotify.com/artist/06HL4z0CvFAxyc27GXpf02",
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
  ],
  "maxResults": 50
}' |
apify call mlg14/spotify-scraper --silent --output-dataset

```

## MCP server setup

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