Error handling¶
Every error SpotifyScraper raises derives from a single base class,
SpotifyScraperError, so you can catch all library failures with one except
clause — or catch specific subclasses for fine-grained handling. The library
never returns error strings or error dicts; failures are always exceptions.
The exception tree¶
SpotifyScraperError # base — catch this to catch everything
├── URLError # invalid Spotify URL, URI, or ID input
├── NetworkError # transport failure talking to Spotify
│ └── RateLimitedError # HTTP 429 (carries retry_after)
├── TokenError # anonymous token bootstrap/refresh failed
├── AuthenticationError # missing/expired credentials for an auth feature
├── NotFoundError # entity does not exist or is unavailable
├── ParsingError # Spotify payload had an unexpected shape
└── MediaError # media download or tagging failed
All of these import directly from the top-level package:
from spotify_scraper import (
SpotifyScraperError,
URLError,
NetworkError,
RateLimitedError,
TokenError,
AuthenticationError,
NotFoundError,
ParsingError,
MediaError,
)
What each error means¶
| Exception | Raised when | Typical fix |
|---|---|---|
URLError |
The input is not a recognizable Spotify URL/URI/ID, or a bare ID was passed without a type. | Check the value you passed to get_*. |
NetworkError |
A connection failed, or retries were exhausted on a 403/5xx. Carries request_url. |
Retry later; check connectivity/proxy. |
RateLimitedError |
A 429 could not be recovered within the retry budget. Subclass of NetworkError; carries retry_after. |
Slow down — lower RateLimit.per_second. |
NotFoundError |
Spotify returned 404 — the entity does not exist or is unavailable. | Verify the ID. |
ParsingError |
Spotify's payload had an unexpected shape (their API changed). | Update the library; check for a release. |
TokenError |
The anonymous token could not be bootstrapped or refreshed. | Usually transient; retry. May indicate a needed update. |
AuthenticationError |
A feature requiring user credentials lacked valid ones (e.g. get_lyrics without a valid sp_dc cookie). |
Provide valid credentials — see the Lyrics & cookies guide. |
MediaError |
A download failed: no preview/image exists, or embed_cover was requested without mutagen. |
Check preview_url/images; install [media]. |
Catch everything¶
The simplest pattern — treat any library failure uniformly:
from spotify_scraper import SpotifyClient, SpotifyScraperError
with SpotifyClient() as client:
try:
track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
except SpotifyScraperError as exc:
print("SpotifyScraper failed:", exc)
else:
print(track.name)
Catch specific failures¶
Order except clauses from most specific to least, since RateLimitedError is a
subclass of NetworkError:
from spotify_scraper import (
SpotifyClient,
NotFoundError,
RateLimitedError,
NetworkError,
ParsingError,
)
with SpotifyClient() as client:
try:
track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
except NotFoundError:
print("No such track.")
except RateLimitedError as exc:
print("Rate limited; retry after", exc.retry_after, "seconds")
except NetworkError as exc:
print("Network problem with", exc.request_url, ":", exc)
except ParsingError:
print("Spotify changed its payload — update SpotifyScraper.")
else:
print(track.name)
Using the extra attributes¶
NetworkError (and its RateLimitedError subclass) carry request_url;
RateLimitedError additionally carries retry_after:
import time
from spotify_scraper import SpotifyClient, RateLimitedError
with SpotifyClient() as client:
try:
track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
except RateLimitedError as exc:
wait = exc.retry_after or 5.0
print(f"Backing off {wait}s (was hitting {exc.request_url})")
time.sleep(wait)
Handling media errors¶
download_preview and download_cover raise MediaError when there is nothing
to download, or when tagging is requested without the extra:
from spotify_scraper import SpotifyClient, MediaError
with SpotifyClient() as client:
track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
try:
client.download_preview(track, "downloads", embed_cover=True)
except MediaError as exc:
# No preview clip, or mutagen ([media] extra) is not installed.
print("Could not download preview:", exc)
Embed-tier degradation is not an error¶
SpotifyScraper uses a two-tier extraction ladder (see Entities).
When the rich tier-1 GraphQL payload changes shape, the library raises an
internal ParsingError for that request, catches it, and falls back to
the entity's embed page rather than surfacing the error to you. You still get a
valid model — just with the tier-1-only fields left as None (or empty tuples).
What this means for your code:
- A successful
get_*call may return a model with some fields unset. Always guard tier-1 fields (if track.play_count is not None:). - The degradation is logged at
WARNINGon thespotify_scraperlogger, so you can observe it without it breaking your flow:
import logging
logging.basicConfig(level=logging.WARNING)
# A tier-1 -> embed fallback now logs:
# WARNING spotify_scraper: Tier-1 track fetch degraded to embed page: ...
- A
ParsingErroronly propagates to you when both tiers fail to produce a usable payload — at which point updating the library is the right move, since payload changes are fixed upstream.