beatport progress

This commit is contained in:
Broque Thomas 2025-09-30 12:36:25 -07:00
parent 4f33ac82ba
commit 5e73b53a0f
5 changed files with 788 additions and 0 deletions

View file

@ -1284,6 +1284,125 @@ class BeatportUnifiedScraper:
print(f" ❌ Error converting track data: {e}")
return None
def scrape_homepage_top10_lists(self) -> Dict[str, List[Dict]]:
"""Scrape Top 10 Lists from homepage - Beatport Top 10 and Hype Top 10"""
print("\n🏆 Scraping Top 10 Lists from homepage...")
soup = self.get_page(self.base_url)
if not soup:
return {"beatport_top10": [], "hype_top10": []}
# Extract Beatport Top 10 tracks
beatport_top10_items = soup.select('[data-testid="top-10-item"]')
print(f" Found {len(beatport_top10_items)} Beatport Top 10 items")
beatport_tracks = []
for i, item in enumerate(beatport_top10_items, 1):
try:
track_data = self.extract_track_from_top10_item(item, i, "Beatport Top 10")
if track_data:
beatport_tracks.append(track_data)
except Exception as e:
print(f" ❌ Error extracting Beatport track {i}: {e}")
# Extract Hype Top 10 tracks
hype_top10_items = soup.select('[data-testid="hype-top-10-item"]')
print(f" Found {len(hype_top10_items)} Hype Top 10 items")
hype_tracks = []
for i, item in enumerate(hype_top10_items, 1):
try:
track_data = self.extract_track_from_top10_item(item, i, "Hype Top 10")
if track_data:
hype_tracks.append(track_data)
except Exception as e:
print(f" ❌ Error extracting Hype track {i}: {e}")
print(f"✅ Extracted {len(beatport_tracks)} Beatport Top 10 + {len(hype_tracks)} Hype Top 10 tracks")
return {
"beatport_top10": beatport_tracks,
"hype_top10": hype_tracks
}
def extract_track_from_top10_item(self, item, rank, list_name):
"""Extract track data from a top 10 list item"""
try:
# Get the track URL
link_elem = item.select_one('a[href*="/track/"]')
track_url = ""
if link_elem and link_elem.get('href'):
track_url = f"https://www.beatport.com{link_elem.get('href')}"
# Extract track title
title = "Unknown Title"
title_selectors = [
'[class*="ItemName"]',
'[class*="TrackName"]',
'[class*="track-name"]',
'a[href*="/track/"]'
]
for selector in title_selectors:
title_elem = item.select_one(selector)
if title_elem:
title = title_elem.get_text(strip=True)
if title and title != "Unknown Title":
break
# Extract artist name
artist = "Unknown Artist"
artist_selectors = [
'[class*="Artists"]',
'[class*="artist"]',
'[class*="Artist"]',
'[class*="ItemArtist"]',
'a[href*="/artist/"]'
]
for selector in artist_selectors:
artist_elem = item.select_one(selector)
if artist_elem:
artist = artist_elem.get_text(strip=True)
if artist and artist != "Unknown Artist":
break
# Extract label name
label = "Unknown Label"
label_selectors = [
'[class*="Label"]',
'[class*="label"]',
'[class*="ItemLabel"]',
'a[href*="/label/"]'
]
for selector in label_selectors:
label_elem = item.select_one(selector)
if label_elem:
label = label_elem.get_text(strip=True)
if label and label != "Unknown Label":
break
# Extract artwork if available
artwork_url = ""
img_elem = item.select_one('img')
if img_elem and img_elem.get('src'):
artwork_url = img_elem.get('src')
return {
"rank": rank,
"title": title,
"artist": artist,
"label": label,
"url": track_url,
"artwork_url": artwork_url,
"list_name": list_name
}
except Exception as e:
print(f"Error extracting track data: {e}")
return None
def scrape_new_on_beatport_hero(self, limit: int = 10) -> List[Dict]:
"""Scrape the 'New on Beatport' hero slideshow from homepage"""
print("\n🎯 Scraping 'New on Beatport' hero slideshow...")

View file

@ -13228,6 +13228,40 @@ def get_beatport_homepage_top_10_releases():
"track_count": 0
}), 500
@app.route('/api/beatport/homepage/top-10-lists', methods=['GET'])
def get_beatport_homepage_top10_lists():
"""Get Beatport Top 10 Lists from homepage - both Beatport Top 10 and Hype Top 10"""
try:
logger.info("🏆 API request for Beatport homepage Top 10 Lists")
# Initialize the Beatport scraper
scraper = BeatportUnifiedScraper()
# Get top 10 lists from homepage
top10_lists = scraper.scrape_homepage_top10_lists()
logger.info(f"✅ Successfully extracted Beatport Top 10: {len(top10_lists['beatport_top10'])}, Hype Top 10: {len(top10_lists['hype_top10'])}")
return jsonify({
"success": True,
"beatport_top10": top10_lists["beatport_top10"],
"hype_top10": top10_lists["hype_top10"],
"beatport_count": len(top10_lists["beatport_top10"]),
"hype_count": len(top10_lists["hype_top10"]),
"source": "beatport_homepage_top10_lists"
})
except Exception as e:
logger.error(f"❌ Error getting Beatport homepage top 10 lists: {e}")
return jsonify({
"success": False,
"error": str(e),
"beatport_top10": [],
"hype_top10": [],
"beatport_count": 0,
"hype_count": 0
}), 500
@app.route('/api/beatport/homepage/featured-charts', methods=['GET'])
def get_beatport_homepage_featured_charts():
"""Get Beatport Featured Charts from homepage section"""

View file

@ -806,6 +806,50 @@
</div>
</div>
<!-- Top 10 Lists Section -->
<div class="beatport-top10-section">
<div class="beatport-top10-header">
<h2 class="beatport-top10-title">🏆 Top 10 Lists</h2>
<p class="beatport-top10-subtitle">Current trending tracks from Beatport charts</p>
</div>
<div class="beatport-top10-container">
<!-- Beatport Top 10 List -->
<div class="beatport-top10-list" id="beatport-top10-list">
<div class="beatport-top10-list-header">
<h3 class="beatport-top10-list-title">🎵 Beatport Top 10</h3>
<p class="beatport-top10-list-subtitle">Most popular tracks on Beatport</p>
</div>
<div class="beatport-top10-tracks" id="beatport-top10-tracks">
<!-- Loading placeholder -->
<div class="beatport-top10-loading">
<div class="beatport-top10-loading-content">
<h4>🎵 Loading Beatport Top 10...</h4>
<p>Fetching trending tracks</p>
</div>
</div>
</div>
</div>
<!-- Hype Top 10 List -->
<div class="beatport-hype10-list" id="beatport-hype10-list">
<div class="beatport-hype10-list-header">
<h3 class="beatport-hype10-list-title">🔥 Hype Top 10</h3>
<p class="beatport-hype10-list-subtitle">Editor's hottest trending picks</p>
</div>
<div class="beatport-hype10-tracks" id="beatport-hype10-tracks">
<!-- Loading placeholder -->
<div class="beatport-hype10-loading">
<div class="beatport-hype10-loading-content">
<h4>🔥 Loading Hype Top 10...</h4>
<p>Fetching editor's picks</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- New Releases Grid Slideshow Section -->
<div class="beatport-releases-section">
<div class="beatport-releases-header">

View file

@ -9734,6 +9734,7 @@ function initializeSyncPage() {
// Initialize rebuild slider if rebuild tab is selected
if (tabId === 'rebuild') {
initializeBeatportRebuildSlider();
loadBeatportTop10Lists();
initializeBeatportReleasesSlider();
initializeBeatportHypePicksSlider();
initializeBeatportChartsSlider();
@ -19719,3 +19720,127 @@ function cleanupBeatportDJSlider() {
beatportDJSliderState.autoPlayInterval = null;
}
}
/**
* Load top 10 lists data from API and populate both lists
*/
async function loadBeatportTop10Lists() {
try {
console.log('🏆 Loading top 10 lists data...');
const response = await fetch('/api/beatport/homepage/top-10-lists');
const data = await response.json();
if (data.success) {
console.log(`🎵 Loaded ${data.beatport_count} Beatport Top 10 + ${data.hype_count} Hype Top 10 tracks`);
// Populate both lists
populateBeatportTop10List(data.beatport_top10);
populateHypeTop10List(data.hype_top10);
return true;
} else {
console.error('Failed to load top 10 lists:', data.error);
showTop10ListsError(data.error || 'No data available');
return false;
}
} catch (error) {
console.error('Error loading top 10 lists:', error);
showTop10ListsError('Failed to load top 10 lists');
return false;
}
}
/**
* Populate Beatport Top 10 list with data
*/
function populateBeatportTop10List(tracks) {
const container = document.getElementById('beatport-top10-list');
if (!container || !tracks || tracks.length === 0) return;
// Generate HTML for the tracks
let tracksHtml = `
<div class="beatport-top10-list-header">
<h3 class="beatport-top10-list-title">🎵 Beatport Top 10</h3>
<p class="beatport-top10-list-subtitle">Most popular tracks on Beatport</p>
</div>
<div class="beatport-top10-tracks">
`;
tracks.forEach((track, index) => {
tracksHtml += `
<div class="beatport-top10-card" data-url="${track.url || '#'}">
<div class="beatport-top10-card-rank">${track.rank || index + 1}</div>
<div class="beatport-top10-card-artwork">
${track.artwork_url ?
`<img src="${track.artwork_url}" alt="${track.title}" loading="lazy">` :
'<div class="beatport-top10-card-placeholder">🎵</div>'
}
</div>
<div class="beatport-top10-card-info">
<h4 class="beatport-top10-card-title">${track.title || 'Unknown Title'}</h4>
<p class="beatport-top10-card-artist">${track.artist || 'Unknown Artist'}</p>
<p class="beatport-top10-card-label">${track.label || 'Unknown Label'}</p>
</div>
</div>
`;
});
tracksHtml += '</div>';
container.innerHTML = tracksHtml;
}
/**
* Populate Hype Top 10 list with data
*/
function populateHypeTop10List(tracks) {
const container = document.getElementById('beatport-hype10-list');
if (!container || !tracks || tracks.length === 0) return;
// Generate HTML for the tracks
let tracksHtml = `
<div class="beatport-hype10-list-header">
<h3 class="beatport-hype10-list-title">🔥 Hype Top 10</h3>
<p class="beatport-hype10-list-subtitle">Editor's trending picks</p>
</div>
<div class="beatport-hype10-tracks">
`;
tracks.forEach((track, index) => {
tracksHtml += `
<div class="beatport-hype10-card" data-url="${track.url || '#'}">
<div class="beatport-hype10-card-rank">${track.rank || index + 1}</div>
<div class="beatport-hype10-card-artwork">
${track.artwork_url ?
`<img src="${track.artwork_url}" alt="${track.title}" loading="lazy">` :
'<div class="beatport-hype10-card-placeholder">🔥</div>'
}
</div>
<div class="beatport-hype10-card-info">
<h4 class="beatport-hype10-card-title">${track.title || 'Unknown Title'}</h4>
<p class="beatport-hype10-card-artist">${track.artist || 'Unknown Artist'}</p>
<p class="beatport-hype10-card-label">${track.label || 'Unknown Label'}</p>
</div>
</div>
`;
});
tracksHtml += '</div>';
container.innerHTML = tracksHtml;
}
/**
* Show error message for top 10 lists
*/
function showTop10ListsError(errorMessage) {
const beatportContainer = document.getElementById('beatport-top10-list');
const hypeContainer = document.getElementById('beatport-hype10-list');
const errorHtml = `
<div class="beatport-top10-error">
<h3> Error Loading Data</h3>
<p>${errorMessage}</p>
</div>
`;
if (beatportContainer) beatportContainer.innerHTML = errorHtml;
if (hypeContainer) hypeContainer.innerHTML = errorHtml;
}

View file

@ -12040,6 +12040,472 @@ body {
}
}
/* =======================================
BEATPORT TOP 10 LISTS SECTION
======================================= */
.beatport-top10-section {
margin-top: 40px;
padding: 0 30px;
margin-bottom: 40px;
}
.beatport-top10-header {
text-align: center;
margin-bottom: 40px;
}
.beatport-top10-title {
font-size: 36px;
font-weight: 700;
color: #ffffff;
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.beatport-top10-subtitle {
font-size: 16px;
color: rgba(255, 255, 255, 0.7);
margin: 0;
}
/* Two-column container for both lists */
.beatport-top10-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
max-width: 1400px;
margin: 0 auto;
overflow: hidden; /* Prevent overflow */
}
/* Individual list styling */
.beatport-top10-list, .beatport-hype10-list {
background: linear-gradient(135deg,
rgba(18, 18, 18, 0.95),
rgba(30, 30, 30, 0.9));
border-radius: 20px;
padding: 30px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
width: 100%;
min-width: 0; /* Allow shrinking */
overflow: hidden; /* Prevent content overflow */
}
/* Color-coded themes */
.beatport-top10-list {
border-left: 4px solid #00ff88;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.5),
0 0 20px rgba(0, 255, 136, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.beatport-hype10-list {
border-left: 4px solid #ff3366;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.5),
0 0 20px rgba(255, 51, 102, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
/* List headers */
.beatport-top10-list-header, .beatport-hype10-list-header {
text-align: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.beatport-top10-list-title, .beatport-hype10-list-title {
font-size: 24px;
font-weight: 600;
color: #ffffff;
margin-bottom: 5px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.beatport-top10-list-subtitle, .beatport-hype10-list-subtitle {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
margin: 0;
}
/* Track containers */
.beatport-top10-tracks, .beatport-hype10-tracks {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Individual track cards - CORRECTED CLASS NAMES */
.beatport-top10-card, .beatport-hype10-card {
display: flex;
align-items: center;
background: linear-gradient(135deg,
rgba(25, 25, 25, 0.9),
rgba(35, 35, 35, 0.8));
border-radius: 16px;
padding: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(10px);
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
cursor: pointer;
position: relative;
overflow: hidden;
width: 100%;
min-width: 0; /* Allow shrinking */
box-sizing: border-box;
min-height: 80px; /* Consistent height */
max-height: 80px; /* Prevent expansion */
}
/* Hover animations for cards */
.beatport-top10-card:hover {
background: linear-gradient(135deg,
rgba(0, 255, 136, 0.12),
rgba(35, 35, 35, 0.95));
border-color: rgba(0, 255, 136, 0.3);
transform: translateY(-3px);
box-shadow:
0 12px 32px rgba(0, 0, 0, 0.5),
0 0 20px rgba(0, 255, 136, 0.2);
}
.beatport-hype10-card:hover {
background: linear-gradient(135deg,
rgba(255, 51, 102, 0.12),
rgba(35, 35, 35, 0.95));
border-color: rgba(255, 51, 102, 0.3);
transform: translateY(-3px);
box-shadow:
0 12px 32px rgba(0, 0, 0, 0.5),
0 0 20px rgba(255, 51, 102, 0.2);
}
/* Track rank number */
.beatport-top10-card-rank {
font-size: 20px;
font-weight: 800;
color: #00ff88;
min-width: 35px;
text-align: center;
margin-right: 16px;
text-shadow: 0 2px 6px rgba(0, 255, 136, 0.3);
background: linear-gradient(135deg, rgba(0, 255, 136, 0.1), rgba(0, 255, 136, 0.05));
border-radius: 12px;
padding: 8px 0;
border: 1px solid rgba(0, 255, 136, 0.2);
}
.beatport-hype10-card-rank {
font-size: 20px;
font-weight: 800;
color: #ff3366;
min-width: 35px;
text-align: center;
margin-right: 16px;
text-shadow: 0 2px 6px rgba(255, 51, 102, 0.3);
background: linear-gradient(135deg, rgba(255, 51, 102, 0.1), rgba(255, 51, 102, 0.05));
border-radius: 12px;
padding: 8px 0;
border: 1px solid rgba(255, 51, 102, 0.2);
}
/* Track artwork */
.beatport-top10-card-artwork, .beatport-hype10-card-artwork {
width: 56px;
height: 56px;
border-radius: 12px;
margin-right: 16px;
overflow: hidden;
position: relative;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
border: 2px solid rgba(255, 255, 255, 0.1);
}
.beatport-top10-card-artwork img, .beatport-hype10-card-artwork img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.beatport-top10-card:hover .beatport-top10-card-artwork img,
.beatport-hype10-card:hover .beatport-hype10-card-artwork img {
transform: scale(1.15);
}
/* Artwork placeholder */
.beatport-top10-card-placeholder, .beatport-hype10-card-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
background: linear-gradient(135deg, rgba(40, 40, 40, 0.8), rgba(60, 60, 60, 0.6));
color: rgba(255, 255, 255, 0.4);
}
/* Track info */
.beatport-top10-card-info, .beatport-hype10-card-info {
flex: 1;
min-width: 0; /* Critical for text truncation */
display: flex;
flex-direction: column;
gap: 4px;
overflow: hidden; /* Prevent text overflow */
width: 100%;
}
.beatport-top10-card-title, .beatport-hype10-card-title {
font-size: 15px;
font-weight: 700;
color: #ffffff;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
max-width: 100%;
width: 100%;
}
.beatport-top10-card-artist, .beatport-hype10-card-artist {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
width: 100%;
}
.beatport-top10-card-label {
font-size: 11px;
color: rgba(0, 255, 136, 0.9);
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-transform: uppercase;
letter-spacing: 0.5px;
max-width: 100%;
width: 100%;
}
.beatport-hype10-card-label {
font-size: 11px;
color: rgba(255, 51, 102, 0.9);
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-transform: uppercase;
letter-spacing: 0.5px;
max-width: 100%;
width: 100%;
}
/* Error states */
.beatport-top10-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
background: linear-gradient(135deg,
rgba(255, 51, 51, 0.1),
rgba(40, 40, 40, 0.8));
border-radius: 16px;
border: 1px solid rgba(255, 51, 51, 0.2);
text-align: center;
padding: 30px;
}
.beatport-top10-error h3 {
color: #ff3366;
font-size: 18px;
margin-bottom: 8px;
}
.beatport-top10-error p {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
margin: 0;
}
/* Loading states */
.beatport-top10-loading, .beatport-hype10-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 300px;
background: linear-gradient(135deg,
rgba(16, 16, 16, 0.6),
rgba(24, 24, 24, 0.4));
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.beatport-top10-loading-content, .beatport-hype10-loading-content {
text-align: center;
}
.beatport-top10-loading-content h4, .beatport-hype10-loading-content h4 {
font-size: 18px;
color: #ffffff;
margin-bottom: 8px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
.beatport-top10-loading-content p, .beatport-hype10-loading-content p {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
margin: 0;
}
/* Top 10 Lists Responsive Design */
@media (max-width: 1300px) {
.beatport-top10-card-title, .beatport-hype10-card-title {
font-size: 14px;
}
.beatport-top10-card-artist, .beatport-hype10-card-artist {
font-size: 12px;
}
.beatport-top10-card-label, .beatport-hype10-card-label {
font-size: 10px;
}
.beatport-top10-card-rank, .beatport-hype10-card-rank {
font-size: 18px;
}
}
@media (max-width: 1200px) {
.beatport-top10-container {
gap: 30px;
max-width: 1200px;
}
.beatport-top10-list, .beatport-hype10-list {
padding: 25px;
max-width: 100%;
}
.beatport-top10-card, .beatport-hype10-card {
min-height: 75px;
max-height: 75px;
}
.beatport-top10-card-artwork, .beatport-hype10-card-artwork {
width: 48px;
height: 48px;
}
}
@media (max-width: 900px) {
.beatport-top10-container {
grid-template-columns: 1fr;
gap: 25px;
}
.beatport-top10-section {
padding: 0 20px;
}
.beatport-top10-title {
font-size: 28px;
}
.beatport-top10-list-title, .beatport-hype10-list-title {
font-size: 20px;
}
.beatport-top10-card, .beatport-hype10-card {
padding: 12px;
min-height: 70px;
max-height: 70px;
}
.beatport-top10-card-artwork, .beatport-hype10-card-artwork {
width: 42px;
height: 42px;
margin-right: 12px;
}
.beatport-top10-card-rank, .beatport-hype10-card-rank {
font-size: 16px;
min-width: 30px;
margin-right: 12px;
padding: 6px 0;
}
}
@media (max-width: 600px) {
.beatport-top10-section {
padding: 0 15px;
margin-top: 30px;
margin-bottom: 30px;
}
.beatport-top10-header {
margin-bottom: 30px;
}
.beatport-top10-title {
font-size: 24px;
}
.beatport-top10-list, .beatport-hype10-list {
padding: 20px;
}
.beatport-top10-list-title, .beatport-hype10-list-title {
font-size: 18px;
}
.beatport-top10-card, .beatport-hype10-card {
padding: 10px;
min-height: 65px;
max-height: 65px;
}
.beatport-top10-card-artwork, .beatport-hype10-card-artwork {
width: 38px;
height: 38px;
margin-right: 10px;
}
.beatport-top10-card-rank, .beatport-hype10-card-rank {
font-size: 14px;
min-width: 26px;
padding: 4px 0;
margin-right: 10px;
}
.beatport-top10-card-title, .beatport-hype10-card-title {
font-size: 13px;
}
.beatport-top10-card-artist, .beatport-hype10-card-artist {
font-size: 11px;
}
.beatport-top10-card-label, .beatport-hype10-card-label {
font-size: 9px;
}
}
/* ================================
BEATPORT NEW RELEASES SECTION
================================ */