Skip to content

Media downloads

SpotifyScraper can download two kinds of media from public pages: cover art (for any entity that carries images) and preview clips (the short MP3 Spotify exposes for many tracks and episodes). Both methods reuse the client's configured transport — same rate limiting, retries, and proxy.

What you can and cannot download

These are the same public assets a browser loads: cover images and ~30-second preview clips. SpotifyScraper does not download full tracks or DRM-protected audio, and cannot. Review the Legal & ToS page before saving anything.

Download cover art

download_cover works with any fetched model that has a name and images — tracks, albums, artists, playlists, episodes, and shows. It returns the Path it wrote.

from spotify_scraper import SpotifyClient

with SpotifyClient() as client:
    album = client.get_album("4aawyAB9vmqN3uQ7FjRGTy")
    path = client.download_cover(album, "downloads")
    print("Saved", path)

Signature:

client.download_cover(
    entity,
    dest=".",            # directory; created if missing
    *,
    size="largest",      # "largest" or "smallest"
    filename=None,       # explicit name; else "<sanitized name>.<ext>"
)

Choosing a size

size selects between the available images by pixel area:

client.download_cover(album, "downloads", size="largest")    # default
client.download_cover(album, "downloads", size="smallest")   # thumbnail

Track covers fall back to the album

A Track often has empty images. When you download a track's cover, the library automatically falls back to the track's album images, so this just works:

with SpotifyClient() as client:
    track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
    client.download_cover(track, "downloads")

If an entity has no images anywhere, download_cover raises MediaError.

Download preview clips

download_preview fetches the preview MP3 for a track or episode and returns the written Path.

from spotify_scraper import SpotifyClient

with SpotifyClient() as client:
    track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
    path = client.download_preview(track, "downloads")
    print("Saved", path)

Signature:

client.download_preview(
    entity,              # a Track or Episode
    dest=".",            # directory; created if missing
    *,
    filename=None,       # explicit name; else "<sanitized name>.mp3"
    embed_cover=False,   # embed cover art + tags (needs the [media] extra)
)

Not every item has a preview

Spotify does not expose a preview clip for every track or episode. When preview_url is missing, download_preview raises MediaError. Guard it:

from spotify_scraper import MediaError

try:
    client.download_preview(track, "downloads")
except MediaError as exc:
    print("No preview:", exc)

Embedding cover art and tags

Pass embed_cover=True to write the cover art and basic ID3 tags (title, and artist or show name) into the MP3. This uses mutagen, which ships in the media extra:

pip install "spotifyscraper[media]"
with SpotifyClient() as client:
    track = client.get_track("4uLU6hMCjMI75M1A2tKUQC")
    client.download_preview(track, "downloads", embed_cover=True)

If you request embed_cover=True without mutagen installed, you get a MediaError telling you to install the extra. Tag mapping:

ID3 frame Value
TIT2 (title) The entity's name.
TPE1 (artist) The track's first artist, or the episode's show name.
APIC (cover) The largest available cover image (JPEG).

File naming and sanitization

When you do not pass filename, the output name is derived from the entity's name:

  • Covers: <sanitized name>.<ext>, where the extension comes from the response content type (jpg, png, webp, gif; defaults to jpg).
  • Previews: <sanitized name>.mp3.

Sanitization normalizes Unicode (NFC), replaces control characters and the path separators \x00-\x1f < > : " / \ | ? * with spaces, collapses runs of whitespace, trims leading/trailing dots and spaces, and caps the stem at 150 characters. An empty result becomes untitled. For example, a track named AC/DC: Back? *Live* is written as AC DC Back Live.mp3.

Provide filename to bypass sanitization and control the name (and extension) yourself:

client.download_cover(album, "downloads", filename="cover.jpg")
client.download_preview(track, "downloads", filename="clip.mp3")

The dest directory is created automatically if it does not exist.

Async downloads

The async client has the same two methods — just await them:

import asyncio
from spotify_scraper import AsyncSpotifyClient

async def main() -> None:
    async with AsyncSpotifyClient() as client:
        track = await client.get_track("4uLU6hMCjMI75M1A2tKUQC")
        await client.download_cover(track, "downloads")
        await client.download_preview(track, "downloads", embed_cover=True)

asyncio.run(main())