diff --git a/.cache b/.cache
new file mode 100644
index 0000000..4d376ec
--- /dev/null
+++ b/.cache
@@ -0,0 +1 @@
+{"access_token": "BQD30P4pxr2e-KGpYXD-EFn6t-X9nO0vFF2mh6HyqRgZUq5UkXEuka37D4o93Ino3Fz5rBGz9jlMo-pw5WE-NdJbajHBNHJsyEOK7tBrjV8DljS4a5FtpJqD5Xi-_J25pZBP-tstpLo", "token_type": "Bearer", "expires_in": 3600, "expires_at": 1775757187}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c49bd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.env
diff --git a/anaconda_projects/db/project_filebrowser.db b/anaconda_projects/db/project_filebrowser.db
new file mode 100644
index 0000000..3fa3a4a
Binary files /dev/null and b/anaconda_projects/db/project_filebrowser.db differ
diff --git a/lab-apis.ipynb b/lab-apis.ipynb
index e923554..6ebb0e2 100644
--- a/lab-apis.ipynb
+++ b/lab-apis.ipynb
@@ -34,7 +34,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 23,
"id": "ea44c82a-5c07-4dbc-beb2-bba2601bb75e",
"metadata": {},
"outputs": [],
@@ -45,7 +45,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 24,
"id": "319cfd4e-f6fb-4419-80e0-e3983cd25e43",
"metadata": {},
"outputs": [],
@@ -56,10 +56,24 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 25,
"id": "04e12954-fd70-4311-88a5-fb7e2c29799c",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Requirement already satisfied: spotipy in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (2.25.2)\n",
+ "Requirement already satisfied: redis>=3.5.3 in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (from spotipy) (7.1.0)\n",
+ "Requirement already satisfied: requests>=2.25.0 in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (from spotipy) (2.32.3)\n",
+ "Requirement already satisfied: urllib3>=1.26.0 in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (from spotipy) (2.3.0)\n",
+ "Requirement already satisfied: charset-normalizer<4,>=2 in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (from requests>=2.25.0->spotipy) (3.3.2)\n",
+ "Requirement already satisfied: idna<4,>=2.5 in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (from requests>=2.25.0->spotipy) (3.7)\n",
+ "Requirement already satisfied: certifi>=2017.4.17 in C:\\Users\\pauli\\anaconda3\\Lib\\site-packages (from requests>=2.25.0->spotipy) (2025.10.5)\n"
+ ]
+ }
+ ],
"source": [
"# If you havent done so, install the spotipy wrapper\n",
"!pip install spotipy"
@@ -75,7 +89,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 26,
"id": "03034bc6-9858-412a-83b7-18abdc345d7e",
"metadata": {},
"outputs": [],
@@ -106,35 +120,35 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 27,
"id": "7772a1e0-9692-4d04-a590-76111a272d8d",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "results = sp.search(q='Lady Gaga', limit=50)\n",
- "results"
+ "# results = sp.search(q='Lady Gaga', limit=50)\n",
+ "# results"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 28,
"id": "edc14c95-474b-4e2a-aea3-bdfd0a205546",
"metadata": {},
"outputs": [],
"source": [
- "results.keys() # We can see that we only have tracks"
+ "# results.keys() # We can see that we only have tracks"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 30,
"id": "ad8ef934-1dbb-4008-ac8e-f5c29823fe6a",
"metadata": {},
"outputs": [],
"source": [
- "results[\"tracks\"].keys() # Let's check the values"
+ "# results[\"tracks\"].keys() # Let's check the values"
]
},
{
@@ -540,11 +554,2732 @@
"source": [
"# Your answer here"
]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "714b6f99",
+ "metadata": {},
+ "source": [
+ "# Lab | APIs-Spotify API (Adapted Version)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f54f2f82",
+ "metadata": {},
+ "source": [
+ "## 1. Title & context"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4d90ae9e",
+ "metadata": {},
+ "source": [
+ "Why adapted?\n",
+ " - Client Credentials is stable for public catalog data.\n",
+ " - Some endpoints may return 403 Forbidden depending on app access and endpoint restrictions.\n",
+ " - This notebook includes defensive error handling."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55ec6ad3",
+ "metadata": {},
+ "source": [
+ "## 2. Install and imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "47aade54",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install -q spotipy pandas"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25b6e178",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import requests\n",
+ "import spotipy\n",
+ "\n",
+ "from spotipy.oauth2 import SpotifyClientCredentials\n",
+ "from spotipy.exceptions import SpotifyException"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7ed913a5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from dotenv import load_dotenv\n",
+ "import os\n",
+ "\n",
+ "load_dotenv(\"key.env\")\n",
+ "\n",
+ "CLIENT_ID = os.getenv(\"SPOTIPY_CLIENT_ID\")\n",
+ "CLIENT_SECRET = os.getenv(\"SPOTIPY_CLIENT_SECRET\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "48f40958",
+ "metadata": {},
+ "source": [
+ "## 3. Credentials"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "20fd4dc7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "import os\n",
+ "\n",
+ "CLIENT_ID = os.getenv(\"SPOTIPY_CLIENT_ID\")\n",
+ "CLIENT_SECRET = os.getenv(\"SPOTIPY_CLIENT_SECRET\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e9bac6c3",
+ "metadata": {},
+ "source": [
+ "## 4. Authentication and initializing the API"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c1bf4419",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Spotify client created successfully.\n"
+ ]
+ }
+ ],
+ "source": [
+ "def build_spotify_client(client_id: str, client_secret: str):\n",
+ " \"\"\"\n",
+ " Build and test a Spotify client using Client Credentials.\n",
+ " Returns a Spotipy client if successful, otherwise None.\n",
+ " \"\"\"\n",
+ " if not client_id or not client_secret:\n",
+ " print(\"Missing CLIENT_ID or CLIENT_SECRET.\")\n",
+ " return None\n",
+ "\n",
+ " try:\n",
+ " auth_manager = SpotifyClientCredentials(\n",
+ " client_id=client_id,\n",
+ " client_secret=client_secret\n",
+ " )\n",
+ "\n",
+ " sp = spotipy.Spotify(\n",
+ " auth_manager=auth_manager,\n",
+ " requests_timeout=10,\n",
+ " retries=3\n",
+ " )\n",
+ "\n",
+ " # Lightweight test call\n",
+ " _ = sp.search(q=\"Lady Gaga\", type=\"track\", limit=1)\n",
+ " print(\"Spotify client created successfully.\")\n",
+ " return sp\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " print(f\"SpotifyException | status={getattr(e, 'http_status', 'N/A')} | message={e}\")\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error: {e}\")\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error: {e}\")\n",
+ "\n",
+ " return None\n",
+ "\n",
+ "\n",
+ "sp = build_spotify_client(CLIENT_ID, CLIENT_SECRET)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c88228fb",
+ "metadata": {},
+ "source": [
+ "## 5. Helper functions for safe API calls"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "974a3b69",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def safe_search(sp, q: str, search_type: str = \"track\", limit: int = 10):\n",
+ " \"\"\"\n",
+ " Safe wrapper for Spotify search.\n",
+ " Returns the API response dict or an empty dict if something fails.\n",
+ " \"\"\"\n",
+ " if sp is None:\n",
+ " print(\"Spotify client is not available.\")\n",
+ " return {}\n",
+ "\n",
+ " try:\n",
+ " return sp.search(q=q, type=search_type, limit=limit)\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"SpotifyException during search | status={status} | query='{q}' | message={e}\")\n",
+ " return {}\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error during search: {e}\")\n",
+ " return {}\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error during search: {e}\")\n",
+ " return {}\n",
+ "\n",
+ "\n",
+ "def extract_track_summary(track: dict) -> dict:\n",
+ " \"\"\"\n",
+ " Extract a clean summary from a Spotify track object.\n",
+ " \"\"\"\n",
+ " if not track:\n",
+ " return {}\n",
+ "\n",
+ " return {\n",
+ " \"track_name\": track.get(\"name\"),\n",
+ " \"track_id\": track.get(\"id\"),\n",
+ " \"popularity\": track.get(\"popularity\"),\n",
+ " \"uri\": track.get(\"uri\"),\n",
+ " \"album_name\": track.get(\"album\", {}).get(\"name\"),\n",
+ " \"album_release_date\": track.get(\"album\", {}).get(\"release_date\"),\n",
+ " \"artists\": \", \".join([a.get(\"name\", \"\") for a in track.get(\"artists\", [])])\n",
+ " }"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23e74489",
+ "metadata": {},
+ "source": [
+ "## 6. Using the search method"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "31a6e7ee",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/search with Params: {'q': 'Lady Gaga', 'limit': 50, 'offset': 0, 'type': 'track', 'market': None} returned 400 due to Invalid limit\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SpotifyException during search | status=400 | query='Lady Gaga' | message=http status: 400, code: -1 - https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track:\n",
+ " Invalid limit, reason: None\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys([])"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results = safe_search(sp, q=\"Lady Gaga\", search_type=\"track\", limit=50)\n",
+ "results.keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4fb4d793",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['tracks'])"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results = safe_search(sp, q=\"Lady Gaga\", search_type=\"track\", limit=10)\n",
+ "results.keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "cbd04371",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['href', 'limit', 'next', 'offset', 'previous', 'total', 'items'])"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "410d2948",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'https://api.spotify.com/v1/search?offset=0&limit=10&query=Lady%20Gaga&type=track'"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"href\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2d8e2920",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'album': {'album_type': 'album',\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}],\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/album/1qwlxZTNLe1jq3b0iidlue'},\n",
+ " 'href': 'https://api.spotify.com/v1/albums/1qwlxZTNLe1jq3b0iidlue',\n",
+ " 'id': '1qwlxZTNLe1jq3b0iidlue',\n",
+ " 'images': [{'height': 640,\n",
+ " 'width': 640,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d0000b273e691217483df8798445c82e2'},\n",
+ " {'height': 300,\n",
+ " 'width': 300,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00001e02e691217483df8798445c82e2'},\n",
+ " {'height': 64,\n",
+ " 'width': 64,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00004851e691217483df8798445c82e2'}],\n",
+ " 'is_playable': True,\n",
+ " 'name': 'The Fame',\n",
+ " 'release_date': '2008-01-01',\n",
+ " 'release_date_precision': 'day',\n",
+ " 'total_tracks': 15,\n",
+ " 'type': 'album',\n",
+ " 'uri': 'spotify:album:1qwlxZTNLe1jq3b0iidlue'},\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}],\n",
+ " 'disc_number': 1,\n",
+ " 'duration_ms': 237200,\n",
+ " 'explicit': True,\n",
+ " 'external_ids': {'isrc': 'USUM70824409'},\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/track/1QV6tiMFM6fSOKOGLMHYYg'},\n",
+ " 'href': 'https://api.spotify.com/v1/tracks/1QV6tiMFM6fSOKOGLMHYYg',\n",
+ " 'id': '1QV6tiMFM6fSOKOGLMHYYg',\n",
+ " 'is_local': False,\n",
+ " 'is_playable': True,\n",
+ " 'name': 'Poker Face',\n",
+ " 'track_number': 4,\n",
+ " 'type': 'track',\n",
+ " 'uri': 'spotify:track:1QV6tiMFM6fSOKOGLMHYYg'},\n",
+ " {'album': {'album_type': 'single',\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'},\n",
+ " {'external_urls': {'spotify': 'https://open.spotify.com/artist/0du5cEVh5yTK9QJze8zA0C'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/0du5cEVh5yTK9QJze8zA0C',\n",
+ " 'id': '0du5cEVh5yTK9QJze8zA0C',\n",
+ " 'name': 'Bruno Mars',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:0du5cEVh5yTK9QJze8zA0C'}],\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/album/10FLjwfpbxLmW8c25Xyc2N'},\n",
+ " 'href': 'https://api.spotify.com/v1/albums/10FLjwfpbxLmW8c25Xyc2N',\n",
+ " 'id': '10FLjwfpbxLmW8c25Xyc2N',\n",
+ " 'images': [{'height': 640,\n",
+ " 'width': 640,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d0000b27382ea2e9e1858aa012c57cd45'},\n",
+ " {'height': 300,\n",
+ " 'width': 300,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00001e0282ea2e9e1858aa012c57cd45'},\n",
+ " {'height': 64,\n",
+ " 'width': 64,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d0000485182ea2e9e1858aa012c57cd45'}],\n",
+ " 'is_playable': True,\n",
+ " 'name': 'Die With A Smile',\n",
+ " 'release_date': '2024-08-16',\n",
+ " 'release_date_precision': 'day',\n",
+ " 'total_tracks': 1,\n",
+ " 'type': 'album',\n",
+ " 'uri': 'spotify:album:10FLjwfpbxLmW8c25Xyc2N'},\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'},\n",
+ " {'external_urls': {'spotify': 'https://open.spotify.com/artist/0du5cEVh5yTK9QJze8zA0C'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/0du5cEVh5yTK9QJze8zA0C',\n",
+ " 'id': '0du5cEVh5yTK9QJze8zA0C',\n",
+ " 'name': 'Bruno Mars',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:0du5cEVh5yTK9QJze8zA0C'}],\n",
+ " 'disc_number': 1,\n",
+ " 'duration_ms': 251667,\n",
+ " 'explicit': False,\n",
+ " 'external_ids': {'isrc': 'USUM72409273'},\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/track/2plbrEY59IikOBgBGLjaoe'},\n",
+ " 'href': 'https://api.spotify.com/v1/tracks/2plbrEY59IikOBgBGLjaoe',\n",
+ " 'id': '2plbrEY59IikOBgBGLjaoe',\n",
+ " 'is_local': False,\n",
+ " 'is_playable': True,\n",
+ " 'name': 'Die With A Smile',\n",
+ " 'track_number': 1,\n",
+ " 'type': 'track',\n",
+ " 'uri': 'spotify:track:2plbrEY59IikOBgBGLjaoe'},\n",
+ " {'album': {'album_type': 'album',\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}],\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/album/2MHUaRi9OCyTN02SoyRRBJ'},\n",
+ " 'href': 'https://api.spotify.com/v1/albums/2MHUaRi9OCyTN02SoyRRBJ',\n",
+ " 'id': '2MHUaRi9OCyTN02SoyRRBJ',\n",
+ " 'images': [{'height': 640,\n",
+ " 'width': 640,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d0000b273b0860cf0a98e09663c82290c'},\n",
+ " {'height': 300,\n",
+ " 'width': 300,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00001e02b0860cf0a98e09663c82290c'},\n",
+ " {'height': 64,\n",
+ " 'width': 64,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00004851b0860cf0a98e09663c82290c'}],\n",
+ " 'is_playable': True,\n",
+ " 'name': 'MAYHEM',\n",
+ " 'release_date': '2025-03-07',\n",
+ " 'release_date_precision': 'day',\n",
+ " 'total_tracks': 14,\n",
+ " 'type': 'album',\n",
+ " 'uri': 'spotify:album:2MHUaRi9OCyTN02SoyRRBJ'},\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}],\n",
+ " 'disc_number': 1,\n",
+ " 'duration_ms': 223398,\n",
+ " 'explicit': False,\n",
+ " 'external_ids': {'isrc': 'USUM72412581'},\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/track/2LHNTC9QZxsL3nWpt8iaSR'},\n",
+ " 'href': 'https://api.spotify.com/v1/tracks/2LHNTC9QZxsL3nWpt8iaSR',\n",
+ " 'id': '2LHNTC9QZxsL3nWpt8iaSR',\n",
+ " 'is_local': False,\n",
+ " 'is_playable': True,\n",
+ " 'name': 'Abracadabra',\n",
+ " 'track_number': 2,\n",
+ " 'type': 'track',\n",
+ " 'uri': 'spotify:track:2LHNTC9QZxsL3nWpt8iaSR'}]"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"items\", [])[:3]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6f82e71d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"limit\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e2dfca8d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'https://api.spotify.com/v1/search?offset=10&limit=10&query=Lady%20Gaga&type=track'"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"next\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7d97024f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"offset\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "953fb566",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"previous\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ae2820d6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "16"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "results.get(\"tracks\", {}).get(\"total\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7e120476",
+ "metadata": {},
+ "source": [
+ "## 7. Exploring the tracks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b8c27a4b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'album': {'album_type': 'album',\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}],\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/album/1qwlxZTNLe1jq3b0iidlue'},\n",
+ " 'href': 'https://api.spotify.com/v1/albums/1qwlxZTNLe1jq3b0iidlue',\n",
+ " 'id': '1qwlxZTNLe1jq3b0iidlue',\n",
+ " 'images': [{'height': 640,\n",
+ " 'width': 640,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d0000b273e691217483df8798445c82e2'},\n",
+ " {'height': 300,\n",
+ " 'width': 300,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00001e02e691217483df8798445c82e2'},\n",
+ " {'height': 64,\n",
+ " 'width': 64,\n",
+ " 'url': 'https://i.scdn.co/image/ab67616d00004851e691217483df8798445c82e2'}],\n",
+ " 'is_playable': True,\n",
+ " 'name': 'The Fame',\n",
+ " 'release_date': '2008-01-01',\n",
+ " 'release_date_precision': 'day',\n",
+ " 'total_tracks': 15,\n",
+ " 'type': 'album',\n",
+ " 'uri': 'spotify:album:1qwlxZTNLe1jq3b0iidlue'},\n",
+ " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}],\n",
+ " 'disc_number': 1,\n",
+ " 'duration_ms': 237200,\n",
+ " 'explicit': True,\n",
+ " 'external_ids': {'isrc': 'USUM70824409'},\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/track/1QV6tiMFM6fSOKOGLMHYYg'},\n",
+ " 'href': 'https://api.spotify.com/v1/tracks/1QV6tiMFM6fSOKOGLMHYYg',\n",
+ " 'id': '1QV6tiMFM6fSOKOGLMHYYg',\n",
+ " 'is_local': False,\n",
+ " 'is_playable': True,\n",
+ " 'name': 'Poker Face',\n",
+ " 'track_number': 4,\n",
+ " 'type': 'track',\n",
+ " 'uri': 'spotify:track:1QV6tiMFM6fSOKOGLMHYYg'}"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_track = results.get(\"tracks\", {}).get(\"items\", [None])[0]\n",
+ "first_track"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d89632d6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['album', 'artists', 'disc_number', 'duration_ms', 'explicit', 'external_ids', 'external_urls', 'href', 'id', 'is_local', 'is_playable', 'name', 'track_number', 'type', 'uri'])"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_track.keys() if first_track else \"No track found\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2df37558",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'external_urls': {'spotify': 'https://open.spotify.com/artist/1HY2Jd0NmPuamShAr6KMms'},\n",
+ " 'href': 'https://api.spotify.com/v1/artists/1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'id': '1HY2Jd0NmPuamShAr6KMms',\n",
+ " 'name': 'Lady Gaga',\n",
+ " 'type': 'artist',\n",
+ " 'uri': 'spotify:artist:1HY2Jd0NmPuamShAr6KMms'}]"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_track.get(\"artists\", []) if first_track else []"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1adc70b5",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Lady Gaga\n"
+ ]
+ }
+ ],
+ "source": [
+ "if first_track:\n",
+ " for artist in first_track.get(\"artists\", []):\n",
+ " print(artist.get(\"name\"))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9af65961",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'1QV6tiMFM6fSOKOGLMHYYg'"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_track.get(\"id\") if first_track else None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "bdde879e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Poker Face'"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_track.get(\"name\") if first_track else None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2f6b5d13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "first_track.get(\"popularity\") if first_track else None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25809a2c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'spotify:track:1QV6tiMFM6fSOKOGLMHYYg'"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_track.get(\"uri\") if first_track else None"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "950b759f",
+ "metadata": {},
+ "source": [
+ "## 8. Clean DataFrame from search results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a6f39fe5",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " track_name | \n",
+ " track_id | \n",
+ " popularity | \n",
+ " uri | \n",
+ " album_name | \n",
+ " album_release_date | \n",
+ " artists | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Poker Face | \n",
+ " 1QV6tiMFM6fSOKOGLMHYYg | \n",
+ " None | \n",
+ " spotify:track:1QV6tiMFM6fSOKOGLMHYYg | \n",
+ " The Fame | \n",
+ " 2008-01-01 | \n",
+ " Lady Gaga | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Die With A Smile | \n",
+ " 2plbrEY59IikOBgBGLjaoe | \n",
+ " None | \n",
+ " spotify:track:2plbrEY59IikOBgBGLjaoe | \n",
+ " Die With A Smile | \n",
+ " 2024-08-16 | \n",
+ " Lady Gaga, Bruno Mars | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Abracadabra | \n",
+ " 2LHNTC9QZxsL3nWpt8iaSR | \n",
+ " None | \n",
+ " spotify:track:2LHNTC9QZxsL3nWpt8iaSR | \n",
+ " MAYHEM | \n",
+ " 2025-03-07 | \n",
+ " Lady Gaga | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " Bad Romance | \n",
+ " 0SiywuOBRcynK0uKGWdCnn | \n",
+ " None | \n",
+ " spotify:track:0SiywuOBRcynK0uKGWdCnn | \n",
+ " The Fame Monster (Deluxe Edition) | \n",
+ " 2009-11-05 | \n",
+ " Lady Gaga | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " Shallow | \n",
+ " 2VxeLyX666F8uXCJ0dZF8B | \n",
+ " None | \n",
+ " spotify:track:2VxeLyX666F8uXCJ0dZF8B | \n",
+ " A Star Is Born Soundtrack | \n",
+ " 2018-10-05 | \n",
+ " Lady Gaga, Bradley Cooper | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Just Dance | \n",
+ " 1dzQoRqT5ucxXVaAhTcT0J | \n",
+ " None | \n",
+ " spotify:track:1dzQoRqT5ucxXVaAhTcT0J | \n",
+ " The Fame | \n",
+ " 2008-01-01 | \n",
+ " Lady Gaga, Colby O'Donis | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " Always Remember Us This Way | \n",
+ " 2rbDhOo9Fh61Bbu23T2qCk | \n",
+ " None | \n",
+ " spotify:track:2rbDhOo9Fh61Bbu23T2qCk | \n",
+ " A Star Is Born Soundtrack (Without Dialogue) | \n",
+ " 2018-10-05 | \n",
+ " Lady Gaga | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " Judas | \n",
+ " 7F25roCtYi55JouckaayPC | \n",
+ " None | \n",
+ " spotify:track:7F25roCtYi55JouckaayPC | \n",
+ " Born This Way (International Special Edition V... | \n",
+ " 2011-05-23 | \n",
+ " Lady Gaga | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " Abracadabra | \n",
+ " 5ZLUm9eab8y3tqQ1OhQSHI | \n",
+ " None | \n",
+ " spotify:track:5ZLUm9eab8y3tqQ1OhQSHI | \n",
+ " Abracadabra | \n",
+ " 2025-02-03 | \n",
+ " Lady Gaga | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " Telephone | \n",
+ " 4TCL0qqKyqsMZml0G3M9IM | \n",
+ " None | \n",
+ " spotify:track:4TCL0qqKyqsMZml0G3M9IM | \n",
+ " The Fame Monster (Deluxe Edition) | \n",
+ " 2009-11-05 | \n",
+ " Lady Gaga, Beyoncé | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " track_name track_id popularity \\\n",
+ "0 Poker Face 1QV6tiMFM6fSOKOGLMHYYg None \n",
+ "1 Die With A Smile 2plbrEY59IikOBgBGLjaoe None \n",
+ "2 Abracadabra 2LHNTC9QZxsL3nWpt8iaSR None \n",
+ "3 Bad Romance 0SiywuOBRcynK0uKGWdCnn None \n",
+ "4 Shallow 2VxeLyX666F8uXCJ0dZF8B None \n",
+ "5 Just Dance 1dzQoRqT5ucxXVaAhTcT0J None \n",
+ "6 Always Remember Us This Way 2rbDhOo9Fh61Bbu23T2qCk None \n",
+ "7 Judas 7F25roCtYi55JouckaayPC None \n",
+ "8 Abracadabra 5ZLUm9eab8y3tqQ1OhQSHI None \n",
+ "9 Telephone 4TCL0qqKyqsMZml0G3M9IM None \n",
+ "\n",
+ " uri \\\n",
+ "0 spotify:track:1QV6tiMFM6fSOKOGLMHYYg \n",
+ "1 spotify:track:2plbrEY59IikOBgBGLjaoe \n",
+ "2 spotify:track:2LHNTC9QZxsL3nWpt8iaSR \n",
+ "3 spotify:track:0SiywuOBRcynK0uKGWdCnn \n",
+ "4 spotify:track:2VxeLyX666F8uXCJ0dZF8B \n",
+ "5 spotify:track:1dzQoRqT5ucxXVaAhTcT0J \n",
+ "6 spotify:track:2rbDhOo9Fh61Bbu23T2qCk \n",
+ "7 spotify:track:7F25roCtYi55JouckaayPC \n",
+ "8 spotify:track:5ZLUm9eab8y3tqQ1OhQSHI \n",
+ "9 spotify:track:4TCL0qqKyqsMZml0G3M9IM \n",
+ "\n",
+ " album_name album_release_date \\\n",
+ "0 The Fame 2008-01-01 \n",
+ "1 Die With A Smile 2024-08-16 \n",
+ "2 MAYHEM 2025-03-07 \n",
+ "3 The Fame Monster (Deluxe Edition) 2009-11-05 \n",
+ "4 A Star Is Born Soundtrack 2018-10-05 \n",
+ "5 The Fame 2008-01-01 \n",
+ "6 A Star Is Born Soundtrack (Without Dialogue) 2018-10-05 \n",
+ "7 Born This Way (International Special Edition V... 2011-05-23 \n",
+ "8 Abracadabra 2025-02-03 \n",
+ "9 The Fame Monster (Deluxe Edition) 2009-11-05 \n",
+ "\n",
+ " artists \n",
+ "0 Lady Gaga \n",
+ "1 Lady Gaga, Bruno Mars \n",
+ "2 Lady Gaga \n",
+ "3 Lady Gaga \n",
+ "4 Lady Gaga, Bradley Cooper \n",
+ "5 Lady Gaga, Colby O'Donis \n",
+ "6 Lady Gaga \n",
+ "7 Lady Gaga \n",
+ "8 Lady Gaga \n",
+ "9 Lady Gaga, Beyoncé "
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "track_items = results.get(\"tracks\", {}).get(\"items\", [])\n",
+ "tracks_df = pd.DataFrame([extract_track_summary(track) for track in track_items])\n",
+ "tracks_df.head(10)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b3dda129",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(10, 7)"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "tracks_df.shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1280878f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " track_name | \n",
+ " artists | \n",
+ " album_name | \n",
+ " popularity | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Poker Face | \n",
+ " Lady Gaga | \n",
+ " The Fame | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Die With A Smile | \n",
+ " Lady Gaga, Bruno Mars | \n",
+ " Die With A Smile | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Abracadabra | \n",
+ " Lady Gaga | \n",
+ " MAYHEM | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " Bad Romance | \n",
+ " Lady Gaga | \n",
+ " The Fame Monster (Deluxe Edition) | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " Shallow | \n",
+ " Lady Gaga, Bradley Cooper | \n",
+ " A Star Is Born Soundtrack | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Just Dance | \n",
+ " Lady Gaga, Colby O'Donis | \n",
+ " The Fame | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " Always Remember Us This Way | \n",
+ " Lady Gaga | \n",
+ " A Star Is Born Soundtrack (Without Dialogue) | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " Judas | \n",
+ " Lady Gaga | \n",
+ " Born This Way (International Special Edition V... | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " Abracadabra | \n",
+ " Lady Gaga | \n",
+ " Abracadabra | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " Telephone | \n",
+ " Lady Gaga, Beyoncé | \n",
+ " The Fame Monster (Deluxe Edition) | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " track_name artists \\\n",
+ "0 Poker Face Lady Gaga \n",
+ "1 Die With A Smile Lady Gaga, Bruno Mars \n",
+ "2 Abracadabra Lady Gaga \n",
+ "3 Bad Romance Lady Gaga \n",
+ "4 Shallow Lady Gaga, Bradley Cooper \n",
+ "5 Just Dance Lady Gaga, Colby O'Donis \n",
+ "6 Always Remember Us This Way Lady Gaga \n",
+ "7 Judas Lady Gaga \n",
+ "8 Abracadabra Lady Gaga \n",
+ "9 Telephone Lady Gaga, Beyoncé \n",
+ "\n",
+ " album_name popularity \n",
+ "0 The Fame None \n",
+ "1 Die With A Smile None \n",
+ "2 MAYHEM None \n",
+ "3 The Fame Monster (Deluxe Edition) None \n",
+ "4 A Star Is Born Soundtrack None \n",
+ "5 The Fame None \n",
+ "6 A Star Is Born Soundtrack (Without Dialogue) None \n",
+ "7 Born This Way (International Special Edition V... None \n",
+ "8 Abracadabra None \n",
+ "9 The Fame Monster (Deluxe Edition) None "
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "tracks_df[[\"track_name\", \"artists\", \"album_name\", \"popularity\"]].head(10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "89291bdb",
+ "metadata": {},
+ "source": [
+ "## Exercise 1: Discovering New Music through Your Favorite Artists"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a3e7509e",
+ "metadata": {},
+ "source": [
+ "### 9. Favorite artists list"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "91c1c6a8",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['Los Fabulosos Cadillacs', 'Manu Chao', 'Muchachito Bombo Infierno']"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "artists = [\"Los Fabulosos Cadillacs\", \"Manu Chao\", \"Muchachito Bombo Infierno\"]\n",
+ "artists"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8d099578",
+ "metadata": {},
+ "source": [
+ "### 10. Helper: get artist ID safely"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "291fefd7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_artist_id(artist_name: str):\n",
+ " \"\"\"\n",
+ " Search for an artist and return the first matching artist ID.\n",
+ " \"\"\"\n",
+ " result = safe_search(sp, q=artist_name, search_type=\"artist\", limit=1)\n",
+ " items = result.get(\"artists\", {}).get(\"items\", [])\n",
+ "\n",
+ " if not items:\n",
+ " print(f\"No artist found for: {artist_name}\")\n",
+ " return None\n",
+ "\n",
+ " return items[0].get(\"id\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0cd51048",
+ "metadata": {},
+ "source": [
+ "### 11. Function: get_top_tracks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "cb73ab5c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_top_tracks(artist_name: str, limit: int = 5, market: str = \"ES\") -> list:\n",
+ " \"\"\"\n",
+ " Return the first `limit` top tracks for a given artist.\n",
+ " Uses artist ID + artist_top_tracks when available.\n",
+ " Includes fallback and error handling.\n",
+ " \"\"\"\n",
+ " artist_id = get_artist_id(artist_name)\n",
+ "\n",
+ " if not artist_id:\n",
+ " return []\n",
+ "\n",
+ " try:\n",
+ " top_tracks = sp.artist_top_tracks(artist_id, country=market)\n",
+ " tracks = top_tracks.get(\"tracks\", [])\n",
+ "\n",
+ " track_names = []\n",
+ " for track in tracks[:limit]:\n",
+ " name = track.get(\"name\")\n",
+ " if name:\n",
+ " track_names.append(name)\n",
+ "\n",
+ " return track_names\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"SpotifyException | top tracks failed for '{artist_name}' | status={status} | message={e}\")\n",
+ "\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error while fetching top tracks for '{artist_name}': {e}\")\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error while fetching top tracks for '{artist_name}': {e}\")\n",
+ "\n",
+ " # Fallback: use search if artist_top_tracks fails\n",
+ " try:\n",
+ " result = safe_search(sp, q=f\"artist:{artist_name}\", search_type=\"track\", limit=10)\n",
+ " items = result.get(\"tracks\", {}).get(\"items\", [])\n",
+ "\n",
+ " filtered = []\n",
+ " for t in items:\n",
+ " artists_in_track = [a.get(\"name\", \"\").lower() for a in t.get(\"artists\", [])]\n",
+ " if artist_name.lower() in artists_in_track:\n",
+ " filtered.append(t.get(\"name\"))\n",
+ "\n",
+ " # Remove duplicates while preserving order\n",
+ " seen = set()\n",
+ " cleaned = []\n",
+ " for t in filtered:\n",
+ " if t and t.lower() not in seen:\n",
+ " seen.add(t.lower())\n",
+ " cleaned.append(t)\n",
+ "\n",
+ " return cleaned[:limit]\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"Fallback search also failed for '{artist_name}': {e}\")\n",
+ " return []"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "85ce7cfa",
+ "metadata": {},
+ "source": [
+ "### 12. Get top tracks for all favorite artists"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a3c79a54",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/artists/2FS22haX3FYbyOsUAkuYqZ/top-tracks with Params: {'country': 'ES'} returned 403 due to Forbidden\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SpotifyException | top tracks failed for 'Los Fabulosos Cadillacs' | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/artists/2FS22haX3FYbyOsUAkuYqZ/top-tracks?country=ES:\n",
+ " Forbidden, reason: None\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/artists/6wH6iStAh4KIaWfuhf0NYM/top-tracks with Params: {'country': 'ES'} returned 403 due to Forbidden\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SpotifyException | top tracks failed for 'Manu Chao' | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/artists/6wH6iStAh4KIaWfuhf0NYM/top-tracks?country=ES:\n",
+ " Forbidden, reason: None\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/artists/6529KpSlnP8S0kXhx7ayBb/top-tracks with Params: {'country': 'ES'} returned 403 due to Forbidden\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SpotifyException | top tracks failed for 'Muchachito Bombo Infierno' | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/artists/6529KpSlnP8S0kXhx7ayBb/top-tracks?country=ES:\n",
+ " Forbidden, reason: None\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[['Siguiendo la Luna - Remasterizado 2008',\n",
+ " 'Mal Bicho - Remasterizado 2008',\n",
+ " 'Manuel Santillán, el León (Versión Reggae) - Remasterizado 2008',\n",
+ " 'Matador - Remasterizado 2008',\n",
+ " 'Vasos Vacíos - Remasterizado 2008'],\n",
+ " ['Me Gustas Tu', 'Bongo Bong', \"Je ne t'aime plus\", 'A me mi piace'],\n",
+ " ['Siempre que quiera', 'Luna', 'Caraguapa', 'La quiero a morir', 'Tu nombre']]"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "top_tracks_list = [get_top_tracks(artist, limit=5) for artist in artists]\n",
+ "top_tracks_list"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "65ef4500",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Artist: Los Fabulosos Cadillacs\n",
+ " - Siguiendo la Luna - Remasterizado 2008\n",
+ " - Mal Bicho - Remasterizado 2008\n",
+ " - Manuel Santillán, el León (Versión Reggae) - Remasterizado 2008\n",
+ " - Matador - Remasterizado 2008\n",
+ " - Vasos Vacíos - Remasterizado 2008\n",
+ "\n",
+ "Artist: Manu Chao\n",
+ " - Me Gustas Tu\n",
+ " - Bongo Bong\n",
+ " - Je ne t'aime plus\n",
+ " - A me mi piace\n",
+ "\n",
+ "Artist: Muchachito Bombo Infierno\n",
+ " - Siempre que quiera\n",
+ " - Luna\n",
+ " - Caraguapa\n",
+ " - La quiero a morir\n",
+ " - Tu nombre\n"
+ ]
+ }
+ ],
+ "source": [
+ "for artist, tracks in zip(artists, top_tracks_list):\n",
+ " print(f\"\\nArtist: {artist}\")\n",
+ " if tracks:\n",
+ " for track in tracks:\n",
+ " print(f\" - {track}\")\n",
+ " else:\n",
+ " print(\" - No tracks found\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5b24e0cb",
+ "metadata": {},
+ "source": [
+ "### 13. Function: find_related_artists"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6fb16584",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def find_related_artists(artist_name: str, limit: int = 5) -> list:\n",
+ " \"\"\"\n",
+ " Return the first `limit` related artists for a given artist.\n",
+ " Uses artist_related_artists when available.\n",
+ " If it fails, falls back to recommendations seeded by the artist.\n",
+ " \"\"\"\n",
+ " artist_id = get_artist_id(artist_name)\n",
+ "\n",
+ " if not artist_id:\n",
+ " return []\n",
+ "\n",
+ " # First attempt: artist_related_artists\n",
+ " try:\n",
+ " related = sp.artist_related_artists(artist_id)\n",
+ " items = related.get(\"artists\", [])\n",
+ "\n",
+ " names = []\n",
+ " for artist in items[:limit]:\n",
+ " name = artist.get(\"name\")\n",
+ " if name:\n",
+ " names.append(name)\n",
+ "\n",
+ " if names:\n",
+ " return names\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"artist_related_artists failed for '{artist_name}' | status={status} | message={e}\")\n",
+ "\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error while fetching related artists for '{artist_name}': {e}\")\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error in related artists for '{artist_name}': {e}\")\n",
+ "\n",
+ " # Fallback: recommendations\n",
+ " try:\n",
+ " recs = sp.recommendations(seed_artists=[artist_id], limit=50)\n",
+ " tracks = recs.get(\"tracks\", [])\n",
+ "\n",
+ " related_names = []\n",
+ " seen = set()\n",
+ " original_lower = artist_name.lower()\n",
+ "\n",
+ " for track in tracks:\n",
+ " for artist in track.get(\"artists\", []):\n",
+ " name = artist.get(\"name\", \"\")\n",
+ " lower_name = name.lower()\n",
+ "\n",
+ " if lower_name == original_lower:\n",
+ " continue\n",
+ " if lower_name in seen:\n",
+ " continue\n",
+ "\n",
+ " seen.add(lower_name)\n",
+ " related_names.append(name)\n",
+ "\n",
+ " if len(related_names) >= limit:\n",
+ " return related_names\n",
+ "\n",
+ " return related_names\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"recommendations fallback failed for '{artist_name}' | status={status} | message={e}\")\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error in recommendations fallback for '{artist_name}': {e}\")\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error in recommendations fallback for '{artist_name}': {e}\")\n",
+ "\n",
+ " return []"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f1506e2f",
+ "metadata": {},
+ "source": [
+ "### 14. Get related artists for all favorite artists"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8d448096",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/artists/2FS22haX3FYbyOsUAkuYqZ/related-artists with Params: {} returned 403 due to Forbidden\n",
+ "HTTP Error for GET to https://api.spotify.com/v1/recommendations with Params: {'limit': 50, 'seed_artists': '2FS22haX3FYbyOsUAkuYqZ'} returned 404 due to None\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "artist_related_artists failed for 'Los Fabulosos Cadillacs' | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/artists/2FS22haX3FYbyOsUAkuYqZ/related-artists:\n",
+ " Forbidden, reason: None\n",
+ "recommendations fallback failed for 'Los Fabulosos Cadillacs' | status=404 | message=http status: 404, code: -1 - https://api.spotify.com/v1/recommendations?limit=50&seed_artists=2FS22haX3FYbyOsUAkuYqZ:\n",
+ " None, reason: None\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/artists/6wH6iStAh4KIaWfuhf0NYM/related-artists with Params: {} returned 403 due to Forbidden\n",
+ "HTTP Error for GET to https://api.spotify.com/v1/recommendations with Params: {'limit': 50, 'seed_artists': '6wH6iStAh4KIaWfuhf0NYM'} returned 404 due to None\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "artist_related_artists failed for 'Manu Chao' | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/artists/6wH6iStAh4KIaWfuhf0NYM/related-artists:\n",
+ " Forbidden, reason: None\n",
+ "recommendations fallback failed for 'Manu Chao' | status=404 | message=http status: 404, code: -1 - https://api.spotify.com/v1/recommendations?limit=50&seed_artists=6wH6iStAh4KIaWfuhf0NYM:\n",
+ " None, reason: None\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/artists/6529KpSlnP8S0kXhx7ayBb/related-artists with Params: {} returned 403 due to Forbidden\n",
+ "HTTP Error for GET to https://api.spotify.com/v1/recommendations with Params: {'limit': 50, 'seed_artists': '6529KpSlnP8S0kXhx7ayBb'} returned 404 due to None\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "artist_related_artists failed for 'Muchachito Bombo Infierno' | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/artists/6529KpSlnP8S0kXhx7ayBb/related-artists:\n",
+ " Forbidden, reason: None\n",
+ "recommendations fallback failed for 'Muchachito Bombo Infierno' | status=404 | message=http status: 404, code: -1 - https://api.spotify.com/v1/recommendations?limit=50&seed_artists=6529KpSlnP8S0kXhx7ayBb:\n",
+ " None, reason: None\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[[], [], []]"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "related_artists_list = [find_related_artists(artist, limit=5) for artist in artists]\n",
+ "related_artists_list"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9a110941",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Artist: Los Fabulosos Cadillacs\n",
+ " - No related artists found\n",
+ "\n",
+ "Artist: Manu Chao\n",
+ " - No related artists found\n",
+ "\n",
+ "Artist: Muchachito Bombo Infierno\n",
+ " - No related artists found\n"
+ ]
+ }
+ ],
+ "source": [
+ "for artist, related in zip(artists, related_artists_list):\n",
+ " print(f\"\\nArtist: {artist}\")\n",
+ " if related:\n",
+ " for r in related:\n",
+ " print(f\" - {r}\")\n",
+ " else:\n",
+ " print(\" - No related artists found\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ff40a4f5",
+ "metadata": {},
+ "source": [
+ "### 15. Challenge: create a discovery playlist structure"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4df6cc95",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "playlist_tracks = []\n",
+ "\n",
+ "# Add top tracks from favorite artists\n",
+ "for artist, track_list in zip(artists, top_tracks_list):\n",
+ " for track in track_list:\n",
+ " playlist_tracks.append({\n",
+ " \"artist\": artist,\n",
+ " \"track\": track,\n",
+ " \"source\": \"favorite_artist\"\n",
+ " })\n",
+ "\n",
+ "# Add top tracks from related artists\n",
+ "for related_group in related_artists_list:\n",
+ " for related_artist in related_group:\n",
+ " related_top_tracks = get_top_tracks(related_artist, limit=2)\n",
+ " for track in related_top_tracks:\n",
+ " playlist_tracks.append({\n",
+ " \"artist\": related_artist,\n",
+ " \"track\": track,\n",
+ " \"source\": \"related_artist\"\n",
+ " })"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d52d5f24",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " artist | \n",
+ " track | \n",
+ " source | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Siguiendo la Luna - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Mal Bicho - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Manuel Santillán, el León (Versión Reggae) - R... | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Matador - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Vasos Vacíos - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Manu Chao | \n",
+ " Me Gustas Tu | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " Manu Chao | \n",
+ " Bongo Bong | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " Manu Chao | \n",
+ " Je ne t'aime plus | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " Manu Chao | \n",
+ " A me mi piace | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Siempre que quiera | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 10 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Luna | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 11 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Caraguapa | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 12 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " La quiero a morir | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 13 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Tu nombre | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " artist \\\n",
+ "0 Los Fabulosos Cadillacs \n",
+ "1 Los Fabulosos Cadillacs \n",
+ "2 Los Fabulosos Cadillacs \n",
+ "3 Los Fabulosos Cadillacs \n",
+ "4 Los Fabulosos Cadillacs \n",
+ "5 Manu Chao \n",
+ "6 Manu Chao \n",
+ "7 Manu Chao \n",
+ "8 Manu Chao \n",
+ "9 Muchachito Bombo Infierno \n",
+ "10 Muchachito Bombo Infierno \n",
+ "11 Muchachito Bombo Infierno \n",
+ "12 Muchachito Bombo Infierno \n",
+ "13 Muchachito Bombo Infierno \n",
+ "\n",
+ " track source \n",
+ "0 Siguiendo la Luna - Remasterizado 2008 favorite_artist \n",
+ "1 Mal Bicho - Remasterizado 2008 favorite_artist \n",
+ "2 Manuel Santillán, el León (Versión Reggae) - R... favorite_artist \n",
+ "3 Matador - Remasterizado 2008 favorite_artist \n",
+ "4 Vasos Vacíos - Remasterizado 2008 favorite_artist \n",
+ "5 Me Gustas Tu favorite_artist \n",
+ "6 Bongo Bong favorite_artist \n",
+ "7 Je ne t'aime plus favorite_artist \n",
+ "8 A me mi piace favorite_artist \n",
+ "9 Siempre que quiera favorite_artist \n",
+ "10 Luna favorite_artist \n",
+ "11 Caraguapa favorite_artist \n",
+ "12 La quiero a morir favorite_artist \n",
+ "13 Tu nombre favorite_artist "
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist_df = pd.DataFrame(playlist_tracks)\n",
+ "playlist_df.head(20)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e5d7125b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(14, 3)"
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist_df.shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "222f7b8e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " artist | \n",
+ " track | \n",
+ " source | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Siguiendo la Luna - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Mal Bicho - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Manuel Santillán, el León (Versión Reggae) - R... | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Matador - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " Los Fabulosos Cadillacs | \n",
+ " Vasos Vacíos - Remasterizado 2008 | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Manu Chao | \n",
+ " Me Gustas Tu | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " Manu Chao | \n",
+ " Bongo Bong | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " Manu Chao | \n",
+ " Je ne t'aime plus | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " Manu Chao | \n",
+ " A me mi piace | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Siempre que quiera | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 10 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Luna | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 11 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Caraguapa | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 12 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " La quiero a morir | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ " | 13 | \n",
+ " Muchachito Bombo Infierno | \n",
+ " Tu nombre | \n",
+ " favorite_artist | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " artist \\\n",
+ "0 Los Fabulosos Cadillacs \n",
+ "1 Los Fabulosos Cadillacs \n",
+ "2 Los Fabulosos Cadillacs \n",
+ "3 Los Fabulosos Cadillacs \n",
+ "4 Los Fabulosos Cadillacs \n",
+ "5 Manu Chao \n",
+ "6 Manu Chao \n",
+ "7 Manu Chao \n",
+ "8 Manu Chao \n",
+ "9 Muchachito Bombo Infierno \n",
+ "10 Muchachito Bombo Infierno \n",
+ "11 Muchachito Bombo Infierno \n",
+ "12 Muchachito Bombo Infierno \n",
+ "13 Muchachito Bombo Infierno \n",
+ "\n",
+ " track source \n",
+ "0 Siguiendo la Luna - Remasterizado 2008 favorite_artist \n",
+ "1 Mal Bicho - Remasterizado 2008 favorite_artist \n",
+ "2 Manuel Santillán, el León (Versión Reggae) - R... favorite_artist \n",
+ "3 Matador - Remasterizado 2008 favorite_artist \n",
+ "4 Vasos Vacíos - Remasterizado 2008 favorite_artist \n",
+ "5 Me Gustas Tu favorite_artist \n",
+ "6 Bongo Bong favorite_artist \n",
+ "7 Je ne t'aime plus favorite_artist \n",
+ "8 A me mi piace favorite_artist \n",
+ "9 Siempre que quiera favorite_artist \n",
+ "10 Luna favorite_artist \n",
+ "11 Caraguapa favorite_artist \n",
+ "12 La quiero a morir favorite_artist \n",
+ "13 Tu nombre favorite_artist "
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist_df.drop_duplicates(subset=[\"artist\", \"track\"]).reset_index(drop=True).head(30)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7bb99875",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Generated discovery playlist:\n",
+ "Los Fabulosos Cadillacs - Siguiendo la Luna - Remasterizado 2008 (favorite_artist)\n",
+ "Los Fabulosos Cadillacs - Mal Bicho - Remasterizado 2008 (favorite_artist)\n",
+ "Los Fabulosos Cadillacs - Manuel Santillán, el León (Versión Reggae) - Remasterizado 2008 (favorite_artist)\n",
+ "Los Fabulosos Cadillacs - Matador - Remasterizado 2008 (favorite_artist)\n",
+ "Los Fabulosos Cadillacs - Vasos Vacíos - Remasterizado 2008 (favorite_artist)\n",
+ "Manu Chao - Me Gustas Tu (favorite_artist)\n",
+ "Manu Chao - Bongo Bong (favorite_artist)\n",
+ "Manu Chao - Je ne t'aime plus (favorite_artist)\n",
+ "Manu Chao - A me mi piace (favorite_artist)\n",
+ "Muchachito Bombo Infierno - Siempre que quiera (favorite_artist)\n",
+ "Muchachito Bombo Infierno - Luna (favorite_artist)\n",
+ "Muchachito Bombo Infierno - Caraguapa (favorite_artist)\n",
+ "Muchachito Bombo Infierno - La quiero a morir (favorite_artist)\n",
+ "Muchachito Bombo Infierno - Tu nombre (favorite_artist)\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"Generated discovery playlist:\")\n",
+ "for _, row in playlist_df.drop_duplicates(subset=[\"artist\", \"track\"]).iterrows():\n",
+ " print(f\"{row['artist']} - {row['track']} ({row['source']})\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "44acfa6c",
+ "metadata": {},
+ "source": [
+ "## Playlists"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ab713c3f",
+ "metadata": {},
+ "source": [
+ "### 16. Safe playlist helpers"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8e7c77ec",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def safe_featured_playlists(limit: int = 5):\n",
+ " \"\"\"\n",
+ " Try to fetch Spotify featured playlists.\n",
+ " If the endpoint is unavailable or forbidden, return an empty list.\n",
+ " \"\"\"\n",
+ " try:\n",
+ " results = sp.featured_playlists(limit=limit)\n",
+ " items = results.get(\"playlists\", {}).get(\"items\", [])\n",
+ "\n",
+ " cleaned = []\n",
+ " for pl in items:\n",
+ " if not pl:\n",
+ " continue\n",
+ " cleaned.append({\n",
+ " \"name\": pl.get(\"name\"),\n",
+ " \"id\": pl.get(\"id\")\n",
+ " })\n",
+ "\n",
+ " return cleaned\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"featured_playlists failed | status={status} | message={e}\")\n",
+ " return []\n",
+ "\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error while fetching featured playlists: {e}\")\n",
+ " return []\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error while fetching featured playlists: {e}\")\n",
+ " return []"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "04926ae5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def search_playlists_alternative(query: str = \"Top Hits\", limit: int = 5):\n",
+ " \"\"\"\n",
+ " Fallback method for playlists using search instead of featured_playlists.\n",
+ " This is useful if featured_playlists is unavailable or restricted.\n",
+ " \"\"\"\n",
+ " result = safe_search(sp, q=query, search_type=\"playlist\", limit=limit)\n",
+ " items = result.get(\"playlists\", {}).get(\"items\", [])\n",
+ "\n",
+ " cleaned = []\n",
+ " for pl in items:\n",
+ " if not pl:\n",
+ " continue\n",
+ " cleaned.append({\n",
+ " \"name\": pl.get(\"name\"),\n",
+ " \"id\": pl.get(\"id\")\n",
+ " })\n",
+ "\n",
+ " return cleaned"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4e274780",
+ "metadata": {},
+ "source": [
+ "### 17. Exercise 2-Featured Exploration"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dc70574a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/browse/featured-playlists with Params: {'locale': None, 'country': None, 'timestamp': None, 'limit': 5, 'offset': 0} returned 403 due to Forbidden\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "featured_playlists failed | status=403 | message=http status: 403, code: -1 - https://api.spotify.com/v1/browse/featured-playlists?limit=5&offset=0:\n",
+ " Forbidden, reason: None\n",
+ "featured_playlists was unavailable. Using search-based playlist exploration instead.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[{'name': 'Hits 2026 🔥 🔥 Aktuelle Charts und die besten Songs 2026 🔝 🙌',\n",
+ " 'id': '1CnDCN10TJZjw6K2H3gNRv'},\n",
+ " {'name': 'Hits 2026 🌼 Top 100 Charts & Radio Songs · Mega Hits ',\n",
+ " 'id': '54kK2yufF2hgWtzI8hhBeH'}]"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "featured = safe_featured_playlists(limit=5)\n",
+ "\n",
+ "if not featured:\n",
+ " print(\"featured_playlists was unavailable. Using search-based playlist exploration instead.\")\n",
+ " featured = search_playlists_alternative(query=\"Top Hits\", limit=5)\n",
+ "\n",
+ "featured"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "80e3b472",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " name | \n",
+ " id | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Hits 2026 🔥 🔥 Aktuelle Charts und die besten ... | \n",
+ " 1CnDCN10TJZjw6K2H3gNRv | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Hits 2026 🌼 Top 100 Charts & Radio Songs · Meg... | \n",
+ " 54kK2yufF2hgWtzI8hhBeH | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " name id\n",
+ "0 Hits 2026 🔥 🔥 Aktuelle Charts und die besten ... 1CnDCN10TJZjw6K2H3gNRv\n",
+ "1 Hits 2026 🌼 Top 100 Charts & Radio Songs · Meg... 54kK2yufF2hgWtzI8hhBeH"
+ ]
+ },
+ "execution_count": 47,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "featured_df = pd.DataFrame(featured)\n",
+ "featured_df"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "72db03b1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Top 5 playlists:\n",
+ "Name: Hits 2026 🔥 🔥 Aktuelle Charts und die besten Songs 2026 🔝 🙌 | ID: 1CnDCN10TJZjw6K2H3gNRv\n",
+ "Name: Hits 2026 🌼 Top 100 Charts & Radio Songs · Mega Hits | ID: 54kK2yufF2hgWtzI8hhBeH\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"Top 5 playlists:\")\n",
+ "for pl in featured:\n",
+ " print(f\"Name: {pl['name']} | ID: {pl['id']}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "70467456",
+ "metadata": {},
+ "source": [
+ "### 18. Choose one playlist"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "43b1bbc1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'1CnDCN10TJZjw6K2H3gNRv'"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist_id = featured[0][\"id\"] if featured else None\n",
+ "playlist_id"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11120ebd",
+ "metadata": {},
+ "source": [
+ "### 19. Safe playlist details"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "cb09e553",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def safe_get_playlist_details(playlist_id: str):\n",
+ " \"\"\"\n",
+ " Fetch playlist details safely.\n",
+ " Returns a dict or None.\n",
+ " \"\"\"\n",
+ " if not playlist_id:\n",
+ " print(\"No playlist_id provided.\")\n",
+ " return None\n",
+ "\n",
+ " try:\n",
+ " playlist = sp.playlist(playlist_id)\n",
+ " return playlist\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"Playlist details failed | status={status} | playlist_id={playlist_id} | message={e}\")\n",
+ " return None\n",
+ "\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error while fetching playlist details: {e}\")\n",
+ " return None\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error while fetching playlist details: {e}\")\n",
+ " return None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f0c29779",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'collaborative': False,\n",
+ " 'description': 'Entdecke die ultimative Playlist mit den heißesten und neuesten Hits! Hier findest du alle Top-Songs aus dem Radio und den Charts, einschließlich Nina Chuba, Apache207, Miley Cyrus, Harry Styles und vielen anderen.',\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/playlist/1CnDCN10TJZjw6K2H3gNRv'},\n",
+ " 'followers': {'href': None, 'total': 151874},\n",
+ " 'href': 'https://api.spotify.com/v1/playlists/1CnDCN10TJZjw6K2H3gNRv?additional_types=track',\n",
+ " 'id': '1CnDCN10TJZjw6K2H3gNRv',\n",
+ " 'images': [{'height': None,\n",
+ " 'url': 'https://image-cdn-ak.spotifycdn.com/image/ab67706c0000da842efcb7c0773121dd6140284b',\n",
+ " 'width': None}],\n",
+ " 'name': 'Hits 2026 🔥 🔥 Aktuelle Charts und die besten Songs 2026 🔝 🙌',\n",
+ " 'owner': {'display_name': 'Filtr Germany',\n",
+ " 'external_urls': {'spotify': 'https://open.spotify.com/user/playlists.germany'},\n",
+ " 'href': 'https://api.spotify.com/v1/users/playlists.germany',\n",
+ " 'id': 'playlists.germany',\n",
+ " 'type': 'user',\n",
+ " 'uri': 'spotify:user:playlists.germany'},\n",
+ " 'primary_color': None,\n",
+ " 'public': True,\n",
+ " 'snapshot_id': 'AAAGJ/c2pfu6PN0Q5RSBfyeGXMhuTebg',\n",
+ " 'type': 'playlist',\n",
+ " 'uri': 'spotify:playlist:1CnDCN10TJZjw6K2H3gNRv'}"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist = safe_get_playlist_details(playlist_id)\n",
+ "playlist"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "62a2e35c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Playlist name: Hits 2026 🔥 🔥 Aktuelle Charts und die besten Songs 2026 🔝 🙌\n",
+ "Description: Entdecke die ultimative Playlist mit den heißesten und neuesten Hits! Hier findest du alle Top-Songs aus dem Radio und den Charts, einschließlich Nina Chuba, Apache207, Miley Cyrus, Harry Styles und vielen anderen.\n",
+ "Total tracks: None\n"
+ ]
+ }
+ ],
+ "source": [
+ "if playlist:\n",
+ " print(\"Playlist name:\", playlist.get(\"name\"))\n",
+ " print(\"Description:\", playlist.get(\"description\"))\n",
+ " print(\"Total tracks:\", playlist.get(\"tracks\", {}).get(\"total\"))\n",
+ "else:\n",
+ " print(\"Could not retrieve playlist details.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9b6af331",
+ "metadata": {},
+ "source": [
+ "### 20. Safe playlist tracks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27c31068",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def safe_get_playlist_tracks(playlist_id: str, limit: int = 10):\n",
+ " \"\"\"\n",
+ " Fetch playlist tracks safely.\n",
+ " Returns a list of playlist items.\n",
+ " \"\"\"\n",
+ " if not playlist_id:\n",
+ " print(\"No playlist_id provided.\")\n",
+ " return []\n",
+ "\n",
+ " try:\n",
+ " results = sp.playlist_items(playlist_id, limit=limit)\n",
+ " return results.get(\"items\", [])\n",
+ "\n",
+ " except SpotifyException as e:\n",
+ " status = getattr(e, \"http_status\", \"N/A\")\n",
+ " print(f\"playlist_items failed | status={status} | playlist_id={playlist_id} | message={e}\")\n",
+ " return []\n",
+ "\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Network error while fetching playlist items: {e}\")\n",
+ " return []\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"Unexpected error while fetching playlist items: {e}\")\n",
+ " return []"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9be692fc",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "HTTP Error for GET to https://api.spotify.com/v1/playlists/1CnDCN10TJZjw6K2H3gNRv/tracks with Params: {'limit': 10, 'offset': 0, 'fields': None, 'market': None, 'additional_types': 'track,episode'} returned 403 due to Forbidden\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "playlist_items failed | status=403 | playlist_id=1CnDCN10TJZjw6K2H3gNRv | message=http status: 403, code: -1 - https://api.spotify.com/v1/playlists/1CnDCN10TJZjw6K2H3gNRv/tracks?limit=10&offset=0&additional_types=track%2Cepisode:\n",
+ " Forbidden, reason: None\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0"
+ ]
+ },
+ "execution_count": 54,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist_items = safe_get_playlist_tracks(playlist_id, limit=10)\n",
+ "len(playlist_items)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5f1bff67",
+ "metadata": {},
+ "source": [
+ "### 21. Extract and display first 10 tracks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "68776fe0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[]"
+ ]
+ },
+ "execution_count": 55,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "first_10_track_names = []\n",
+ "\n",
+ "for item in playlist_items:\n",
+ " track = item.get(\"track\")\n",
+ " if not track:\n",
+ " continue\n",
+ "\n",
+ " track_name = track.get(\"name\")\n",
+ " if track_name:\n",
+ " first_10_track_names.append(track_name)\n",
+ "\n",
+ "first_10_track_names"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b4f562b6",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "First 10 tracks in the chosen playlist:\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"First 10 tracks in the chosen playlist:\")\n",
+ "for name in first_10_track_names:\n",
+ " print(\"-\", name)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "87e255e0",
+ "metadata": {},
+ "source": [
+ "### 22. Dictionary: track -> artists"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a4cfe99e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{}"
+ ]
+ },
+ "execution_count": 57,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "track_artists_dict = {}\n",
+ "\n",
+ "for item in playlist_items:\n",
+ " track = item.get(\"track\")\n",
+ " if not track:\n",
+ " continue\n",
+ "\n",
+ " track_name = track.get(\"name\")\n",
+ " artist_names = [artist.get(\"name\") for artist in track.get(\"artists\", []) if artist.get(\"name\")]\n",
+ "\n",
+ " if track_name:\n",
+ " track_artists_dict[track_name] = artist_names\n",
+ "\n",
+ "track_artists_dict"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "419cb205",
+ "metadata": {},
+ "source": [
+ "### 23. Bonus: convert playlist tracks to DataFrame"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d18be38d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ "Empty DataFrame\n",
+ "Columns: []\n",
+ "Index: []"
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "playlist_rows = []\n",
+ "\n",
+ "for item in playlist_items:\n",
+ " track = item.get(\"track\")\n",
+ " if not track:\n",
+ " continue\n",
+ "\n",
+ " playlist_rows.append({\n",
+ " \"track_name\": track.get(\"name\"),\n",
+ " \"artists\": \", \".join([a.get(\"name\", \"\") for a in track.get(\"artists\", [])]),\n",
+ " \"album_name\": track.get(\"album\", {}).get(\"name\"),\n",
+ " \"popularity\": track.get(\"popularity\")\n",
+ " })\n",
+ "\n",
+ "playlist_tracks_df = pd.DataFrame(playlist_rows)\n",
+ "playlist_tracks_df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "79592ca3",
+ "metadata": {},
+ "source": [
+ "### 24. Final conclusions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "66d8e5c4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Conclusions:\n",
+ "1. The Spotify API can be used to search tracks, artists, and playlists.\n",
+ "2. The search endpoint is useful for exploring public music catalog data.\n",
+ "3. We successfully extracted top tracks for favorite artists and identified related artists.\n",
+ "4. We created a discovery playlist structure combining favorite and related artists.\n",
+ "5. Playlist exploration may depend on endpoint availability and app permissions, so defensive error handling is important.\n",
+ "6. This adapted lab is designed to work reliably with the Client Credentials flow.\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"\"\"\n",
+ "Conclusions:\n",
+ "1. The Spotify API can be used to search tracks, artists, and playlists.\n",
+ "2. The search endpoint is useful for exploring public music catalog data.\n",
+ "3. We successfully extracted top tracks for favorite artists and identified related artists.\n",
+ "4. We created a discovery playlist structure combining favorite and related artists.\n",
+ "5. Playlist exploration may depend on endpoint availability and app permissions, so defensive error handling is important.\n",
+ "6. This adapted lab is designed to work reliably with the Client Credentials flow.\n",
+ "\"\"\")"
+ ]
}
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "base",
"language": "python",
"name": "python3"
},
@@ -558,7 +3293,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.13"
+ "version": "3.13.5"
}
},
"nbformat": 4,