Skip to content

Instantly share code, notes, and snippets.

@Rynaro
Last active September 25, 2024 17:16
Show Gist options
  • Save Rynaro/eb3db0e10dbed1bb0009b9ac0a975c32 to your computer and use it in GitHub Desktop.
Save Rynaro/eb3db0e10dbed1bb0009b9ac0a975c32 to your computer and use it in GitHub Desktop.
Spotify Playlist Track + Artist Scraper
// Script to extract track names and artists from your current playlist page and export them in CSV format.
// This script is intended for personal use to manage playlists efficiently.
// Any misuse of this script is not the responsibility of the author.
(async function() {
// Map to store tracks using `aria-rowindex` as the key
const tracksMap = new Map();
// Adjust these parameters if needed
const scrollDelay = 800; // Delay between scrolls in milliseconds
const scrollAmount = 300; // Scroll increment in pixels
const maxScrollAttempts = 1000; // Maximum number of scroll attempts to prevent infinite loops
// Target the identified scrollable container
const scrollableElement = document.querySelector('div.main-view-container [data-overlayscrollbars-viewport]');
if (!scrollableElement) {
console.error('Scrollable container not found.');
return;
}
// Helper function to pause execution
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Scroll and collect tracks
for (let attempt = 0; attempt < maxScrollAttempts; attempt++) {
// Scroll down by the specified amount
scrollableElement.scrollBy(0, scrollAmount);
// Wait for the DOM to update
await sleep(scrollDelay);
// Extract tracks currently in the DOM
const trackRows = scrollableElement.querySelectorAll('div[role="row"]');
trackRows.forEach(row => {
row.querySelector('[data-testid="tracklist-row"]');
const ariaRowIndex = row.getAttribute('aria-rowindex');
if (ariaRowIndex && !tracksMap.has(ariaRowIndex)) {
// Extract the song title
const songTitleElement = row.querySelector('a[data-testid="internal-track-link"] div[data-encore-id="text"]');
const songTitle = songTitleElement ? songTitleElement.textContent.trim() : 'Unknown Title';
// Extract the artist names
const artistElements = row.querySelectorAll('span.encore-text-body-small a');
const artistNames = artistElements.length > 0
? Array.from(artistElements).map(artist => artist.textContent.trim()).join(', ')
: 'Unknown Artist';
tracksMap.set(ariaRowIndex, { title: songTitle, artist: artistNames });
}
});
// Log progress
console.log(`Collected ${tracksMap.size} tracks so far...`);
// Check if all tracks are collected (stop scrolling)
// Assuming we know the total track count, break if collected all
const totalTracks = 336; // Replace with actual total if available
if (tracksMap.size >= totalTracks) {
console.log('All tracks have been collected.');
break;
}
}
// Convert the collected data to an array and sort by track index
const tracksArray = Array.from(tracksMap.entries())
.sort((a, b) => a[0] - b[0]) // Sort by track index
.map(entry => entry[1]); // Extract the track details
// Output the collected tracks
console.table(tracksArray);
// Optional: Export the data to a CSV file with UTF-8 BOM encoding
const exportToCSV = true; // Set to true if you want to export the data
if (exportToCSV) {
// Add BOM (Byte Order Mark) for UTF-8 encoding to handle special characters
const BOM = '\uFEFF';
let csvContent = 'Index,Title,Artist\n' + Array.from(tracksMap.entries())
.sort((a, b) => a[0] - b[0])
.map(entry => `${entry[0]},"${entry[1].title.replace(/"/g, '""')}","${entry[1].artist.replace(/"/g, '""')}"`)
.join('\n');
// Create Blob with UTF-8 BOM
let blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' });
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'spotify_playlist.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
console.log('CSV file has been downloaded.');
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment