diff --git a/beatport_unified_scraper.py b/beatport_unified_scraper.py index ed039009..3c9e5aa7 100644 --- a/beatport_unified_scraper.py +++ b/beatport_unified_scraper.py @@ -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...") diff --git a/web_server.py b/web_server.py index 2a97b8aa..8b3f6d97 100644 --- a/web_server.py +++ b/web_server.py @@ -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""" diff --git a/webui/index.html b/webui/index.html index 3e66cf4e..64e28b04 100644 --- a/webui/index.html +++ b/webui/index.html @@ -850,6 +850,26 @@ + +
+
+

šŸ’æ Top 10 Releases

+

Most popular albums and EPs on Beatport

+
+ +
+
+ +
+
+

šŸ’æ Loading Top 10 Releases...

+

Fetching trending albums and EPs

+
+
+
+
+
+
diff --git a/webui/static/script.js b/webui/static/script.js index d74fc11c..bbe7fa24 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -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 = ` +
+ `; + + releases.forEach((release, index) => { + releasesHtml += ` +
+
${release.rank || index + 1}
+
+ ${release.image_url ? + `${release.title}` : + '
šŸ’æ
' + } +
+
+

${release.title || 'Unknown Title'}

+

${release.artist || 'Unknown Artist'}

+

${release.label || 'Unknown Label'}

+
+
+ `; + }); + + releasesHtml += '
'; + container.innerHTML = releasesHtml; +} + +/** + * Show error message for top 10 releases + */ +function showTop10ReleasesError(errorMessage) { + const container = document.getElementById('beatport-releases-top10-list'); + + const errorHtml = ` +
+

āŒ Error Loading Releases

+

${errorMessage}

+
+ `; + + if (container) container.innerHTML = errorHtml; +} diff --git a/webui/static/style.css b/webui/static/style.css index b4fb0c2c..b1b4ac5d 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -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;