Spotalike ํ”„๋กœ์ ํŠธ ๊ฐœ์„  - ๊ฐ€์ˆ˜๋ช… ๋งค์นญ ๋ฌธ์ œ ํ•ด๊ฒฐ


๋ฌธ์ œ ์ƒํ™ฉ

์ด์ „ ํฌ์ŠคํŒ…(์ง€๋‚œ ํฌ์ŠคํŒ…)์—์„œ Spotalike ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , FastAPI ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ๊นŒ์ง€ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์—ˆ๋‹ค.

  1. ๊ฐ€์ˆ˜๋ช… ๋ฏธ์‚ฌ์šฉ

    • ๊ฒ€์ƒ‰์— ์ž…๋ ฅํ•œ ๊ฐ€์ˆ˜๋ช…์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„ ํŠน์ • ๊ฐ€์ˆ˜์˜ ๋…ธ๋ž˜๋ฅผ ์„ ํƒํ•˜์ง€ ๋ชปํ•จ.
  2. ์ฒซ ๋ฒˆ์งธ ๊ณก ์ž๋™ ์„ ํƒ

    • ๋™์ผํ•œ ๊ณก๋ช…์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋“œ๋กญ๋‹ค์šด์—์„œ ๊ฐ€์žฅ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋ž˜๋ฅผ ์ž๋™์œผ๋กœ ์„ ํƒํ•จ.

์˜ˆ๋ฅผ ๋“ค์–ด, โ€œChristopherโ€์˜ When I Get Old๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค ํ–ˆ์œผ๋‚˜, ๋“œ๋กญ๋‹ค์šด์—์„œ ์ž๋™์œผ๋กœ โ€œDescendentsโ€์˜ ๋™์ผ ๊ณก๋ช…์ด ์„ ํƒ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.


ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

1. ๊ฒ€์ƒ‰ ๋ฌธ์ž์—ด ์กฐํ•ฉ ๋ฐฉ์‹

์ฒ˜์Œ์—๋Š” ๊ณก๋ช…๊ณผ ๊ฐ€์ˆ˜๋ช…์„ ์กฐํ•ฉํ•œ ๋ฌธ์ž์—ด๋กœ ๊ฒ€์ƒ‰ํ•˜๋ ค ํ–ˆ์œผ๋‚˜, Spotalike์˜ ๊ฒ€์ƒ‰์ฐฝ์€ ์ฒ˜์Œ๋ถ€ํ„ฐ โ€œWhen I Get Old by Christopherโ€ ํ˜•์‹์œผ๋กœ ์ž…๋ ฅํ•˜๋ฉด ๊ด€๋ จ ๋…ธ๋ž˜๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์•˜๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๋ฐฉ๋ฒ•์€ ์ ํ•ฉํ•˜์ง€ ์•Š์•˜๋‹ค.


2. URL ์ง์ ‘ ์ƒ์„ฑ ๋ฐฉ์‹

๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€ URL์„ ๋ถ„์„ํ•ด๋ณด๋‹ˆ ๊ณ ์œ  ๋ฒˆํ˜ธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์—ˆ๋‹ค.

  • ๊ธฐ๋ณธ URL: https://spotalike.com/en
  • ๊ฒ€์ƒ‰ ํ›„ URL: https://spotalike.com/en/songs-similar-to/christopher-when-i-get-old/3952490

์ด ๊ณ ์œ  ๋ฒˆํ˜ธ๋ฅผ ์˜ˆ์ธกํ•˜๊ฑฐ๋‚˜ ์ฐธ์กฐํ•  ๋ฐฉ๋ฒ•์ด ์—†์–ด ์ด ์ ‘๊ทผ ๋ฐฉ์‹๋„ ํฌ๊ธฐํ–ˆ๋‹ค.


3. ๋“œ๋กญ๋‹ค์šด์—์„œ ๊ฐ€์ˆ˜๋ช… ๋งค์นญ

๋งˆ์ง€๋ง‰์œผ๋กœ, ๋“œ๋กญ๋‹ค์šด์—์„œ ํŠน์ • ๊ณก๋ช…๊ณผ ๊ฐ€์ˆ˜๋ช…์„ ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์„ ์ฐพ์•„ ์„ ํƒํ•˜๋Š” ๋ฐฉ์‹์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

  • ๋“œ๋กญ๋‹ค์šด ๋‚ด ๊ณก๋ช…๊ณผ ๊ฐ€์ˆ˜๋ช… ์š”์†Œ๋ฅผ ๊ฐ๊ฐ ์ถ”์ถœ.
  • ์ž…๋ ฅ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์„ ์ฐพ์•„ ํด๋ฆญ.
  • ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์—†์œผ๋ฉด ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ.

๊ฐœ์„ ๋œ ์ฝ”๋“œ

์ฃผ์š” ์ˆ˜์ • ๋‚ด์šฉ

  1. ๋“œ๋กญ๋‹ค์šด ๋‚ด ๊ณก๋ช…๊ณผ ๊ฐ€์ˆ˜๋ช…์„ ๊ฐ๊ฐ ์ถ”์ถœ.
  2. ์ž…๋ ฅ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์„ ํด๋ฆญํ•˜๋„๋ก ๊ฐœ์„ .
  3. ์ผ์น˜ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์—†์„ ๊ฒฝ์šฐ ๋นˆ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜.
def get_spotalike_tracks(song_title: str, artist_name: str):
    """
    Fetch tracks similar to the given song and artist.
 
    Args:
        song_title (str): The title of the song.
        artist_name (str): The artist of the song.
 
    Returns:
        list: A list of tracks with title, artist, and duration.
    """
    options = Options()
    options.add_argument("--disable-gpu")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    # options.add_argument("--headless")  # Uncomment for performance in production
 
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
    tracks = []
 
    try:
        logging.info("Opening Spotalike...")
        driver.get(BASE_URL)
        wait = WebDriverWait(driver, 10)
 
        # Enter the song title and artist name into the search field
        logging.info(f"Searching for: {song_title} by {artist_name}")
        search_field = wait.until(EC.presence_of_element_located((By.ID, "search")))
        query = f"{song_title}"
        search_field.send_keys(query)
 
        # Wait for the dropdown to appear and validate
        dropdown_locator = (By.CSS_SELECTOR, "ul[class^='Dropdown_dropdown']")
        wait.until(EC.visibility_of_element_located(dropdown_locator))
 
        # Find all dropdown items
        dropdown_items = driver.find_elements(By.CSS_SELECTOR, "ul[class^='Dropdown_dropdown'] li button")
        match_found = False
        for item in dropdown_items:
            # Extract song title and artist from the dropdown
            song_title_elements = item.find_elements(By.CSS_SELECTOR, "span.Dropdown_highlighted__81RfT")
            extracted_song_title = "".join([elem.text for elem in song_title_elements]).strip()
 
            artist_elements = item.find_elements(By.CSS_SELECTOR, "span.Dropdown_artist__DoTwq")
            extracted_artist = "".join([elem.text for elem in artist_elements]).strip()
            # Check for an exact match
            if extracted_artist.lower() == artist_name.lower() and extracted_song_title.lower() == song_title.lower():
                logging.info(f"Found match: {extracted_song_title} by {extracted_artist}")
                item.click()  # Click the matched item
                match_found = True
                break
 
        if not match_found:
            logging.warning("No exact match found in the dropdown.")
            return []
 
        # Wait for the table of recommendations to load
        table_body = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "tbody")))
        tracks = extract_tracks(table_body)
 
    except Exception as e:
        logging.error(f"An error occurred: {e}", exc_info=True)
    finally:
        logging.info("Closing the driver.")
        driver.quit()
 
    return tracks

๊ฒฐ๊ณผ

์ด์ œ ํŠน์ • ๊ณก๋ช…๊ณผ ๊ฐ€์ˆ˜๋ช…์„ ์ •ํ™•ํžˆ ์ž…๋ ฅํ•˜๋ฉด ์›ํ•˜๋Š” ํ•ญ๋ชฉ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Christopher์˜ When I Get Old๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ฉด ๋“œ๋กญ๋‹ค์šด์—์„œ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋Š” ๊ณก๋ช…์„ ์ฐพ์•„ ํด๋ฆญํ•œ ํ›„ ์ถ”์ฒœ ๊ณก ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.


ํ–ฅํ›„ ๊ฐœ์„  ์‚ฌํ•ญ

  1. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐœ์„ 

    • ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ๋ฉ”์‹œ์ง€ ์ œ๊ณต.
  2. ๊ฐ€์ˆ˜๋ช…/๊ณก๋ช… ์˜คํƒ€ ์ฒ˜๋ฆฌ

    • ๊ฐ€์ˆ˜๋ช…์ด๋‚˜ ๊ณก๋ช…์— ์˜คํƒ€๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์œ ์‚ฌ ํ•ญ๋ชฉ ์ถ”์ฒœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€.