# Spotify Artist Scraper - Listeners & Followers (`usestring/spotify-artists`) Actor

Look up Spotify artists by ID, URI or open.spotify.com URL and get exact numbers: monthly listeners and follower count as integers rather than the rounded 100.6M the page prints, plus verified status, artist image and the top-tracks list with a play count on every track. No API credentials.

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

## Pricing

from $2.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.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### What does the Spotify Artist Scraper do?

Give it a list of Spotify artists and it returns one row each, with the audience figures as exact
integers. `monthlyListeners` comes back as `100593317`, not as the "100.6M" the artist page renders.
`followerCount` is the same: a whole number, straight out of Spotify's own response. Every top track
carries its play count as an integer too.

That distinction matters the moment you start tracking a number over time. A rounded display figure
moves in steps of a hundred thousand, which means a week of real growth can look like no change at
all and then jump. Exact integers give you a real series to difference.

You supply the artists; the Actor does not do discovery. IDs, `spotify:artist:` URIs and
`open.spotify.com` artist URLs all work and all collapse to the same fetch, so a mixed list is never
billed twice for one artist.

- No Spotify account, developer app, client secret or Web API quota is involved
- Up to 200 artists per run, fetched in parallel
- `topTracks` gives you the artist page's track list with an ID, a name and an exact play count each
- An ID Spotify does not recognise is reported as a failure rather than written out as an all-null
  row that counts as a success
- The token and query the run needs are fetched once, not once per artist, so the overhead amortises
  across a large roster

### What data does it extract?

| Field | Type | Description |
| --- | --- | --- |
| `artistId` | string | Spotify's 22-character artist ID |
| `name` | string | Artist name as Spotify publishes it |
| `monthlyListeners` | number | Exact integer, e.g. `100593317` |
| `followerCount` | number | Exact integer, e.g. `54812903` |
| `isVerified` | boolean | Whether Spotify shows the artist as verified |
| `topTracks` | object\[] | `{ trackId, name, playCount }` per track, `playCount` an exact integer |
| `imageUrl` | string | Artist avatar, the widest of the sizes Spotify ships |
| `artistUrl` | string | `https://open.spotify.com/artist/<artistId>` |
| `sourceUrl` | string | The query URL this row was read from |
| `collectedAt` | string | ISO timestamp, set once per run |
| `popularity` | null | Always empty. See below |
| `genres` | string\[] | Always empty. See below |

**On exactness, since it is the point of this Actor.** `followerCount` and `monthlyListeners` are
integers read directly out of Spotify's own JSON response. They are not parsed back from display
text, and no compact-number expansion happens anywhere: nothing here turns "100.6M" into
`100600000`. Play counts arrive from Spotify as decimal strings and are converted to numbers as they
stand, with no rounding. So if you diff two runs a week apart, the difference is real movement rather
than a rendering artefact.

**On the two empty columns.** `popularity` and `genres` are Spotify Web API fields with no
counterpart anywhere in the public web player's data, and the Web API needs a client credential this
Actor deliberately does not use. They are carried as `null` and `[]` so that every row in the dataset
has the same column set, which keeps CSV exports and warehouse loads stable. They are not part of the
default table view. If you need genres or the popularity score, you need a Spotify developer app and
the Web API, not a scraper.

### Why scrape Spotify artist data?

The buyers for this are labels, distributors, A\&R teams and music analytics shops, and the use cases
mostly come down to watching a number that Spotify does not let you export.

- **A\&R scouting on a real number.** Rank a watchlist by exact monthly listeners rather than by a
  rounded figure that buckets a hundred artists into the same "1.2M". Week-over-week deltas on a few
  hundred unsigned artists are where the signal is.
- **Roster reporting.** Run your whole roster on a schedule and you have a dated table of listeners,
  followers and per-track play counts to put in front of a label services team or an investor.
- **Audience conversion.** `followerCount` against `monthlyListeners` is a rough measure of how much
  of an artist's passive listening has turned into intent. A high listener count on a thin follower
  base reads very differently from the reverse, especially for playlist-driven artists.
- **Catalogue and release tracking.** `topTracks` play counts show which songs are actually carrying
  an artist, and whether a new release has displaced the back catalogue or just landed beside it.
- **Deal diligence and valuation.** For catalogue acquisition or an advance, a series of dated exact
  play counts per track is far more useful than a screenshot of a rounded figure.
- **Enriching a rights or metadata database.** `artistId` is Spotify's own key, so rows join cleanly
  to anything else you hold keyed on Spotify URIs.

### How to use it

1. Open the Actor and click **Try for free**.
2. Put your artists into **Artists**, one per line. The easiest source is the Spotify app: right-click
   an artist, **Share**, **Copy link to artist**, and paste. Plain 22-character IDs and
   `spotify:artist:` URIs work equally well.
3. Leave **Maximum results** alone for most runs. The `artists` input already caps at 200, so it only
   matters if you want fewer rows than artists.
4. Click **Start**. The run fetches its session once, then one request per artist at the concurrency
   you set.
5. Export the dataset from the **Storage** tab as CSV, JSON or Excel, or read it over the API.
6. For a growth series, put the run on a **Schedule** and append each run's rows to a table keyed on
   `artistId` and `collectedAt`.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `artists` | string\[] | required | Spotify artist IDs, `spotify:artist:` URIs or `open.spotify.com` artist URLs. 1 to 200 per run |
| `maxItems` | integer | `1000` | Cap on dataset rows, 1 to 50000 |
| `concurrency` | integer | `5` | Artists fetched in parallel, 1 to 10 |

```json
{
  "artists": [
    "4gzpq5DPGxSnKTe4SA8HAU",
    "spotify:artist:06HL4z0CvFAxyc27GXpf02",
    "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4"
  ],
  "concurrency": 5
}
```

An ID, a URI and a full URL for the same artist resolve to one target, so duplicates in your list do
not cost extra fetches or produce extra rows. Anything that is not a 22-character Spotify ID is
rejected before it costs a fetch and appears in the run's failure list. That includes artist names:
this Actor takes artists you already know, and there is no search by name.

### Output

One row per artist. This is the shape, with plausible values rather than a captured run:

```json
{
  "artistId": "4gzpq5DPGxSnKTe4SA8HAU",
  "name": "The Weeknd",
  "followerCount": 96437215,
  "monthlyListeners": 100593317,
  "popularity": null,
  "genres": [],
  "isVerified": true,
  "topTracks": [
    { "trackId": "0VjIjW4GlUZAMYd2vXMi3b", "name": "Blinding Lights", "playCount": 4917364118 },
    { "trackId": "2LBqCSwhJGcFQeTHMVGwy3", "name": "Die For You", "playCount": 2801945011 },
    { "trackId": "7CyPwkp0oE8Ro9Dd5CUDjW", "name": "One Of The Girls", "playCount": 1642087339 }
  ],
  "imageUrl": "https://i.scdn.co/image/ab6761610000e5eb214f3cf1cbe7139c1e26ffbb",
  "artistUrl": "https://open.spotify.com/artist/4gzpq5DPGxSnKTe4SA8HAU",
  "sourceUrl": "https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryArtistOverview",
  "collectedAt": "2026-08-19T09:14:02.771Z"
}
```

`sourceUrl` is the artist-overview query the row was read from; the sample above trims its query
string, which in a real run also carries the artist URI and the persisted-query hash.

The run also writes a `SUMMARY` record to the key-value store with the item count, the failure count
and every artist that failed, each with its error.

### How it reads exact figures

Worth understanding, because it explains both the accuracy and the shape of the run.

The Spotify artist page renders nothing server-side. An ordinary fetch returns the web-player shell,
and pointing a crawler user-agent at it only upgrades that to Open Graph tags whose listener count is
already rounded for display. Reading the page, in other words, gets you "100.6M" at best.

The exact figures come from `queryArtistOverview`, the same call the web player makes for its own
artist page. That call needs two things, and Spotify's public pages hand out both: an anonymous
bearer token, which any `/embed/` page mints server-side, and the persisted-query hash for the
operation, which is safelisted and therefore has to be read out of the web-player bundle rather than
invented. An unregistered query is refused outright.

The Actor collects both once per run, in three fetches: the `open.spotify.com` shell, the bundle it
links, and one `/embed/artist/` page. Then it issues one request per artist. Because the bootstrap is
per run rather than per artist, its cost disappears into a long list. If the bootstrap fails, the run
fails once with that error rather than reporting every artist on your list as separately broken.

### Reliability and limitations

Every field comes out of Spotify's GraphQL response rather than from rendered markup, so a value is
either present and exact or absent. Nothing is inferred from a display string.

An artist ID Spotify does not know answers with HTTP 200 and an empty record, which would otherwise
reach the dataset as an all-null row counted as a success. That case is caught and recorded under
`failures` in the run's `SUMMARY` instead. A run in which every artist failed exits with an error.

Artist-level data only. No full discography, no album or playlist listings, no track audio features,
no related artists and no search. The `topTracks` list is the one the artist page shows, not the
complete catalogue. Only `spotify.com` URLs are accepted; a link on any other host is rejected before
it costs a fetch.

There are no retries, deliberately. The String Unblocker owns proxy rotation and anti-bot solver
selection, so a retry loop here would only re-roll the same block.

### How much does it cost?

The Actor uses Apify's pay-per-event pricing and charges once per result row written to the dataset,
which here means once per artist successfully collected. You pay for rows, not for run time, memory
or the three bootstrap fetches, and a run that returns nothing charges nothing. The current
per-result price is on the pricing tab above.

Charging happens on the push, so an account spend limit trims the write rather than overrunning it. A
row that could not be charged for is never written.

Runs started from an Apify **free plan** stop at 250 requests and 250 results and report that they
reached the limit. Since the `artists` input already caps at 200, a free-plan run is bound by that
ceiling in practice rather than by the limit. Any paid plan runs the full input and whatever
`maxItems` you set. The limit exists because this Actor fetches through our own infrastructure, which
Apify does not cover for free-plan runs.

### Using it with the Apify API

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

const client = new ApifyClient({ token: "YOUR_APIFY_TOKEN" });

const run = await client.actor("usestring/spotify-artists").call({
  artists: [
    "4gzpq5DPGxSnKTe4SA8HAU",
    "https://open.spotify.com/artist/06HL4z0CvFAxyc27GXpf02"
  ]
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();

for (const artist of items) {
  console.log(artist.name, artist.monthlyListeners, artist.followerCount);
}
```

The same call works from the Python client, or against the
[Apify REST API](https://docs.apify.com/api/v2) in any language. To see which artists failed and why,
read the `SUMMARY` record from the run's key-value store.

### Is it legal to scrape Spotify?

The Actor reads figures Spotify publishes on public artist pages, with no login and no paywall in
between. It does not sign in, does not touch listening history, playlists or any user account, and
collects no personal data. An artist page is a promotional surface, and the follower and listener
counts on it are published deliberately.

Scraping public data is broadly lawful in the US and the EU. What you do with it afterwards is a
different question, governed by copyright, database rights and Spotify's own terms, and reselling a
derived dataset is a materially different act from using it internally for scouting or reporting.
Artist images belong to their rights holders. Take legal advice before you republish or resell.
Nothing here is legal advice.

### Related actors

- [TikTok Profile Scraper](https://apify.com/usestring/tiktok-profiles) for the other surface an
  artist's audience shows up on, with exact follower and like counts
- [LinkedIn Profile Scraper](https://apify.com/usestring/linkedin-profiles) for the people side of a
  label, management company or distributor
- [Yahoo Finance Quote Scraper](https://apify.com/usestring/yahoo-finance-quotes) if you are tracking
  the listed music groups alongside the artists

### FAQ

**Are the monthly listeners exact or rounded?**
Exact. `monthlyListeners` and `followerCount` are integers taken straight from Spotify's JSON, and
every `topTracks[].playCount` is exact as well. Nothing in this Actor expands a display string such
as "100.6M" into a number, so a difference between two runs is real movement.

**Do I need a Spotify account, API key or client credentials?**
No. The Actor uses the anonymous access token that Spotify's own public `/embed/` page mints, so
there is no login, no developer app and no client secret. It is also not subject to Web API rate
quotas.

**Can I search Spotify by artist name?**
No. You supply artist IDs, URIs or artist URLs. A name is rejected as an invalid target before any
fetch is spent on it, and appears in the run's failure list. Get an ID from the Spotify app with
**Share**, **Copy link to artist**.

**How many artists can one run collect?**
Up to 200, which is the ceiling on the `artists` input. For a larger roster, split it across several
runs or several scheduled runs.

**Does it return albums, full discographies or playlists?**
No. It returns artist-level fields plus the top-tracks list Spotify shows on the artist page. There
is no album, playlist or audio-features data, and no related-artists graph.

**How fresh is the data, and what happens when an artist fails?**
Each artist is fetched live when the run starts, and `collectedAt` stamps that moment. Nothing is
cached between runs. An artist that fails, including an ID Spotify does not recognise, is recorded
under `failures` in the run's `SUMMARY` and the rest of the list continues. If every artist failed,
the run exits with an error.

**Why are `genres` and `popularity` always empty?**
Because they exist only in Spotify's Web API, which needs a client credential this Actor does not
use. They are kept as empty columns so the dataset has one stable shape per row.

### Feedback

If a figure looks wrong, an artist consistently fails, or there is a field on the artist page you
want returned, open an issue from this Actor's **Issues** tab on the Store page and include the IDs
you ran.

# Actor input Schema

## `artists` (type: `array`):

Spotify artist IDs or open.spotify.com artist URLs.

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

Global cap on dataset items. Runs started from an Apify free plan stop at 250 requests and 250 results; any paid plan runs the full amount.

## `concurrency` (type: `integer`):

Targets fetched in parallel.

## Actor input object example

```json
{
  "artists": [
    "4gzpq5DPGxSnKTe4SA8HAU"
  ],
  "maxItems": 1000,
  "concurrency": 5
}
```

# Actor output Schema

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

Look up Spotify artists by ID, URI or open.spotify.com URL and get exact numbers: monthly listeners and follower count as integers rather than the rounded 100.6M the page prints, plus verified status, artist image and the top-tracks list with a play count on every track. No API credentials.

## `summary` (type: `string`):

Item count, failure count and every target that failed, with its error.

# 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 = {
    "artists": [
        "4gzpq5DPGxSnKTe4SA8HAU"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("usestring/spotify-artists").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 = { "artists": ["4gzpq5DPGxSnKTe4SA8HAU"] }

# Run the Actor and wait for it to finish
run = client.actor("usestring/spotify-artists").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 '{
  "artists": [
    "4gzpq5DPGxSnKTe4SA8HAU"
  ]
}' |
apify call usestring/spotify-artists --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,usestring/spotify-artists"
        }
    }
}

```

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/y53QfRlxIsUkrAseS/builds/6NaQNyS2d9yXRnmdo/openapi.json
