From b186fed05b512e1bfd8c1c1028316c4169eefbda Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:56:00 -0700 Subject: [PATCH] Fix Beatport new releases scraper broken by hashed CSS class changes Replace all hardcoded styled-components class selectors (with build hashes that break on every Beatport deploy) with partial class name matches using [class*="prefix"] pattern. Applies to new releases endpoint and hero release scraper. --- beatport_unified_scraper.py | 10 +++++----- web_server.py | 33 +++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/beatport_unified_scraper.py b/beatport_unified_scraper.py index ab02715d..3a1a2f42 100644 --- a/beatport_unified_scraper.py +++ b/beatport_unified_scraper.py @@ -1905,7 +1905,7 @@ class BeatportUnifiedScraper: # Method 2 (FALLBACK): Look for the specific wrapper class (legacy support) if len(tracks) < 5: - hero_wrapper = soup.find('div', class_='Homepage-style__NewOnBeatportWrapper-sc-deeb4244-2 iyIchZ') + hero_wrapper = soup.select_one('[class*="Homepage-style__NewOnBeatportWrapper"]') if hero_wrapper: print(" ✅ Found Homepage NewOnBeatportWrapper (fallback)") tracks.extend(self._extract_from_hero_wrapper(hero_wrapper, limit)) @@ -3788,7 +3788,7 @@ class BeatportUnifiedScraper: data['release_slug'] = url_parts[1] # Extract release title - title_elem = release_element.select_one('.HeroRelease-style__ReleaseName-sc-aeec852a-3') + title_elem = release_element.select_one('[class*="HeroRelease-style__ReleaseName"]') if title_elem: data['title'] = self.clean_text(title_elem.get_text(strip=True)) @@ -3799,7 +3799,7 @@ class BeatportUnifiedScraper: data['alt_text'] = img_elem.get('alt', '') # Extract artists - artists_container = release_element.select_one('.HeroRelease-style__Artists-sc-aeec852a-1') + artists_container = release_element.select_one('[class*="HeroRelease-style__Artists"]') if artists_container: artist_links = artists_container.find_all('a') artists = [] @@ -3817,7 +3817,7 @@ class BeatportUnifiedScraper: data['artists_string'] = ', '.join([a['name'] for a in artists]) # Extract label - label_elem = release_element.select_one('.HeroRelease-style__Label-sc-aeec852a-0') + label_elem = release_element.select_one('[class*="HeroRelease-style__Label"]') if label_elem: label_link = label_elem.find('a') if label_link: @@ -3826,7 +3826,7 @@ class BeatportUnifiedScraper: data['label_beatport_url'] = urljoin(self.base_url, data['label_url']) if data['label_url'] else None # Extract any badges (like EXCLUSIVE) - badges_elem = release_element.select_one('.HeroRelease-style__Badges-sc-aeec852a-8') + badges_elem = release_element.select_one('[class*="HeroRelease-style__Badges"]') if badges_elem: badge_text = self.clean_text(badges_elem.get_text(strip=True)) if badge_text: diff --git a/web_server.py b/web_server.py index 4ad27965..b81b96f6 100644 --- a/web_server.py +++ b/web_server.py @@ -6251,8 +6251,27 @@ def get_beatport_new_releases(): if not soup: raise Exception("Could not fetch Beatport homepage") - # Extract release cards using the working CSS selector - release_cards = soup.select('.ReleaseCard-style__Wrapper-sc-7c61989b-12.duhBUN') + # Find New Releases GridSlider container by section heading + # Use partial class match to avoid brittle hashed class names + gridsliders = soup.select('[class*="GridSlider-style__Wrapper"]') + releases_container = None + + for container in gridsliders: + h2 = container.select_one('h2') + if h2: + title = h2.get_text(strip=True).lower() + if 'new release' in title: + releases_container = container + logger.info(f"🆕 FOUND NEW RELEASES: '{h2.get_text(strip=True)}'") + break + + # Fallback: try ReleaseCard partial class match on whole page + if releases_container: + release_cards = releases_container.select('[class*="ReleaseCard-style__Wrapper"]') + else: + logger.warning("⚠️ No New Releases GridSlider found, trying page-wide ReleaseCard search") + release_cards = soup.select('[class*="ReleaseCard-style__Wrapper"]') + releases = [] logger.info(f"🔍 Found {len(release_cards)} release cards") @@ -6260,22 +6279,24 @@ def get_beatport_new_releases(): for i, card in enumerate(release_cards[:100]): # Limit to 100 for 10 slides release_data = {} - # Extract title - title_elem = card.select_one('[class*="title"], [class*="Title"], h1, h2, h3, h4, h5, h6') + # Extract title from Meta section + title_elem = card.select_one('[class*="ReleaseCard-style__Meta"] a[href*="/release/"]') + if not title_elem: + title_elem = card.select_one('[class*="title"], [class*="Title"], h3, h4, h5, h6') if title_elem: title_text = title_elem.get_text(strip=True) if title_text and len(title_text) > 2 and title_text not in ['New Releases', 'Buy', 'Play']: release_data['title'] = title_text # Extract artist - artist_elem = card.select_one('[class*="artist"], [class*="Artist"], a[href*="/artist/"]') + artist_elem = card.select_one('a[href*="/artist/"]') if artist_elem: artist_text = artist_elem.get_text(strip=True) if artist_text and len(artist_text) > 1: release_data['artist'] = artist_text # Extract label - label_elem = card.select_one('[class*="label"], [class*="Label"], a[href*="/label/"]') + label_elem = card.select_one('a[href*="/label/"]') if label_elem: label_text = label_elem.get_text(strip=True) if label_text and len(label_text) > 1: