diff --git a/.cache b/.cache new file mode 100644 index 0000000..34c0273 --- /dev/null +++ b/.cache @@ -0,0 +1 @@ +{"access_token": "BQDbZzn2BSFjIxhuQuO3FYPfS5aR5ediTk-5mJebDGfbz2Ji8wehPzkIgpP1VAQxcm_sspSe8Wo9HRAt5ZElhjDRT8DOYxZIEC2AoPMwUwyGiSb_aIMnt0_9dLkupAbWUHDn6abpG7E", "token_type": "Bearer", "expires_in": 3600, "expires_at": 1775164616} \ No newline at end of file diff --git a/.ipynb_checkpoints/lab-apis-checkpoint.ipynb b/.ipynb_checkpoints/lab-apis-checkpoint.ipynb new file mode 100644 index 0000000..93db5eb --- /dev/null +++ b/.ipynb_checkpoints/lab-apis-checkpoint.ipynb @@ -0,0 +1,1409 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "27422808-8a16-4be1-897f-57b379af8991", + "metadata": {}, + "source": [ + "# Lab | APIs" + ] + }, + { + "cell_type": "markdown", + "id": "50f30950-3e31-499a-92ea-1535422c570b", + "metadata": {}, + "source": [ + "In order to use the `Spotify` API (`SpotiPy`), create an account in `Spotify` and follow [these](https://developer.spotify.com/documentation/general/guides/app-settings/) steps. " + ] + }, + { + "cell_type": "markdown", + "id": "a0479b95-6ca5-415e-b894-1f5cb17b055b", + "metadata": {}, + "source": [ + "## Authentication and initializing the API" + ] + }, + { + "cell_type": "markdown", + "id": "47d71611-c617-4972-a0c3-7090c24b399c", + "metadata": {}, + "source": [ + "Save your client ID and your client secret in your preferred way, and read it or load it into the following variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ea44c82a-5c07-4dbc-beb2-bba2601bb75e", + "metadata": {}, + "outputs": [], + "source": [ + "CLIENT_ID = \"\"\n", + "CLIENT_SECRET = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "319cfd4e-f6fb-4419-80e0-e3983cd25e43", + "metadata": {}, + "outputs": [], + "source": [ + "CLIENT_ID = \"4109b2d9f5014671863bb2df1d1fbdd3\"\n", + "CLIENT_SECRET = \"5cb719fae80c4191a9c0b288f51bfe50\"" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "04e12954-fd70-4311-88a5-fb7e2c29799c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting spotipy\n", + " Downloading spotipy-2.26.0-py3-none-any.whl.metadata (5.1 kB)\n", + "Collecting redis>=3.5.3 (from spotipy)\n", + " Downloading redis-7.4.0-py3-none-any.whl.metadata (12 kB)\n", + "Requirement already satisfied: requests>=2.25.0 in /opt/anaconda3/lib/python3.13/site-packages (from spotipy) (2.32.5)\n", + "Requirement already satisfied: urllib3>=1.26.0 in /opt/anaconda3/lib/python3.13/site-packages (from spotipy) (2.5.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/anaconda3/lib/python3.13/site-packages (from requests>=2.25.0->spotipy) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/anaconda3/lib/python3.13/site-packages (from requests>=2.25.0->spotipy) (3.11)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/anaconda3/lib/python3.13/site-packages (from requests>=2.25.0->spotipy) (2026.1.4)\n", + "Downloading spotipy-2.26.0-py3-none-any.whl (31 kB)\n", + "Downloading redis-7.4.0-py3-none-any.whl (409 kB)\n", + "Installing collected packages: redis, spotipy\n", + "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2/2\u001b[0m [spotipy]\n", + "\u001b[1A\u001b[2KSuccessfully installed redis-7.4.0 spotipy-2.26.0\n" + ] + } + ], + "source": [ + "# If you havent done so, install the spotipy wrapper\n", + "!pip install spotipy" + ] + }, + { + "cell_type": "markdown", + "id": "dc0e86da-8846-4207-84c3-cd20b9e01d0e", + "metadata": {}, + "source": [ + "Once you have done it, we will start initializing the API." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "03034bc6-9858-412a-83b7-18abdc345d7e", + "metadata": {}, + "outputs": [], + "source": [ + "import spotipy\n", + "from spotipy.oauth2 import SpotifyClientCredentials\n", + "\n", + "#Initialize SpotiPy with user credentials\n", + "sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id=CLIENT_ID,\n", + " client_secret=CLIENT_SECRET))\n" + ] + }, + { + "cell_type": "markdown", + "id": "8fed9628-08d7-4290-a4be-5527696b01c5", + "metadata": {}, + "source": [ + "## Using the search method" + ] + }, + { + "cell_type": "markdown", + "id": "6575a3c6-f25a-4905-b1f3-c0efd50dcc1e", + "metadata": {}, + "source": [ + "Now, let's use the search method by introducing a \"query\". For example, let's try searching for \"Lady Gaga\":" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7772a1e0-9692-4d04-a590-76111a272d8d", + "metadata": { + "tags": [] + }, + "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 403 due to Active premium subscription required for the owner of the app. When the subscription status changes, it can take a few hours before requests are allowed again.\n" + ] + }, + { + "ename": "SpotifyException", + "evalue": "http status: 403, code: -1 - https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track:\n Active premium subscription required for the owner of the app. When the subscription status changes, it can take a few hours before requests are allowed again., reason: None", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mHTTPError\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:271\u001b[39m, in \u001b[36mSpotify._internal_call\u001b[39m\u001b[34m(self, method, url, payload, params)\u001b[39m\n\u001b[32m 266\u001b[39m response = \u001b[38;5;28mself\u001b[39m._session.request(\n\u001b[32m 267\u001b[39m method, url, headers=headers, proxies=\u001b[38;5;28mself\u001b[39m.proxies,\n\u001b[32m 268\u001b[39m timeout=\u001b[38;5;28mself\u001b[39m.requests_timeout, **args\n\u001b[32m 269\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m271\u001b[39m \u001b[43mresponse\u001b[49m\u001b[43m.\u001b[49m\u001b[43mraise_for_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 272\u001b[39m results = response.json()\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/requests/models.py:1026\u001b[39m, in \u001b[36mResponse.raise_for_status\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1025\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m http_error_msg:\n\u001b[32m-> \u001b[39m\u001b[32m1026\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m HTTPError(http_error_msg, response=\u001b[38;5;28mself\u001b[39m)\n", + "\u001b[31mHTTPError\u001b[39m: 403 Client Error: Forbidden for url: https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[31mSpotifyException\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m results = \u001b[43msp\u001b[49m\u001b[43m.\u001b[49m\u001b[43msearch\u001b[49m\u001b[43m(\u001b[49m\u001b[43mq\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mLady Gaga\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlimit\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m50\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 2\u001b[39m results\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:613\u001b[39m, in \u001b[36mSpotify.search\u001b[39m\u001b[34m(self, q, limit, offset, type, market)\u001b[39m\n\u001b[32m 598\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34msearch\u001b[39m(\u001b[38;5;28mself\u001b[39m, q, limit=\u001b[32m10\u001b[39m, offset=\u001b[32m0\u001b[39m, \u001b[38;5;28mtype\u001b[39m=\u001b[33m\"\u001b[39m\u001b[33mtrack\u001b[39m\u001b[33m\"\u001b[39m, market=\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[32m 599\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\" searches for an item\u001b[39;00m\n\u001b[32m 600\u001b[39m \n\u001b[32m 601\u001b[39m \u001b[33;03m Parameters:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 611\u001b[39m \u001b[33;03m from_token.\u001b[39;00m\n\u001b[32m 612\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m613\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_get\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 614\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msearch\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mq\u001b[49m\u001b[43m=\u001b[49m\u001b[43mq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlimit\u001b[49m\u001b[43m=\u001b[49m\u001b[43mlimit\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moffset\u001b[49m\u001b[43m=\u001b[49m\u001b[43moffset\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mtype\u001b[39;49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mtype\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmarket\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmarket\u001b[49m\n\u001b[32m 615\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:321\u001b[39m, in \u001b[36mSpotify._get\u001b[39m\u001b[34m(self, url, args, payload, **kwargs)\u001b[39m\n\u001b[32m 318\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m args:\n\u001b[32m 319\u001b[39m kwargs.update(args)\n\u001b[32m--> \u001b[39m\u001b[32m321\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_internal_call\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mGET\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpayload\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:291\u001b[39m, in \u001b[36mSpotify._internal_call\u001b[39m\u001b[34m(self, method, url, payload, params)\u001b[39m\n\u001b[32m 286\u001b[39m reason = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 288\u001b[39m logger.error(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mHTTP Error for \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmethod\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00murl\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m with Params: \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 289\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00margs.get(\u001b[33m'\u001b[39m\u001b[33mparams\u001b[39m\u001b[33m'\u001b[39m)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m returned \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse.status_code\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m due to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmsg\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m291\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m SpotifyException(\n\u001b[32m 292\u001b[39m response.status_code,\n\u001b[32m 293\u001b[39m -\u001b[32m1\u001b[39m,\n\u001b[32m 294\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse.url\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmsg\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m,\n\u001b[32m 295\u001b[39m reason=reason,\n\u001b[32m 296\u001b[39m headers=response.headers,\n\u001b[32m 297\u001b[39m )\n\u001b[32m 298\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m requests.exceptions.RetryError \u001b[38;5;28;01mas\u001b[39;00m retry_error:\n\u001b[32m 299\u001b[39m request = retry_error.request\n", + "\u001b[31mSpotifyException\u001b[39m: http status: 403, code: -1 - https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track:\n Active premium subscription required for the owner of the app. When the subscription status changes, it can take a few hours before requests are allowed again., reason: None" + ] + } + ], + "source": [ + "results = sp.search(q='Lady Gaga', limit=50)\n", + "results" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "edc14c95-474b-4e2a-aea3-bdfd0a205546", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[6]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m.keys() \u001b[38;5;66;03m# We can see that we only have tracks\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], + "source": [ + "results.keys() # We can see that we only have tracks" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ad8ef934-1dbb-4008-ac8e-f5c29823fe6a", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m].keys() \u001b[38;5;66;03m# Let's check the values\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], + "source": [ + "results[\"tracks\"].keys() # Let's check the values" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "004b7814-4dd5-408e-b7ba-1da87f9250cb", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mhref\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;66;03m# Query we have searched \u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], + "source": [ + "results[\"tracks\"][\"href\"] # Query we have searched " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7285cedd-fbe1-47cf-98d5-a7fdc3e5c8b8", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mitems\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;66;03m#items (actual tracks)\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], + "source": [ + "results[\"tracks\"][\"items\"] #items (actual tracks)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "529fff56-47d3-4d78-8ff5-9530fe290d1d", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[10]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mlimit\u001b[39m\u001b[33m\"\u001b[39m]\u001b[38;5;66;03m#Limit we have chosen\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], + "source": [ + "results[\"tracks\"][\"limit\"]#Limit we have chosen" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "92c64c57-3bd2-4d42-bbd1-84a040f1e02a", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mnext\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;66;03m#link to the next page (next 50 tracks)\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], + "source": [ + "results[\"tracks\"][\"next\"] #link to the next page (next 50 tracks)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5ccdf79-5d9e-40de-adb9-2cc1e5b7c74a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "results[\"tracks\"][\"offset\"] # Actual offset (starting point)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "356730c1-60a2-4ea8-bd2c-e0522bab8a4d", + "metadata": {}, + "outputs": [], + "source": [ + "results[\"tracks\"][\"previous\"] #Previous search" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c44c8fd-63ea-45ba-94bd-5c5e8e1458b3", + "metadata": {}, + "outputs": [], + "source": [ + "results[\"tracks\"][\"total\"] # Number of matches" + ] + }, + { + "cell_type": "markdown", + "id": "7a127c64-3274-4ecc-aa0f-83ae34af4655", + "metadata": {}, + "source": [ + "## Exploring the tracks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c3541a2-0fd2-41e0-9b27-60a7dc36c4cb", + "metadata": { + "scrolled": true, + "tags": [] + }, + "outputs": [], + "source": [ + "results[\"tracks\"][\"items\"][0] # Explore the first song" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2c35eb2-3ea6-4329-9f29-7c062f466638", + "metadata": {}, + "outputs": [], + "source": [ + "results[\"tracks\"][\"items\"][0].keys() # We will focus on album, artists, id, name, popularity, type and uri" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "889ca3c3-b0c8-4037-96fb-6add847f537f", + "metadata": {}, + "outputs": [], + "source": [ + "# Track artists\n", + "results[\"tracks\"][\"items\"][0][\"artists\"] " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9c6a0b2-cea7-48ff-8c51-179d15388aa2", + "metadata": {}, + "outputs": [], + "source": [ + "# Track artists names\n", + "for artist in results[\"tracks\"][\"items\"][0][\"artists\"]:\n", + " print(artist[\"name\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a826e9c-d2e7-4537-a82c-3dc3a2b80b9f", + "metadata": {}, + "outputs": [], + "source": [ + "# Track ID\n", + "results[\"tracks\"][\"items\"][0][\"id\"] " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5bd871b-6087-4680-819c-1a1d8ba879bc", + "metadata": {}, + "outputs": [], + "source": [ + "# Track name\n", + "results[\"tracks\"][\"items\"][0][\"name\"] " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "812661f1-29db-452f-a719-cdfbe95ba9f6", + "metadata": {}, + "outputs": [], + "source": [ + "# Popularity index\n", + "results[\"tracks\"][\"items\"][0][\"popularity\"] " + ] + }, + { + "cell_type": "markdown", + "id": "0e81c762-e6c5-424e-a4eb-12ab45dffb9f", + "metadata": {}, + "source": [ + "Spotify songs are identified by either a \"url\", a \"uri\", or an \"id\". \n", + "\n", + "- The `id` is an alphanumeric code, and it's the nuclear part of the identifier.\n", + "\n", + "- The `uri` contains \"spotify:track\" before the id. An uri is useful because it can be searched manually in the Spotify app.\n", + "\n", + "- The `url` is a link to the song on the Spotify web player.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bcdccfc-dde9-4f4b-8af5-3caa335b89b5", + "metadata": {}, + "outputs": [], + "source": [ + "results[\"tracks\"][\"items\"][0][\"uri\"]" + ] + }, + { + "cell_type": "markdown", + "id": "71c3c9c1-4ec2-42bf-a243-b21105ae1699", + "metadata": {}, + "source": [ + "## Exercise 1: Discovering New Music through Your Favorite Artists\n", + "\n", + "**Objective:** \n", + "Uncover new music by exploring the top tracks of your favorite artists and their related artists.\n", + "\n", + "**Instructions:**\n", + "\n", + "1. **List Your Favorite Artists**:\n", + " - Make a list of your three favorite artists and store it in a variable named `artists`.\n", + " - Example: `artists = [\"Los Fabulosos Cadillacs\", \"Manu Chao\", \"Muchachito Bombo Infierno\"]`.\n", + "\n", + "2. **Fetch Top Tracks**:\n", + " - Write a function named `get_top_tracks`.\n", + " - This function should accept an artist's name and return the name of the first 5 top tracks by that artist.\n", + " - Use the function `get_top_tracks` to get the first 5 top tracks for each artist in your `artists` list and store the results in a new list named `top_tracks_list`.\n", + "\n", + "3. **Discover Related Artists**:\n", + " - Write a function named `find_related_artists`.\n", + " - This function should accept an artist's name and return the names of the first 5 artists related to the provided artist.\n", + " - Store the results in a list named `related_artists_list`.\n", + "\n", + "**Challenge:** \n", + "Combine the above steps to create a playlist that includes the top tracks of your favorite artists and the top tracks of the artists related to them." + ] + }, + { + "cell_type": "markdown", + "id": "0c442378-e26f-47c8-b4f1-b4fa07089935", + "metadata": {}, + "source": [ + "**Hint Section for 3. **Discover Related Artists**:**\n", + "\n", + "1. **Getting Artist ID**:\n", + " - Remember that every artist on Spotify has a unique identifier: their `id`. To get the related artists, you first need to fetch the ID of the given artist.\n", + " - Consider using the `sp.search` method to query the artist's name. The method requires a `q` parameter, which is your query (in this case, the artist's name). It also has a `limit` parameter, which specifies the number of tracks it returns. In this case, 1 track is enough, since we just want the artist ID. \n", + " - Each track in the results has an associated 'artists' field. This field is a list containing details about all artists involved in that track.\n", + " - For most tracks, especially those by a single artist, this list will contain one artist. From this artist's details, you can extract the 'id' field, which is the unique identifier for that artist on Spotify.\n", + "\n", + "\n", + "3. **Fetching Related Artists**:\n", + " - Once you have the artist's ID, you can use another SpotiPy method to fetch related artists. Think about which SpotiPy method allows you to get related artists using an artist's ID. Here is the documentation link: https://spotipy.readthedocs.io/en/2.22.1/. \n", + " - This method will return a list of related artists. You'll need to extract the relevant details (artist names) from this list.\n", + "\n", + "4. **Iterating for Multiple Artists**:\n", + " - Once you have a function that returns related artists names for one artist, you can use a list comprehension to apply this function to a list of artist names.\n", + "\n", + "5. **Testing**:\n", + " - Always test your function with one artist name first. Once you're confident it works, then apply it to the entire list.\n", + "\n", + "Remember, the key is to break the problem down into manageable steps. Use the SpotiPy documentation as a resource to understand available methods and their return structures." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "29694252-f217-454d-8881-681b2b6eeb1e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Figures\n", + "Imported (with 6LACK)\n", + "Figures, a Reprise\n", + "FOREVER (with 6LACK)\n", + "COFFIN (feat. Eminem)\n" + ] + } + ], + "source": [ + "# First tried the API and doublecheck with one artist if it works\n", + "'''\n", + "I used Last FM API to do this excersice, the API from spotipy don't work anymore and I don't have a premium account\n", + "'''\n", + "\n", + "import requests\n", + "\n", + "API_KEY = \"ERASED_FOR_SAFETY\"\n", + "\n", + "url = \"http://ws.audioscrobbler.com/2.0/\"\n", + "params = {\n", + " \"method\": \"artist.gettoptracks\",\n", + " \"artist\": \"Jessie Reyez\",\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": 5\n", + "}\n", + "\n", + "response = requests.get(url, params=params)\n", + "data = response.json()\n", + "\n", + "for track in data[\"toptracks\"][\"track\"]:\n", + " print(track[\"name\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "008714c9-7ce3-4ca7-bf56-486a08f5e923", + "metadata": {}, + "outputs": [], + "source": [ + "#List Your Favorite Artists:\n", + "artists = [\"One Direction\", \"Humbe\", \"Jessie Reyez\", \"Morat\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "54535bd0-dc2a-40f2-a950-2242ac3721ec", + "metadata": {}, + "outputs": [], + "source": [ + "#Fetch Top Tracks:\n", + "\n", + "def get_top_tracks(artist_name, limit=5):\n", + " params = {\n", + " \"method\": \"artist.gettoptracks\",\n", + " \"artist\": artist_name,\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": limit \n", + " }\n", + "\n", + " response = requests.get(url, params=params)\n", + " data = response.json()\n", + "\n", + " tracks = data.get(\"toptracks\",{}).get(\"track\", [])\n", + "\n", + " if isinstance(tracks,dict):\n", + " tracks = [tracks]\n", + "\n", + " return [track[\"name\"]for track in tracks[:limit]]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d42f3123-1292-447c-ac3a-6c66babfc378", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Fantasmas', 'Kintsugi', 'AMOR DE CINE', 'Te Lo Prometo', 'EL POETA']" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_top_tracks(\"Humbe\") #Test1" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "fb0a7df8-c71d-44f5-ba5e-70108e162fbc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Top Tracks List:\n" + ] + }, + { + "data": { + "text/plain": [ + "[['What Makes You Beautiful',\n", + " 'Night Changes',\n", + " 'Story of My Life',\n", + " 'Drag Me Down',\n", + " 'Steal My Girl'],\n", + " ['Fantasmas', 'Kintsugi', 'AMOR DE CINE', 'Te Lo Prometo', 'EL POETA'],\n", + " ['Figures',\n", + " 'Imported (with 6LACK)',\n", + " 'Figures, a Reprise',\n", + " 'FOREVER (with 6LACK)',\n", + " 'COFFIN (feat. Eminem)'],\n", + " ['Besos En Guerra',\n", + " 'Cuando Nadie Ve',\n", + " 'No Se Va',\n", + " 'Cómo Te Atreves',\n", + " 'Presiento']]" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Test 2\n", + "print(\"Top Tracks List:\")\n", + "top_tracks_list = [get_top_tracks(artist) for artist in artists]\n", + "\n", + "top_tracks_list" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "fccd5029-8788-4c89-a146-93606492f39a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'Latin mafia',\n", + " 'match': '1',\n", + " 'url': 'https://www.last.fm/music/Latin+mafia',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Kenia Os',\n", + " 'mbid': 'f28b704b-38e2-4b01-b51a-5337d7e5ac2d',\n", + " 'match': '0.451426',\n", + " 'url': 'https://www.last.fm/music/Kenia+Os',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Danna Paola',\n", + " 'mbid': '3ad91c4e-ef5a-4d7c-99e7-21a8b0c9379d',\n", + " 'match': '0.388265',\n", + " 'url': 'https://www.last.fm/music/Danna+Paola',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Manuel Medrano',\n", + " 'mbid': '43d48ba4-760e-48f9-8f15-3254aa2e7c37',\n", + " 'match': '0.381252',\n", + " 'url': 'https://www.last.fm/music/Manuel+Medrano',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Jesse & Joy',\n", + " 'mbid': '64b85e52-08db-4995-a6e3-d2a6acc5a058',\n", + " 'match': '0.346041',\n", + " 'url': 'https://www.last.fm/music/Jesse+&+Joy',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'}]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "params = {\n", + " \"method\": \"artist.getsimilar\",\n", + " \"artist\": \"Humbe\",\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": 5 \n", + " }\n", + "response = requests.get(url, params=params)\n", + "data = response.json()\n", + "data[\"similarartists\"][\"artist\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "52f31560-aabf-48f2-9bf7-2ee2779362ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"similarartists\": {\n", + " \"artist\": [\n", + " {\n", + " \"name\": \"Latin mafia\",\n", + " \"match\": \"1\",\n", + " \"url\": \"https://www.last.fm/music/Latin+mafia\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Kenia Os\",\n", + " \"mbid\": \"f28b704b-38e2-4b01-b51a-5337d7e5ac2d\",\n", + " \"match\": \"0.451426\",\n", + " \"url\": \"https://www.last.fm/music/Kenia+Os\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Danna Paola\",\n", + " \"mbid\": \"3ad91c4e-ef5a-4d7c-99e7-21a8b0c9379d\",\n", + " \"match\": \"0.388265\",\n", + " \"url\": \"https://www.last.fm/music/Danna+Paola\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Manuel Medrano\",\n", + " \"mbid\": \"43d48ba4-760e-48f9-8f15-3254aa2e7c37\",\n", + " \"match\": \"0.381252\",\n", + " \"url\": \"https://www.last.fm/music/Manuel+Medrano\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Jesse & Joy\",\n", + " \"mbid\": \"64b85e52-08db-4995-a6e3-d2a6acc5a058\",\n", + " \"match\": \"0.346041\",\n", + " \"url\": \"https://www.last.fm/music/Jesse+&+Joy\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " }\n", + " ],\n", + " \"@attr\": {\n", + " \"artist\": \"Humbe\"\n", + " }\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "import json\n", + "print(json.dumps(data, indent=2, ensure_ascii=False))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "d5d52eb0-d232-447d-ad56-c85c58176ea9", + "metadata": {}, + "outputs": [], + "source": [ + "#Discover Related Artists\n", + "\n", + "def find_related_artists(artist_name, limit=5):\n", + " params = {\n", + " \"method\": \"artist.getsimilar\",\n", + " \"artist\": artist_name,\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": limit \n", + " }\n", + " \n", + " response = requests.get(url, params=params)\n", + " data = response.json()\n", + "\n", + " similar_artists = data.get(\"similarartists\",{}).get(\"artist\", [])\n", + "\n", + " if isinstance(similar_artists,dict):\n", + " similar_artists = [similar_artists]\n", + "\n", + " return [artist[\"name\"]for artist in similar_artists[:limit]]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "2b7e056e-95a7-4380-80cf-634f256dfec3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Latin mafia', 'Kenia Os', 'Danna Paola', 'Manuel Medrano', 'Jesse & Joy']\n" + ] + } + ], + "source": [ + "print(find_related_artists(\"Humbe\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d4e65751-04f2-4d4f-83c5-d182261ef7ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Related Artists List:\n" + ] + }, + { + "data": { + "text/plain": [ + "[['Louis Tomlinson', 'Niall Horan', 'Liam Payne', 'ZAYN', 'Harry Styles'],\n", + " ['Latin mafia', 'Kenia Os', 'Danna Paola', 'Manuel Medrano', 'Jesse & Joy'],\n", + " ['Jessie Reyez & 6LACK', 'Kehlani', 'Kiana Ledé', 'Mahalia', 'Alessia Cara'],\n", + " ['Reik', 'TIMØ', 'Ha*Ash', 'Camila', 'HA-ASH']]" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Test with all artists\n", + "print(\"Related Artists List:\")\n", + "similar_artists_list = [find_related_artists(artist) for artist in artists]\n", + "\n", + "similar_artists_list" + ] + }, + { + "cell_type": "markdown", + "id": "94ad5fdc-22e5-4521-8aa1-c6833eb7e949", + "metadata": {}, + "source": [ + "## Playlists\n", + "\n", + "The `sp.featured_playlists()` method in `spotipy` fetches a list of Spotify's featured playlists at a given moment. These are curated playlists that Spotify often highlights on the platform's homepage. The method provides a snapshot of the playlists that are being promoted or featured by Spotify at the time of the request.\n", + "\n", + "Once you've fetched the featured playlists, you can extract their IDs (and other details)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25fb0cf8-c13a-41b0-b8f8-7e0700fd1e41", + "metadata": {}, + "outputs": [], + "source": [ + "sp.featured_playlists() # We get a playlist id of a playlist we like" + ] + }, + { + "cell_type": "markdown", + "id": "90f558f3-c638-4df4-b5a4-e24f7847d52a", + "metadata": {}, + "source": [ + "### Getting a Playlist's Details\n", + "To fetch details about a specific playlist, you can use the playlist method. You'll need the playlist's Spotify ID." + ] + }, + { + "cell_type": "markdown", + "id": "0eef529f-617f-4ea3-8156-07472ac8e6d5", + "metadata": {}, + "source": [ + "In this example, we will use the following playlist id: *37i9dQZF1DXd9zR7tdziuQ*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46d35121-9256-4cf4-81f5-118b87f7af32", + "metadata": {}, + "outputs": [], + "source": [ + "playlist_id = \"37i9dQZF1DXd9zR7tdziuQ\"\n", + "playlist = sp.playlist(playlist_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5260f67f-6024-4fee-8449-30904f03bf76", + "metadata": {}, + "outputs": [], + "source": [ + "print(playlist['name']) # Print the playlist's name\n", + "print(playlist['description']) # Print the playlist's description" + ] + }, + { + "cell_type": "markdown", + "id": "13bc8631-69f0-4b98-9cc9-5baecbaea9ba", + "metadata": {}, + "source": [ + "### Getting Tracks from a Playlist\n", + "If you want to get the tracks from a specific playlist, you can use the playlist_tracks method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69c78f8d-7e6a-4d15-bcbb-fc93edb82433", + "metadata": {}, + "outputs": [], + "source": [ + "tracks = sp.playlist_tracks(playlist_id)\n", + "for track in tracks['items']:\n", + " print(track['track']['name']) # Print each track's name" + ] + }, + { + "cell_type": "markdown", + "id": "2775714d-acc7-4555-96bd-2c541ab0855e", + "metadata": {}, + "source": [ + "### Getting Artists from a Playlist\n", + "\n", + "To extract all the artists from the tracks in a playlist, you'd typically follow these steps:\n", + "\n", + "1. Fetch the playlist's tracks.\n", + "2. Iterate through each track.\n", + "3. For each track, extract the associated artists." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65c5e5c4-f186-42c6-b136-4ef02b0b01ff", + "metadata": {}, + "outputs": [], + "source": [ + "# List to store unique artist names\n", + "artists_list = []\n", + "\n", + "for track_item in tracks['items']:\n", + " track = track_item['track']\n", + " for artist in track['artists']:\n", + " artist_name = artist['name']\n", + " if artist_name not in artists_list: # This ensures each artist is added only once\n", + " artists_list.append(artist_name)\n", + "\n", + "print(artists_list)" + ] + }, + { + "cell_type": "markdown", + "id": "7b52207e-a4f0-4f90-9f4e-3170d7f0f3fe", + "metadata": {}, + "source": [ + "## Exercise 2: Unraveling the World of Playlists\n", + "\n", + "\n", + "1. **Featured Exploration**: \n", + " - Fetch the list of Spotify's current featured playlists. \n", + " - Extract and display the names and IDs of the top 5 featured playlists.\n", + " \n", + "2. **Deep Dive**:\n", + " - Choose any one of the top 5 featured playlists (you can choose the one you personally find most interesting or simply pick one randomly).\n", + " - Fetch and display its name, description, and total track count.\n", + "\n", + "3. **Track-tastic**:\n", + " - Extract and display the names of the first 10 tracks in the chosen playlist.\n", + "\n", + "4. **Artistic Flair**:\n", + " - Create a dictionary where the keys are the names of the first 10 tracks, and the values are lists containing the names of the artists associated with each track.\n", + " - For example: `{\"TrackName1\": [\"Artist1\", \"Artist2\"], \"TrackName2\": [\"Artist3\"]}`\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "ed92d961-9646-4375-a386-ccc320a958f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting deezer-python\n", + " Downloading deezer_python-7.2.0-py3-none-any.whl.metadata (14 kB)\n", + "Requirement already satisfied: httpx>=0.23 in /opt/anaconda3/lib/python3.13/site-packages (from deezer-python) (0.28.1)\n", + "Requirement already satisfied: anyio in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (4.10.0)\n", + "Requirement already satisfied: certifi in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/anaconda3/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.23->deezer-python) (0.16.0)\n", + "Requirement already satisfied: sniffio>=1.1 in /opt/anaconda3/lib/python3.13/site-packages (from anyio->httpx>=0.23->deezer-python) (1.3.0)\n", + "Downloading deezer_python-7.2.0-py3-none-any.whl (24 kB)\n", + "Installing collected packages: deezer-python\n", + "Successfully installed deezer-python-7.2.0\n" + ] + } + ], + "source": [ + "# I'll be using Deezer API for this part, found through Google tat is similar\n", + "!pip install deezer-python\n", + "\n", + "import deezer\n", + "client= deezer.Client()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "6893b655-1a3d-4d55-8499-c46fb8542c99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "#Featured Exploration\n", + "\n", + "playlists = client.search_playlists(\"pop\")\n", + "\n", + "for p in list(playlists)[:5]:\n", + " print(p)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "28422ef6-b654-4a68-afbf-f2fa234052ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: Happy Hits\n", + "ID: 1479458365\n", + "\n", + "Name: Global Hits\n", + "ID: 2098157264\n", + "\n", + "Name: Feel Good Pop\n", + "ID: 1964085082\n", + "\n", + "Name: Fresh Pop\n", + "ID: 2228601362\n", + "\n", + "Name: Chill Hits\n", + "ID: 1976454162\n", + "\n" + ] + } + ], + "source": [ + "top_5_playlists = list(playlists)[:5]\n", + "\n", + "for pl in top_5_playlists:\n", + " print(\"Name:\", pl.title)\n", + " print(\"ID:\", pl.id)\n", + " print()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "b6f9ea05-59b4-436d-83ca-325cf342f455", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: Happy Hits\n", + "Description: ¡Éxitos con buen rollo para animarte cuando lo necesites!\n", + "Total tracks: 71\n" + ] + } + ], + "source": [ + "#Deep Dive\n", + "chosen_playlist = top_5_playlists[0]\n", + "\n", + "print(\"Name:\", chosen_playlist.title)\n", + "print(\"Description:\", getattr(chosen_playlist,\"description\",\"No description\"))\n", + "print(\"Total tracks:\", chosen_playlist.nb_tracks)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "488e497b-8bd0-4be8-847c-c9a1d4750eab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WHERE IS MY HUSBAND!\n", + "I Just Might\n", + "12 to 12\n", + "The Fate of Ophelia\n", + "NUEVAYoL\n", + "Gone Gone Gone\n", + "Zoo (From \"Zootropolis 2\")\n", + "Lush Life\n", + "Gabriela\n", + "The Dead Dance\n" + ] + } + ], + "source": [ + "#Track-tastic\n", + "tracks = list(chosen_playlist.get_tracks())[:10]\n", + "for track in tracks:\n", + " print(track.title)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "65d2300a-ad59-4737-8628-05e8f6ac233b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'WHERE IS MY HUSBAND!': ['Raye'],\n", + " 'I Just Might': ['Bruno Mars'],\n", + " '12 to 12': ['sombr'],\n", + " 'The Fate of Ophelia': ['Taylor Swift'],\n", + " 'NUEVAYoL': ['Bad Bunny'],\n", + " 'Gone Gone Gone': ['David Guetta', 'Teddy Swims', 'Tones and I'],\n", + " 'Zoo (From \"Zootropolis 2\")': ['Disney', 'Shakira'],\n", + " 'Lush Life': ['Zara Larsson'],\n", + " 'Gabriela': ['KATSEYE'],\n", + " 'The Dead Dance': ['Lady Gaga']}" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Artistic Flair\n", + "''' ex: {\"TrackName1\": [\"Artist1\", \"Artist2\"], \"TrackName2\": [\"Artist3\"]}'''\n", + "track_artist_dict ={}\n", + "\n", + "for track in tracks:\n", + " if hasattr(track, \"contributors\") and track.contributors:\n", + " artist_names = [artist.name for artist in track.contributors]\n", + " elif hasattr(track, \"artists\") and track.artist:\n", + " artist_names = [track.artist.name]\n", + " else:\n", + " artist_names = []\n", + "\n", + " track_artist_dict[track.title] = artist_names\n", + " \n", + "\n", + "track_artist_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b051b4a3-d1c2-4e9c-b0e1-408bcc3affff", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/lab-apis.ipynb b/lab-apis.ipynb index e923554..93db5eb 100644 --- a/lab-apis.ipynb +++ b/lab-apis.ipynb @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "ea44c82a-5c07-4dbc-beb2-bba2601bb75e", "metadata": {}, "outputs": [], @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "319cfd4e-f6fb-4419-80e0-e3983cd25e43", "metadata": {}, "outputs": [], @@ -56,10 +56,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "04e12954-fd70-4311-88a5-fb7e2c29799c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting spotipy\n", + " Downloading spotipy-2.26.0-py3-none-any.whl.metadata (5.1 kB)\n", + "Collecting redis>=3.5.3 (from spotipy)\n", + " Downloading redis-7.4.0-py3-none-any.whl.metadata (12 kB)\n", + "Requirement already satisfied: requests>=2.25.0 in /opt/anaconda3/lib/python3.13/site-packages (from spotipy) (2.32.5)\n", + "Requirement already satisfied: urllib3>=1.26.0 in /opt/anaconda3/lib/python3.13/site-packages (from spotipy) (2.5.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /opt/anaconda3/lib/python3.13/site-packages (from requests>=2.25.0->spotipy) (3.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/anaconda3/lib/python3.13/site-packages (from requests>=2.25.0->spotipy) (3.11)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/anaconda3/lib/python3.13/site-packages (from requests>=2.25.0->spotipy) (2026.1.4)\n", + "Downloading spotipy-2.26.0-py3-none-any.whl (31 kB)\n", + "Downloading redis-7.4.0-py3-none-any.whl (409 kB)\n", + "Installing collected packages: redis, spotipy\n", + "\u001b[2K \u001b[38;2;114;156;31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2/2\u001b[0m [spotipy]\n", + "\u001b[1A\u001b[2KSuccessfully installed redis-7.4.0 spotipy-2.26.0\n" + ] + } + ], "source": [ "# If you havent done so, install the spotipy wrapper\n", "!pip install spotipy" @@ -75,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "03034bc6-9858-412a-83b7-18abdc345d7e", "metadata": {}, "outputs": [], @@ -106,12 +127,39 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "7772a1e0-9692-4d04-a590-76111a272d8d", "metadata": { "tags": [] }, - "outputs": [], + "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 403 due to Active premium subscription required for the owner of the app. When the subscription status changes, it can take a few hours before requests are allowed again.\n" + ] + }, + { + "ename": "SpotifyException", + "evalue": "http status: 403, code: -1 - https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track:\n Active premium subscription required for the owner of the app. When the subscription status changes, it can take a few hours before requests are allowed again., reason: None", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mHTTPError\u001b[39m Traceback (most recent call last)", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:271\u001b[39m, in \u001b[36mSpotify._internal_call\u001b[39m\u001b[34m(self, method, url, payload, params)\u001b[39m\n\u001b[32m 266\u001b[39m response = \u001b[38;5;28mself\u001b[39m._session.request(\n\u001b[32m 267\u001b[39m method, url, headers=headers, proxies=\u001b[38;5;28mself\u001b[39m.proxies,\n\u001b[32m 268\u001b[39m timeout=\u001b[38;5;28mself\u001b[39m.requests_timeout, **args\n\u001b[32m 269\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m271\u001b[39m \u001b[43mresponse\u001b[49m\u001b[43m.\u001b[49m\u001b[43mraise_for_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 272\u001b[39m results = response.json()\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/requests/models.py:1026\u001b[39m, in \u001b[36mResponse.raise_for_status\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1025\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m http_error_msg:\n\u001b[32m-> \u001b[39m\u001b[32m1026\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m HTTPError(http_error_msg, response=\u001b[38;5;28mself\u001b[39m)\n", + "\u001b[31mHTTPError\u001b[39m: 403 Client Error: Forbidden for url: https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[31mSpotifyException\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m results = \u001b[43msp\u001b[49m\u001b[43m.\u001b[49m\u001b[43msearch\u001b[49m\u001b[43m(\u001b[49m\u001b[43mq\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mLady Gaga\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlimit\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m50\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 2\u001b[39m results\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:613\u001b[39m, in \u001b[36mSpotify.search\u001b[39m\u001b[34m(self, q, limit, offset, type, market)\u001b[39m\n\u001b[32m 598\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34msearch\u001b[39m(\u001b[38;5;28mself\u001b[39m, q, limit=\u001b[32m10\u001b[39m, offset=\u001b[32m0\u001b[39m, \u001b[38;5;28mtype\u001b[39m=\u001b[33m\"\u001b[39m\u001b[33mtrack\u001b[39m\u001b[33m\"\u001b[39m, market=\u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[32m 599\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\" searches for an item\u001b[39;00m\n\u001b[32m 600\u001b[39m \n\u001b[32m 601\u001b[39m \u001b[33;03m Parameters:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 611\u001b[39m \u001b[33;03m from_token.\u001b[39;00m\n\u001b[32m 612\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m613\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_get\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 614\u001b[39m \u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msearch\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mq\u001b[49m\u001b[43m=\u001b[49m\u001b[43mq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlimit\u001b[49m\u001b[43m=\u001b[49m\u001b[43mlimit\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moffset\u001b[49m\u001b[43m=\u001b[49m\u001b[43moffset\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mtype\u001b[39;49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mtype\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmarket\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmarket\u001b[49m\n\u001b[32m 615\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:321\u001b[39m, in \u001b[36mSpotify._get\u001b[39m\u001b[34m(self, url, args, payload, **kwargs)\u001b[39m\n\u001b[32m 318\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m args:\n\u001b[32m 319\u001b[39m kwargs.update(args)\n\u001b[32m--> \u001b[39m\u001b[32m321\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_internal_call\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mGET\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpayload\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/spotipy/client.py:291\u001b[39m, in \u001b[36mSpotify._internal_call\u001b[39m\u001b[34m(self, method, url, payload, params)\u001b[39m\n\u001b[32m 286\u001b[39m reason = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 288\u001b[39m logger.error(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mHTTP Error for \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmethod\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00murl\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m with Params: \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 289\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00margs.get(\u001b[33m'\u001b[39m\u001b[33mparams\u001b[39m\u001b[33m'\u001b[39m)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m returned \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse.status_code\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m due to \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmsg\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m291\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m SpotifyException(\n\u001b[32m 292\u001b[39m response.status_code,\n\u001b[32m 293\u001b[39m -\u001b[32m1\u001b[39m,\n\u001b[32m 294\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse.url\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmsg\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m,\n\u001b[32m 295\u001b[39m reason=reason,\n\u001b[32m 296\u001b[39m headers=response.headers,\n\u001b[32m 297\u001b[39m )\n\u001b[32m 298\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m requests.exceptions.RetryError \u001b[38;5;28;01mas\u001b[39;00m retry_error:\n\u001b[32m 299\u001b[39m request = retry_error.request\n", + "\u001b[31mSpotifyException\u001b[39m: http status: 403, code: -1 - https://api.spotify.com/v1/search?q=Lady+Gaga&limit=50&offset=0&type=track:\n Active premium subscription required for the owner of the app. When the subscription status changes, it can take a few hours before requests are allowed again., reason: None" + ] + } + ], "source": [ "results = sp.search(q='Lady Gaga', limit=50)\n", "results" @@ -119,63 +167,135 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "edc14c95-474b-4e2a-aea3-bdfd0a205546", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[6]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m.keys() \u001b[38;5;66;03m# We can see that we only have tracks\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], "source": [ "results.keys() # We can see that we only have tracks" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "ad8ef934-1dbb-4008-ac8e-f5c29823fe6a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m].keys() \u001b[38;5;66;03m# Let's check the values\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], "source": [ "results[\"tracks\"].keys() # Let's check the values" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "004b7814-4dd5-408e-b7ba-1da87f9250cb", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mhref\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;66;03m# Query we have searched \u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], "source": [ "results[\"tracks\"][\"href\"] # Query we have searched " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "7285cedd-fbe1-47cf-98d5-a7fdc3e5c8b8", "metadata": { "scrolled": true, "tags": [] }, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mitems\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;66;03m#items (actual tracks)\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], "source": [ "results[\"tracks\"][\"items\"] #items (actual tracks)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "529fff56-47d3-4d78-8ff5-9530fe290d1d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[10]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mlimit\u001b[39m\u001b[33m\"\u001b[39m]\u001b[38;5;66;03m#Limit we have chosen\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], "source": [ "results[\"tracks\"][\"limit\"]#Limit we have chosen" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "92c64c57-3bd2-4d42-bbd1-84a040f1e02a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'results' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mresults\u001b[49m[\u001b[33m\"\u001b[39m\u001b[33mtracks\u001b[39m\u001b[33m\"\u001b[39m][\u001b[33m\"\u001b[39m\u001b[33mnext\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;66;03m#link to the next page (next 50 tracks)\u001b[39;00m\n", + "\u001b[31mNameError\u001b[39m: name 'results' is not defined" + ] + } + ], "source": [ "results[\"tracks\"][\"next\"] #link to the next page (next 50 tracks)" ] @@ -382,12 +502,540 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "29694252-f217-454d-8881-681b2b6eeb1e", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Figures\n", + "Imported (with 6LACK)\n", + "Figures, a Reprise\n", + "FOREVER (with 6LACK)\n", + "COFFIN (feat. Eminem)\n" + ] + } + ], + "source": [ + "# First tried the API and doublecheck with one artist if it works\n", + "'''\n", + "I used Last FM API to do this excersice, the API from spotipy don't work anymore and I don't have a premium account\n", + "'''\n", + "\n", + "import requests\n", + "\n", + "API_KEY = \"ERASED_FOR_SAFETY\"\n", + "\n", + "url = \"http://ws.audioscrobbler.com/2.0/\"\n", + "params = {\n", + " \"method\": \"artist.gettoptracks\",\n", + " \"artist\": \"Jessie Reyez\",\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": 5\n", + "}\n", + "\n", + "response = requests.get(url, params=params)\n", + "data = response.json()\n", + "\n", + "for track in data[\"toptracks\"][\"track\"]:\n", + " print(track[\"name\"])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "008714c9-7ce3-4ca7-bf56-486a08f5e923", + "metadata": {}, + "outputs": [], + "source": [ + "#List Your Favorite Artists:\n", + "artists = [\"One Direction\", \"Humbe\", \"Jessie Reyez\", \"Morat\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "54535bd0-dc2a-40f2-a950-2242ac3721ec", + "metadata": {}, + "outputs": [], + "source": [ + "#Fetch Top Tracks:\n", + "\n", + "def get_top_tracks(artist_name, limit=5):\n", + " params = {\n", + " \"method\": \"artist.gettoptracks\",\n", + " \"artist\": artist_name,\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": limit \n", + " }\n", + "\n", + " response = requests.get(url, params=params)\n", + " data = response.json()\n", + "\n", + " tracks = data.get(\"toptracks\",{}).get(\"track\", [])\n", + "\n", + " if isinstance(tracks,dict):\n", + " tracks = [tracks]\n", + "\n", + " return [track[\"name\"]for track in tracks[:limit]]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d42f3123-1292-447c-ac3a-6c66babfc378", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Fantasmas', 'Kintsugi', 'AMOR DE CINE', 'Te Lo Prometo', 'EL POETA']" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_top_tracks(\"Humbe\") #Test1" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "fb0a7df8-c71d-44f5-ba5e-70108e162fbc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Top Tracks List:\n" + ] + }, + { + "data": { + "text/plain": [ + "[['What Makes You Beautiful',\n", + " 'Night Changes',\n", + " 'Story of My Life',\n", + " 'Drag Me Down',\n", + " 'Steal My Girl'],\n", + " ['Fantasmas', 'Kintsugi', 'AMOR DE CINE', 'Te Lo Prometo', 'EL POETA'],\n", + " ['Figures',\n", + " 'Imported (with 6LACK)',\n", + " 'Figures, a Reprise',\n", + " 'FOREVER (with 6LACK)',\n", + " 'COFFIN (feat. Eminem)'],\n", + " ['Besos En Guerra',\n", + " 'Cuando Nadie Ve',\n", + " 'No Se Va',\n", + " 'Cómo Te Atreves',\n", + " 'Presiento']]" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Test 2\n", + "print(\"Top Tracks List:\")\n", + "top_tracks_list = [get_top_tracks(artist) for artist in artists]\n", + "\n", + "top_tracks_list" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "fccd5029-8788-4c89-a146-93606492f39a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'Latin mafia',\n", + " 'match': '1',\n", + " 'url': 'https://www.last.fm/music/Latin+mafia',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Kenia Os',\n", + " 'mbid': 'f28b704b-38e2-4b01-b51a-5337d7e5ac2d',\n", + " 'match': '0.451426',\n", + " 'url': 'https://www.last.fm/music/Kenia+Os',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Danna Paola',\n", + " 'mbid': '3ad91c4e-ef5a-4d7c-99e7-21a8b0c9379d',\n", + " 'match': '0.388265',\n", + " 'url': 'https://www.last.fm/music/Danna+Paola',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Manuel Medrano',\n", + " 'mbid': '43d48ba4-760e-48f9-8f15-3254aa2e7c37',\n", + " 'match': '0.381252',\n", + " 'url': 'https://www.last.fm/music/Manuel+Medrano',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'},\n", + " {'name': 'Jesse & Joy',\n", + " 'mbid': '64b85e52-08db-4995-a6e3-d2a6acc5a058',\n", + " 'match': '0.346041',\n", + " 'url': 'https://www.last.fm/music/Jesse+&+Joy',\n", + " 'image': [{'#text': 'https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'small'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'medium'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'large'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'extralarge'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': 'mega'},\n", + " {'#text': 'https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png',\n", + " 'size': ''}],\n", + " 'streamable': '0'}]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "params = {\n", + " \"method\": \"artist.getsimilar\",\n", + " \"artist\": \"Humbe\",\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": 5 \n", + " }\n", + "response = requests.get(url, params=params)\n", + "data = response.json()\n", + "data[\"similarartists\"][\"artist\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "52f31560-aabf-48f2-9bf7-2ee2779362ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"similarartists\": {\n", + " \"artist\": [\n", + " {\n", + " \"name\": \"Latin mafia\",\n", + " \"match\": \"1\",\n", + " \"url\": \"https://www.last.fm/music/Latin+mafia\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Kenia Os\",\n", + " \"mbid\": \"f28b704b-38e2-4b01-b51a-5337d7e5ac2d\",\n", + " \"match\": \"0.451426\",\n", + " \"url\": \"https://www.last.fm/music/Kenia+Os\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Danna Paola\",\n", + " \"mbid\": \"3ad91c4e-ef5a-4d7c-99e7-21a8b0c9379d\",\n", + " \"match\": \"0.388265\",\n", + " \"url\": \"https://www.last.fm/music/Danna+Paola\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Manuel Medrano\",\n", + " \"mbid\": \"43d48ba4-760e-48f9-8f15-3254aa2e7c37\",\n", + " \"match\": \"0.381252\",\n", + " \"url\": \"https://www.last.fm/music/Manuel+Medrano\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " },\n", + " {\n", + " \"name\": \"Jesse & Joy\",\n", + " \"mbid\": \"64b85e52-08db-4995-a6e3-d2a6acc5a058\",\n", + " \"match\": \"0.346041\",\n", + " \"url\": \"https://www.last.fm/music/Jesse+&+Joy\",\n", + " \"image\": [\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"small\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"medium\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"large\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"extralarge\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"mega\"\n", + " },\n", + " {\n", + " \"#text\": \"https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png\",\n", + " \"size\": \"\"\n", + " }\n", + " ],\n", + " \"streamable\": \"0\"\n", + " }\n", + " ],\n", + " \"@attr\": {\n", + " \"artist\": \"Humbe\"\n", + " }\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "import json\n", + "print(json.dumps(data, indent=2, ensure_ascii=False))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "d5d52eb0-d232-447d-ad56-c85c58176ea9", + "metadata": {}, "outputs": [], "source": [ - "# Your answer here" + "#Discover Related Artists\n", + "\n", + "def find_related_artists(artist_name, limit=5):\n", + " params = {\n", + " \"method\": \"artist.getsimilar\",\n", + " \"artist\": artist_name,\n", + " \"api_key\": API_KEY,\n", + " \"format\": \"json\",\n", + " \"limit\": limit \n", + " }\n", + " \n", + " response = requests.get(url, params=params)\n", + " data = response.json()\n", + "\n", + " similar_artists = data.get(\"similarartists\",{}).get(\"artist\", [])\n", + "\n", + " if isinstance(similar_artists,dict):\n", + " similar_artists = [similar_artists]\n", + "\n", + " return [artist[\"name\"]for artist in similar_artists[:limit]]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "2b7e056e-95a7-4380-80cf-634f256dfec3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Latin mafia', 'Kenia Os', 'Danna Paola', 'Manuel Medrano', 'Jesse & Joy']\n" + ] + } + ], + "source": [ + "print(find_related_artists(\"Humbe\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d4e65751-04f2-4d4f-83c5-d182261ef7ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Related Artists List:\n" + ] + }, + { + "data": { + "text/plain": [ + "[['Louis Tomlinson', 'Niall Horan', 'Liam Payne', 'ZAYN', 'Harry Styles'],\n", + " ['Latin mafia', 'Kenia Os', 'Danna Paola', 'Manuel Medrano', 'Jesse & Joy'],\n", + " ['Jessie Reyez & 6LACK', 'Kehlani', 'Kiana Ledé', 'Mahalia', 'Alessia Cara'],\n", + " ['Reik', 'TIMØ', 'Ha*Ash', 'Camila', 'HA-ASH']]" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Test with all artists\n", + "print(\"Related Artists List:\")\n", + "similar_artists_list = [find_related_artists(artist) for artist in artists]\n", + "\n", + "similar_artists_list" ] }, { @@ -533,20 +1181,215 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 41, "id": "ed92d961-9646-4375-a386-ccc320a958f5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting deezer-python\n", + " Downloading deezer_python-7.2.0-py3-none-any.whl.metadata (14 kB)\n", + "Requirement already satisfied: httpx>=0.23 in /opt/anaconda3/lib/python3.13/site-packages (from deezer-python) (0.28.1)\n", + "Requirement already satisfied: anyio in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (4.10.0)\n", + "Requirement already satisfied: certifi in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (2026.1.4)\n", + "Requirement already satisfied: httpcore==1.* in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (1.0.9)\n", + "Requirement already satisfied: idna in /opt/anaconda3/lib/python3.13/site-packages (from httpx>=0.23->deezer-python) (3.11)\n", + "Requirement already satisfied: h11>=0.16 in /opt/anaconda3/lib/python3.13/site-packages (from httpcore==1.*->httpx>=0.23->deezer-python) (0.16.0)\n", + "Requirement already satisfied: sniffio>=1.1 in /opt/anaconda3/lib/python3.13/site-packages (from anyio->httpx>=0.23->deezer-python) (1.3.0)\n", + "Downloading deezer_python-7.2.0-py3-none-any.whl (24 kB)\n", + "Installing collected packages: deezer-python\n", + "Successfully installed deezer-python-7.2.0\n" + ] + } + ], "source": [ - "# Your answer here" + "# I'll be using Deezer API for this part, found through Google tat is similar\n", + "!pip install deezer-python\n", + "\n", + "import deezer\n", + "client= deezer.Client()" ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "6893b655-1a3d-4d55-8499-c46fb8542c99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "#Featured Exploration\n", + "\n", + "playlists = client.search_playlists(\"pop\")\n", + "\n", + "for p in list(playlists)[:5]:\n", + " print(p)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "28422ef6-b654-4a68-afbf-f2fa234052ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: Happy Hits\n", + "ID: 1479458365\n", + "\n", + "Name: Global Hits\n", + "ID: 2098157264\n", + "\n", + "Name: Feel Good Pop\n", + "ID: 1964085082\n", + "\n", + "Name: Fresh Pop\n", + "ID: 2228601362\n", + "\n", + "Name: Chill Hits\n", + "ID: 1976454162\n", + "\n" + ] + } + ], + "source": [ + "top_5_playlists = list(playlists)[:5]\n", + "\n", + "for pl in top_5_playlists:\n", + " print(\"Name:\", pl.title)\n", + " print(\"ID:\", pl.id)\n", + " print()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "b6f9ea05-59b4-436d-83ca-325cf342f455", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: Happy Hits\n", + "Description: ¡Éxitos con buen rollo para animarte cuando lo necesites!\n", + "Total tracks: 71\n" + ] + } + ], + "source": [ + "#Deep Dive\n", + "chosen_playlist = top_5_playlists[0]\n", + "\n", + "print(\"Name:\", chosen_playlist.title)\n", + "print(\"Description:\", getattr(chosen_playlist,\"description\",\"No description\"))\n", + "print(\"Total tracks:\", chosen_playlist.nb_tracks)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "488e497b-8bd0-4be8-847c-c9a1d4750eab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WHERE IS MY HUSBAND!\n", + "I Just Might\n", + "12 to 12\n", + "The Fate of Ophelia\n", + "NUEVAYoL\n", + "Gone Gone Gone\n", + "Zoo (From \"Zootropolis 2\")\n", + "Lush Life\n", + "Gabriela\n", + "The Dead Dance\n" + ] + } + ], + "source": [ + "#Track-tastic\n", + "tracks = list(chosen_playlist.get_tracks())[:10]\n", + "for track in tracks:\n", + " print(track.title)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "65d2300a-ad59-4737-8628-05e8f6ac233b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'WHERE IS MY HUSBAND!': ['Raye'],\n", + " 'I Just Might': ['Bruno Mars'],\n", + " '12 to 12': ['sombr'],\n", + " 'The Fate of Ophelia': ['Taylor Swift'],\n", + " 'NUEVAYoL': ['Bad Bunny'],\n", + " 'Gone Gone Gone': ['David Guetta', 'Teddy Swims', 'Tones and I'],\n", + " 'Zoo (From \"Zootropolis 2\")': ['Disney', 'Shakira'],\n", + " 'Lush Life': ['Zara Larsson'],\n", + " 'Gabriela': ['KATSEYE'],\n", + " 'The Dead Dance': ['Lady Gaga']}" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Artistic Flair\n", + "''' ex: {\"TrackName1\": [\"Artist1\", \"Artist2\"], \"TrackName2\": [\"Artist3\"]}'''\n", + "track_artist_dict ={}\n", + "\n", + "for track in tracks:\n", + " if hasattr(track, \"contributors\") and track.contributors:\n", + " artist_names = [artist.name for artist in track.contributors]\n", + " elif hasattr(track, \"artists\") and track.artist:\n", + " artist_names = [track.artist.name]\n", + " else:\n", + " artist_names = []\n", + "\n", + " track_artist_dict[track.title] = artist_names\n", + " \n", + "\n", + "track_artist_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b051b4a3-d1c2-4e9c-b0e1-408bcc3affff", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python [conda env:base] *", "language": "python", - "name": "python3" + "name": "conda-base-py" }, "language_info": { "codemirror_mode": { @@ -558,7 +1401,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.13.9" } }, "nbformat": 4,