Why I Built This

At the time of writing, my Last.fm account contains more than 18 years of listening history and hundreds of thousands of scrobbles.

Over the years I’ve built a collection of Spotify playlists using that listening data. One of those is a “Heavy Rotation” playlist featuring my most-played tracks from the last 90 days.

Spotify offers playlists like On Repeat and Wrapped, but I wanted more control over the timeframe and selection criteria. Maintaining the playlist meant manually comparing my Last.fm listening history against Spotify every few weeks.

Eventually I decided to automate the process with Python.


The Goal

Take my top 50 tracks from the last 90 days and automatically rebuild a Spotify playlist.

The playlist is rebuilt from scratch using the 50 most-played tracks from a rolling 90-day listening window.

Screenshot of Heavy Rotation playlist

The Data Source

Because Last.fm stores historical listening activity independently of Spotify, it provides a consistent source of listening data spanning multiple devices, apps, and years of music habits.

Last.fm scrobble history screenshot

It’s easier to pull listening history from Last.fm than directly from Spotify.

And they both have APIs.


The Pipeline

Last.fm API
    ↓
Fetch Listening History (90 days)
    ↓
Aggregate Top Tracks
    ↓
Resolve Spotify Matches
    ↓
Rebuild Playlist

Matching Songs

Finding the most-played songs was relatively straightforward. Last.fm already exposes listening history through its API.

The harder problem was matching those songs to Spotify.

Track names aren’t always identical across platforms. Some songs have multiple versions, including remasters, live recordings, deluxe editions, radio edits, and explicit or clean releases. Even small differences in metadata can cause Spotify search results to return unexpected matches.

To solve this, the script searches Spotify using the track and artist name from Last.fm, then attempts to resolve the best match. While the current implementation works well for most tracks, improving matching accuracy is one of the next areas I’d like to explore.

top_tracks = get_top_tracks(limit=50)

for track in top_tracks:
    spotify_id = resolve(track)

sp.playlist_replace_items(playlist_id, [])
sp.playlist_add_items(playlist_id, track_ids)

Playlist Results

% python run_heavy_rotation.py 
Fetching Last.fm top tracks...
Fetching Last.fm top tracks (3 month window)...
Top tracks loaded: 50
Searching: KOTA The Friend - Oregon
Searching: Kenya Vaun - Strike 3 Freestyle (feat. Hunna G)
...
Searching: Noga Erez - DUMB Remix (feat. V1V1D)

Resolved 50 tracks
Clearing playlist...
Playlist cleared successfully
🚀 LIVE MODE: starting playlist rebuild
Tracks to add: 50
Done — rebuilt playlist with 50 tracks
Updated Heavy Rotation playlist screenshot

The script successfully resolved all 50 tracks and rebuilt the playlist in a single run, replacing a manual process I previously performed every few weeks.


What Surprised Me

The hardest part wasn’t retrieving listening history from Last.fm.

It wasn’t authenticating with Spotify either.

The hardest part was accurately matching tracks between two different platforms.

This reinforced a lesson common in analytics and engineering projects: obtaining data is often easier than reconciling it.


What I’d Improve Next

For now, the script solves a real workflow that I previously handled manually. Future improvements will focus on improving matching accuracy and expanding the approach to support additional playlist types.

  • Fuzzy matching
  • Confidence scores
  • Multi-playlist generation
  • Publishing playlists for other users

Tools & Skills

  • Python
  • Last.fm API
  • Spotify Web API
  • OAuth Authentication
  • Playlist Automation
  • Data Matching & Reconciliation
  • AI-Assisted Development

This project was built using an AI-assisted workflow. Rather than generating an entire application from a prompt, I used AI tools to help troubleshoot API authentication issues, explore library documentation, iterate on matching logic, and accelerate development.