beatport progress

This commit is contained in:
Broque Thomas 2025-09-30 13:23:01 -07:00
parent 5e73b53a0f
commit 69c8622588
5 changed files with 771 additions and 0 deletions

View file

@ -1403,6 +1403,263 @@ class BeatportUnifiedScraper:
print(f"Error extracting track data: {e}")
return None
def scrape_homepage_top10_releases(self) -> List[Dict]:
"""Scrape Top 10 Releases from homepage - FIXED VERSION"""
print("\n💿 FIXED: Scraping Top 10 Releases from homepage...")
soup = self.get_page(self.base_url)
if not soup:
print(" ❌ Could not get homepage")
return []
# Extract Top 10 Releases items - EXACT same as test script
top10_releases_items = soup.select('[data-testid="top-10-releases-item"]')
print(f" FOUND {len(top10_releases_items)} Top 10 Releases items")
if len(top10_releases_items) == 0:
print(" ❌ No items found - trying alternatives")
return []
releases = []
for i, item in enumerate(top10_releases_items, 1):
try:
# Use the SAME function name as the test script
release_data = self.extract_release_from_item_FIXED(item, i)
if release_data:
releases.append(release_data)
print(f"{i}. {release_data['artist']} - {release_data['title']}")
else:
print(f"{i}. No data extracted")
except Exception as e:
print(f" ❌ Error extracting release {i}: {e}")
print(f"✅ FINAL: Extracted {len(releases)} Top 10 Releases")
return releases
def extract_release_from_item_FIXED(self, item, rank):
"""Extract release data from a list item - EXACT COPY FROM WORKING TEST SCRIPT"""
try:
# Get the release URL
link_elem = item.select_one('a[href*="/release/"]')
release_url = ""
if link_elem and link_elem.get('href'):
release_url = f"https://www.beatport.com{link_elem.get('href')}"
# Extract release title
title = "Unknown Title"
# Try multiple selectors for title
title_selectors = [
'[class*="ItemName"]',
'[class*="ReleaseName"]',
'[class*="release-name"]',
'[class*="TrackName"]',
'[class*="track-name"]',
'a[href*="/release/"]',
'h3', 'h4', 'h5',
'[class*="title"]',
'[class*="Title"]'
]
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" and len(title) > 2:
break
# Extract artist name - try multiple approaches
artist = "Unknown Artist"
artist_selectors = [
'[class*="Artists"]',
'[class*="artist"]',
'[class*="Artist"]',
'[class*="ItemArtist"]',
'a[href*="/artist/"]',
'[class*="by"]',
'[class*="By"]'
]
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" and len(artist) > 1:
break
# Extract label name - try multiple approaches
label = "Unknown Label"
label_selectors = [
'[class*="Label"]',
'[class*="label"]',
'[class*="ItemLabel"]',
'a[href*="/label/"]',
'[class*="imprint"]',
'[class*="Imprint"]'
]
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" and len(label) > 2:
break
# Extract image URL - this is important for releases
image_url = ""
image_selectors = [
'img[src]',
'img[data-src]',
'img[data-lazy]',
'[style*="background-image"]',
'picture img',
'.artwork img',
'[class*="artwork"] img',
'[class*="Artwork"] img',
'[class*="image"] img',
'[class*="Image"] img'
]
for selector in image_selectors:
img_elem = item.select_one(selector)
if img_elem:
# Try different image source attributes
img_src = (img_elem.get('src') or
img_elem.get('data-src') or
img_elem.get('data-lazy') or
img_elem.get('data-original'))
if img_src and img_src.startswith(('http', '//')):
image_url = img_src
break
elif img_src and img_src.startswith('/'):
image_url = f"https://www.beatport.com{img_src}"
break
return {
"rank": rank,
"title": title,
"artist": artist,
"label": label,
"url": release_url,
"image_url": image_url,
"list_name": "Top 10 Releases"
}
except Exception as e:
print(f"Error extracting release data: {e}")
return None
def extract_release_from_top10_item(self, item, rank):
"""Extract release data from a top 10 releases item"""
try:
# Get the release URL
link_elem = item.select_one('a[href*="/release/"]')
release_url = ""
if link_elem and link_elem.get('href'):
release_url = f"https://www.beatport.com{link_elem.get('href')}"
# Extract release title
title = "Unknown Title"
title_selectors = [
'[class*="ItemName"]',
'[class*="ReleaseName"]',
'[class*="release-name"]',
'[class*="TrackName"]',
'[class*="track-name"]',
'a[href*="/release/"]',
'h3', 'h4', 'h5',
'[class*="title"]',
'[class*="Title"]'
]
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" and len(title) > 2:
break
# Extract artist name
artist = "Unknown Artist"
artist_selectors = [
'[class*="Artists"]',
'[class*="artist"]',
'[class*="Artist"]',
'[class*="ItemArtist"]',
'a[href*="/artist/"]',
'[class*="by"]',
'[class*="By"]'
]
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" and len(artist) > 1:
break
# Extract label name
label = "Unknown Label"
label_selectors = [
'[class*="Label"]',
'[class*="label"]',
'[class*="ItemLabel"]',
'a[href*="/label/"]',
'[class*="imprint"]',
'[class*="Imprint"]'
]
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" and len(label) > 2:
break
# Extract image URL - important for releases
image_url = ""
image_selectors = [
'img[src]',
'img[data-src]',
'img[data-lazy]',
'[style*="background-image"]',
'picture img',
'.artwork img',
'[class*="artwork"] img',
'[class*="Artwork"] img',
'[class*="image"] img',
'[class*="Image"] img'
]
for selector in image_selectors:
img_elem = item.select_one(selector)
if img_elem:
img_src = (img_elem.get('src') or
img_elem.get('data-src') or
img_elem.get('data-lazy') or
img_elem.get('data-original'))
if img_src and img_src.startswith(('http', '//')):
image_url = img_src
break
elif img_src and img_src.startswith('/'):
image_url = f"https://www.beatport.com{img_src}"
break
return {
"rank": rank,
"title": title,
"artist": artist,
"label": label,
"url": release_url,
"image_url": image_url,
"list_name": "Top 10 Releases"
}
except Exception as e:
print(f"Error extracting release 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

@ -13262,6 +13262,44 @@ def get_beatport_homepage_top10_lists():
"hype_count": 0
}), 500
@app.route('/api/beatport/homepage/top-10-releases-cards', methods=['GET'])
def get_beatport_homepage_top10_releases_cards():
"""Get Beatport Top 10 Releases CARDS from homepage (not individual tracks)"""
try:
logger.info("💿 API request for Beatport homepage Top 10 Releases CARDS")
# Initialize the Beatport scraper
scraper = BeatportUnifiedScraper()
# Get top 10 releases from homepage
top10_releases = scraper.scrape_homepage_top10_releases()
logger.info(f"✅ API extracted {len(top10_releases)} Top 10 Release Cards")
# Debug: Log first release if any
if top10_releases:
logger.info(f"First release: {top10_releases[0].get('title', 'No title')} by {top10_releases[0].get('artist', 'No artist')}")
else:
logger.warning("❌ No releases found by scraper")
return jsonify({
"success": True,
"releases": top10_releases,
"releases_count": len(top10_releases),
"source": "beatport_homepage_top10_releases_cards"
})
except Exception as e:
logger.error(f"❌ Error getting Beatport homepage Top 10 Releases cards: {e}")
import traceback
logger.error(f"Full traceback: {traceback.format_exc()}")
return jsonify({
"success": False,
"error": str(e),
"releases": [],
"releases_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

@ -850,6 +850,26 @@
</div>
</div>
<!-- Top 10 Releases Section -->
<div class="beatport-releases-top10-section">
<div class="beatport-releases-top10-header">
<h2 class="beatport-releases-top10-title">💿 Top 10 Releases</h2>
<p class="beatport-releases-top10-subtitle">Most popular albums and EPs on Beatport</p>
</div>
<div class="beatport-releases-top10-container">
<div class="beatport-releases-top10-list" id="beatport-releases-top10-list">
<!-- Loading placeholder -->
<div class="beatport-releases-top10-loading">
<div class="beatport-releases-top10-loading-content">
<h4>💿 Loading Top 10 Releases...</h4>
<p>Fetching trending albums and EPs</p>
</div>
</div>
</div>
</div>
</div>
<!-- New Releases Grid Slideshow Section -->
<div class="beatport-releases-section">
<div class="beatport-releases-header">

View file

@ -9735,6 +9735,7 @@ function initializeSyncPage() {
if (tabId === 'rebuild') {
initializeBeatportRebuildSlider();
loadBeatportTop10Lists();
loadBeatportTop10Releases();
initializeBeatportReleasesSlider();
initializeBeatportHypePicksSlider();
initializeBeatportChartsSlider();
@ -19844,3 +19845,79 @@ function showTop10ListsError(errorMessage) {
if (beatportContainer) beatportContainer.innerHTML = errorHtml;
if (hypeContainer) hypeContainer.innerHTML = errorHtml;
}
/**
* Load top 10 releases data from API and populate the list
*/
async function loadBeatportTop10Releases() {
try {
console.log('💿 Loading top 10 releases data...');
const response = await fetch('/api/beatport/homepage/top-10-releases-cards');
const data = await response.json();
if (data.success) {
console.log(`💿 Loaded ${data.releases_count} Top 10 Releases`);
populateBeatportTop10Releases(data.releases);
return true;
} else {
console.error('Failed to load top 10 releases:', data.error);
showTop10ReleasesError(data.error || 'No data available');
return false;
}
} catch (error) {
console.error('Error loading top 10 releases:', error);
showTop10ReleasesError('Failed to load top 10 releases');
return false;
}
}
/**
* Populate Top 10 Releases list with data
*/
function populateBeatportTop10Releases(releases) {
const container = document.getElementById('beatport-releases-top10-list');
if (!container || !releases || releases.length === 0) return;
// Generate HTML for the releases
let releasesHtml = `
<div class="beatport-releases-top10-tracks">
`;
releases.forEach((release, index) => {
releasesHtml += `
<div class="beatport-releases-top10-card" data-url="${release.url || '#'}">
<div class="beatport-releases-top10-card-rank">${release.rank || index + 1}</div>
<div class="beatport-releases-top10-card-artwork">
${release.image_url ?
`<img src="${release.image_url}" alt="${release.title}" loading="lazy">` :
'<div class="beatport-releases-top10-card-placeholder">💿</div>'
}
</div>
<div class="beatport-releases-top10-card-info">
<h4 class="beatport-releases-top10-card-title">${release.title || 'Unknown Title'}</h4>
<p class="beatport-releases-top10-card-artist">${release.artist || 'Unknown Artist'}</p>
<p class="beatport-releases-top10-card-label">${release.label || 'Unknown Label'}</p>
</div>
</div>
`;
});
releasesHtml += '</div>';
container.innerHTML = releasesHtml;
}
/**
* Show error message for top 10 releases
*/
function showTop10ReleasesError(errorMessage) {
const container = document.getElementById('beatport-releases-top10-list');
const errorHtml = `
<div class="beatport-releases-top10-error">
<h3> Error Loading Releases</h3>
<p>${errorMessage}</p>
</div>
`;
if (container) container.innerHTML = errorHtml;
}

View file

@ -12506,6 +12506,384 @@ body {
}
}
/* =======================================
BEATPORT TOP 10 RELEASES SECTION
======================================= */
.beatport-releases-top10-section {
margin-top: 40px;
padding: 0 30px;
margin-bottom: 40px;
}
.beatport-releases-top10-header {
text-align: center;
margin-bottom: 40px;
}
.beatport-releases-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-releases-top10-subtitle {
font-size: 16px;
color: rgba(255, 255, 255, 0.7);
margin: 0;
}
/* Single column container for releases */
.beatport-releases-top10-container {
max-width: 1200px;
margin: 0 auto;
}
/* List styling with unique design */
.beatport-releases-top10-list {
background: linear-gradient(135deg,
rgba(20, 20, 20, 0.95),
rgba(35, 35, 35, 0.9));
border-radius: 24px;
padding: 40px;
backdrop-filter: blur(25px);
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.6),
0 0 25px rgba(138, 43, 226, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
border-left: 4px solid #8a2be2;
overflow: hidden;
}
/* Track containers with grid layout for releases */
.beatport-releases-top10-tracks {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
/* Individual release cards - unique design with images */
.beatport-releases-top10-card {
display: flex;
align-items: center;
background: linear-gradient(135deg,
rgba(30, 30, 30, 0.9),
rgba(45, 45, 45, 0.85));
border-radius: 20px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(15px);
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
cursor: pointer;
position: relative;
overflow: hidden;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
min-height: 120px;
max-height: 120px;
width: 100%;
min-width: 0;
box-sizing: border-box;
}
/* Hover animations for release cards */
.beatport-releases-top10-card:hover {
background: linear-gradient(135deg,
rgba(138, 43, 226, 0.15),
rgba(45, 45, 45, 0.95));
border-color: rgba(138, 43, 226, 0.4);
transform: translateY(-5px) scale(1.02);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.6),
0 0 30px rgba(138, 43, 226, 0.25);
}
/* Release rank number */
.beatport-releases-top10-card-rank {
font-size: 24px;
font-weight: 900;
color: #8a2be2;
min-width: 40px;
text-align: center;
margin-right: 20px;
text-shadow: 0 3px 8px rgba(138, 43, 226, 0.4);
background: linear-gradient(135deg, rgba(138, 43, 226, 0.15), rgba(138, 43, 226, 0.08));
border-radius: 16px;
padding: 12px 0;
border: 2px solid rgba(138, 43, 226, 0.3);
backdrop-filter: blur(10px);
}
/* Release artwork - larger since these have images */
.beatport-releases-top10-card-artwork {
width: 80px;
height: 80px;
border-radius: 16px;
margin-right: 20px;
overflow: hidden;
position: relative;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
border: 3px solid rgba(255, 255, 255, 0.15);
backdrop-filter: blur(5px);
}
.beatport-releases-top10-card-artwork img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.beatport-releases-top10-card:hover .beatport-releases-top10-card-artwork img {
transform: scale(1.2) rotate(2deg);
}
/* Artwork placeholder for releases */
.beatport-releases-top10-card-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
background: linear-gradient(135deg, rgba(138, 43, 226, 0.2), rgba(80, 80, 80, 0.8));
color: rgba(255, 255, 255, 0.6);
}
/* Release info */
.beatport-releases-top10-card-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
overflow: hidden;
width: 100%;
}
.beatport-releases-top10-card-title {
font-size: 18px;
font-weight: 800;
color: #ffffff;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.9);
max-width: 100%;
width: 100%;
}
.beatport-releases-top10-card-artist {
font-size: 15px;
color: rgba(255, 255, 255, 0.9);
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
width: 100%;
}
.beatport-releases-top10-card-label {
font-size: 13px;
color: rgba(138, 43, 226, 0.95);
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-transform: uppercase;
letter-spacing: 0.8px;
max-width: 100%;
width: 100%;
}
/* Loading states for releases */
.beatport-releases-top10-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
background: linear-gradient(135deg,
rgba(20, 20, 20, 0.7),
rgba(30, 30, 30, 0.5));
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.beatport-releases-top10-loading-content {
text-align: center;
}
.beatport-releases-top10-loading-content h4 {
font-size: 20px;
color: #ffffff;
margin-bottom: 10px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
.beatport-releases-top10-loading-content p {
font-size: 15px;
color: rgba(255, 255, 255, 0.7);
margin: 0;
}
/* Error states for releases */
.beatport-releases-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.12),
rgba(45, 45, 45, 0.9));
border-radius: 20px;
border: 1px solid rgba(255, 51, 51, 0.25);
text-align: center;
padding: 40px;
}
.beatport-releases-top10-error h3 {
color: #ff3366;
font-size: 20px;
margin-bottom: 10px;
}
.beatport-releases-top10-error p {
color: rgba(255, 255, 255, 0.8);
font-size: 15px;
margin: 0;
}
/* Responsive Design for Top 10 Releases */
@media (max-width: 1300px) {
.beatport-releases-top10-card-title {
font-size: 16px;
}
.beatport-releases-top10-card-artist {
font-size: 14px;
}
.beatport-releases-top10-card-label {
font-size: 12px;
}
.beatport-releases-top10-card-rank {
font-size: 20px;
}
}
@media (max-width: 1200px) {
.beatport-releases-top10-tracks {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
.beatport-releases-top10-list {
padding: 30px;
}
.beatport-releases-top10-card {
min-height: 110px;
max-height: 110px;
padding: 16px;
}
.beatport-releases-top10-card-artwork {
width: 70px;
height: 70px;
}
}
@media (max-width: 900px) {
.beatport-releases-top10-tracks {
grid-template-columns: 1fr;
gap: 14px;
}
.beatport-releases-top10-section {
padding: 0 20px;
}
.beatport-releases-top10-title {
font-size: 28px;
}
.beatport-releases-top10-card {
min-height: 100px;
max-height: 100px;
padding: 14px;
}
.beatport-releases-top10-card-artwork {
width: 60px;
height: 60px;
margin-right: 16px;
}
.beatport-releases-top10-card-rank {
font-size: 18px;
min-width: 35px;
margin-right: 16px;
padding: 8px 0;
}
}
@media (max-width: 600px) {
.beatport-releases-top10-section {
padding: 0 15px;
margin-top: 30px;
margin-bottom: 30px;
}
.beatport-releases-top10-header {
margin-bottom: 30px;
}
.beatport-releases-top10-list {
padding: 20px;
}
.beatport-releases-top10-title {
font-size: 24px;
}
.beatport-releases-top10-card {
min-height: 90px;
max-height: 90px;
padding: 12px;
}
.beatport-releases-top10-card-artwork {
width: 50px;
height: 50px;
margin-right: 12px;
}
.beatport-releases-top10-card-rank {
font-size: 16px;
min-width: 30px;
margin-right: 12px;
padding: 6px 0;
}
.beatport-releases-top10-card-title {
font-size: 14px;
}
.beatport-releases-top10-card-artist {
font-size: 12px;
}
.beatport-releases-top10-card-label {
font-size: 10px;
}
}
/* ================================
BEATPORT NEW RELEASES SECTION
================================ */
@ -13863,6 +14241,7 @@ body {
}
.beatport-dj-creator {
display: none;
font-size: 16px;
color: rgba(255, 255, 255, 0.9);
font-weight: 500;