Building a Spotify Song Recommender System with Python and Streamlit
Ever wondered how Spotify seems to know what songs you’ll like next? I wanted to explore the mechanics behind recommender systems, and I…
Building a Spotify Song Recommender System with Python and Streamlit
Ever wondered how Spotify seems to know what songs you’ll like next? I wanted to explore the mechanics behind recommender systems, and I thought a fun way to do it would be to build a song recommender that finds similar tracks based on artist, genre, and subgenre using cosine similarity. In this blog, I’ll take you step by step through how I built my project, from preparing the data to creating a Streamlit app where you can see recommendations with covers and 30-seconds previews.
I built this project as a hands-on way to understand recommender systems, and you can check out the full code on Github: https://github.com/ahmedfarazsyk/spotify_song_recommender
Here we will use instance-based learning approach instead of the traditional model-based learning.
Instance-Based Learning
The algorithm doesn’t build a general model or coefficients. It stores all training data (song feature vectors) and computes similarity on the fly. When you query with a seed song, it looks up its neighbors in the dataset using cosine similarity and returns the top matches. This is exactly what this recommender does: it uses the similarity matrix (precomputed from instances) to find recommendations.

Instance-based learning
Getting the Data
I started with a dataset of 18,454 songs available on Kaggle: https://www.kaggle.com/datasets/sujaykapadnis/spotify-songs
It includes metadata like:
track_id, track_name, track_artist, lyrics, track_popularity, track_album_id, track_album_name, track_album_release_date, playlist_name, playlist_id, playlist_genre, playlist_subgenre, danceability, energy, key, loudness, mode, speechiness, acousticness, instrumentalness, liveness, valence, tempo, duration_ms, language
The goal was to represent each song in a way that allows comparison with other songs. Features like artist, genre, and subgenre are categorical, so I needed a method that could encode them numerically to compute similarity.
Taking a Quick Look at the Data
Before jumping into modeling, I explored the dataset:
import pandas as pd
df = pd.read_csv("spotify_songs.csv")
print(df.head())
print(df.info())

This gave me a clear view of the columns I’d use and any missing values that needed handling. Initially, I thought that lyrics would be a better option to find the similar songs. But after building a similarity matrix, it gave me the most irrelevant songs. They were similar based on the words repeated in lyrics but they have completely different genre and artist. When we search for some song on Spotify we are often recommended with songs that have same genre, sub genre or artist. So this time I opted for Playlist Genre, Playlist Sub Genre and Track Artist as my key attributes.
Data Transformation
Categorical text features need to be converted into numeric vectors. But before that I needed to clean the data a little bit. There are many artists whose first name is similar, there are 5946 unique artists in the data, but 4412 unique artists by their first name. Eg. The Weeknd, The Cranberries, The Game, The Who, The Rolling Stones etc. So if we just build a similarity matrix without transformation that would treat different artists similarly and first name and last name of a same artist differently. Same goes for Genre and Sub Genre.
The solution is to remove the spaces between the words, so each name is treated differently:
song_df["track_artist"] = song_df["track_artist"].apply(lambda x: x.replace(" ", "" ))
song_df["playlist_genre"] = song_df["playlist_genre"].apply(lambda x: x.replace(" ", ""))
song_df["playlist_subgenre"] = song_df["playlist_subgenre"].apply(lambda x: x.replace(" ", ""))
I combined artist, genre, and subgenre then used CountVectorizer to form a single feature matrix with 6000 most frequent words.
After transformation, each song was represented as a vector in a high-dimensional space. This makes it possible to compute cosine similarity between songs.
Building the Cosine Similarity Matrix
Cosine similarity measures how close two vectors are in this feature space. Mathematically:
cosine similarity = A⋅B / ∣∣A∣∣ ∣∣B∣∣
I used scikit-learn to compute a large similarity matrix (size 18,454 × 18,454):
from sklearn.metrics.pairwise import cosine_similarity
similarity = cosine_similarity(vectors)
Using Spotify API to Extract Relevant Data
While the similarity matrix finds the closest songs, I wanted to show album covers and 30-second previews in my app. For this, I used the Spotify Web API:
- Setup: Create a Spotify Developer App to get
CLIENT_IDandCLIENT_SECRET. - Fetch track metadata (covers and preview URLs):
import requests
import base64
import os
def get_token():
auth_string = client_id + ":" + client_secret
auth_bytes = auth_string.encode("utf-8")
auth_base64 = str(base64.b64encode(auth_bytes), "utf-8")
url = "https://accounts.spotify.com/api/token"
headers = {
"Authorization": "Basic " + auth_base64,
"Content-Type": "application/x-www-form-urlencoded"
}
data = {"grant_type":"client_credentials"}
result = post(url, headers = headers, data = data)
json_result = json.loads(result.content)
token = json_result["access_token"]
return token
This is how we extract the cover of a particular track:
def get_auth_header(token):
return {"Authorization": "Bearer " + token}
def get_cover(token, track_id):
url = f"https://api.spotify.com/v1/tracks/{track_id}"
headers = get_auth_header(token)
result = get(url, headers = headers)
json_result = json.loads(result.content)["album"]["images"][0]["url"]
return json_result
After November 27, 2024, Spotify deprecated its 30-seconds preview url. But I found an unofficial hack. Just hit the preview url directly.
def get_track_preview_embed(track_id: str):
"""
Scrapes Spotify embed page for a given track_id and extracts the audio preview URL.
"""
url = f"https://open.spotify.com/embed/track/{track_id}"
headers = {
"User-Agent": "Mozilla/5.0" # pretend to be a browser
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"Failed to fetch embed page: {response.status_code}")
html = response.text
# Look for JSON containing "audioPreview"
match = re.search(r'"audioPreview"\s*:\s*\{.*?\}', html)
if not match:
return None
audio_preview_json = match.group(0)
# Convert the matched snippet into a dict
try:
# Ensure it's valid JSON
audio_preview_json = "{" + audio_preview_json + "}"
data = json.loads(audio_preview_json)
return data["audioPreview"]["url"]
except Exception as e:
print("Error parsing preview:", e)
return None
Building the Streamlit App
Finally, I created a Streamlit web app that allows users to type a song, get recommendations, and see each song’s name, cover, and preview:
import streamlit as st
st.title("Song Recommender System")
select_song_name = st.selectbox("Which song do you want to select?",
songs["track_name"].values)
if st.button("Recommend"):
names, covers, previews = recommend(select_song_name)
st.write(song['name'])
st.image(song['cover'])
st.audio(song['preview'])
This provides an interactive interface for exploring similar tracks in real time.
Results
Here’s an example of a song I tried:
- Seed Song: “Viva La Vida”
- Top 10 Recommendations: (covers, previews, and song names shown in the app)

Limitations
- The similarity is based purely on artist, genre, and subgenre, not on audio features or listener behavior.
- The dataset is static; new songs won’t be included unless the matrix is recomputed.
- Predictions are slower since the recommendations are searched around a matrix of 18454 x 18454.
Future Improvements
- Enhance similarity by including audio embeddings (loudness, speechiness, acousticness, instrumentalness etc).
- Dockerize the app for easy deployment.
- Deploy to the cloud so users can try it without local setup.
Conclusion
This project shows how instance-based learning can power a simple yet effective recommender. By using cosine similarity, the system makes recommendations that are easy to understand and directly tied to the features chosen.
Of course, this approach has limits. It doesn’t scale well with huge datasets, and the quality of recommendations depends entirely on the features we feed into it. Still, it’s a solid reminder that even without complex models, good results are possible with the right representation of data.
If you’d like to explore the implementation or try it out yourself, the complete project is available here: https://github.com/ahmedfarazsyk/spotify_song_recommender
메타데이터
- post_id
- 6e830f84ae4e
- slug
- building-a-spotify-song-recommender-system-with-python-and-streamlit-6e830f84ae4e
- url
- https://medium.com/@shaikhahmedfaraz64/building-a-spotify-song-recommender-system-with-python-and-streamlit-6e830f84ae4e
- canonical_url
- https://medium.com/@shaikhahmedfaraz64/building-a-spotify-song-recommender-system-with-python-and-streamlit-6e830f84ae4e
- author_url
- https://medium.com/@shaikhahmedfaraz64
- status
- ok
- fetched_at
- 2026-06-09 14:34:10