Spotalike๋ฅผ ํ์ฉํ ์์ ํ๋ ์ด๋ฆฌ์คํธ ํ๋ก์ ํธ
๊ฐ์
๋๋ ํ์์ YouTube๋ก ์์ ์ ์ฆ๊ฒจ ๋ฃ๋๋ค. ํ ๊ณก์ ๊ฝํ๋ฉด ๋ฐ๋ณต ์ฌ์์ ํ๋ ํธ์ธ๋ฐ, ์ด๋ฒ์๋ Christopher, CHUNG HA - When I Get Old์ ๋น ์ก๋ค. ์ด ๋ ธ๋๋ฅผ ๋ฃ๋ค ๋ณด๋, ๋น์ทํ ๋ถ์๊ธฐ์ ๊ณก๋ค์ ๋ชจ์ ์๋์ผ๋ก ์ถ์ฒ๋ฐ๊ณ ํ๋ ์ด๋ฆฌ์คํธ๋ฅผ ๋ง๋ค๊ณ ์ถ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
๊ทธ๋ ๊ฒ ๋ฐ๊ฒฌํ ๊ฒ์ด Spotalike๋ผ๋ ์๋น์ค๋ค. ์ฒ์์๋ ์ด ํ๋ซํผ์ ํ์ฉํด ์ถ์ฒ๋ฐ์ ๊ณก๋ค์ YouTube ์ฌ์๋ชฉ๋ก์ผ๋ก ์๋ ์์ฑํ๋ ํ๋ก์ ํธ๋ฅผ ๊ธฐํํ๋ค.
ํ๋ก์ ํธ ์์ด๋์ด์ ํ์ฅ
์ฒ์์๋ ๊ฐ๋จํ ๋ฐฉ์์ผ๋ก ์์ํ๋ ค๊ณ ํ๋ค. Spotalike์์ ์ถ์ฒ๋ฐ์ ๊ณก๋ค์ YouTube ์ฌ์๋ชฉ๋ก์ผ๋ก ๋ฐ๋ก ์์ฑํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ค๊ณ ํ์ง๋ง, ์ด๋ YouTube Music์์ ์ด๋ฏธ ์ ๊ณตํ๋ ๊ธฐ๋ฅ๊ณผ ํฌ๊ฒ ๋ค๋ฅผ ๋ฐ ์์๋ค. ๊ทธ๋์ ์ฐจ๋ณํ๋ฅผ ์ํด Obsidian์ Graph View์ฒ๋ผ ๋น์ทํ ๊ณก๋ค์ ์๊ฐ์ ์ผ๋ก ์ฐ๊ฒฐํด ๋ณด์ฌ์ฃผ๋ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์ด ๋ณด๊ธฐ๋ก ํ๋ค. ์ฌ์ฉ์๋ ๋๋๊ทธ ์ค ๋๋กญ ๋ฐฉ์์ผ๋ก ์ฌ์๋ชฉ๋ก์ ๊ตฌ์ฑํ๊ณ ์์ ํ ์ ์๋ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์๋ค.
๊ฐ๋ฐ ์ญ๋์ด ๋ถ์กฑํ ์ํ์ง๋ง, ์ฒ์ฒํ ์๋ํ๋ฉฐ ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋ค์ด๋ณด๊ธฐ๋ก ํ๋ค.
๊ฐ๋ฐ ๊ณผ์
1. Spotalike ํฌ๋กค๋ง ๊ธฐ๋ฅ ๊ตฌํ
Spotalike์์ ํน์ ๋ ธ๋๋ฅผ ๊ฒ์ํ๋ฉด ์ถ์ฒ ๊ณก ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ ๋จผ์ ๊ตฌํํ๋ค. ์ด๋ฅผ ์ํด Selenium์ ์ฌ์ฉํด ๋ค์ ์์ ์ ์ํํ๋ค.
- ๊ฒ์์ฐฝ์ ๋ ธ๋ ์ ๋ชฉ ์ ๋ ฅ
- ๋๋กญ๋ค์ด ๋ฆฌ์คํธ์์ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ ํด๋ฆญ
- ์ถ์ฒ ๊ณก ๋ฆฌ์คํธ๋ฅผ ์ถ์ถ
์ด ๊ณผ์ ์์ ์ด๋ ค์๋ ์์๋ค. ๋๋กญ๋ค์ด ๋ฉ๋ด๊ฐ blur ์ฒ๋ฆฌ๋์ด ์์๋ฅผ ํ์ธํ๊ธฐ ์ด๋ ค์ ๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด focusout ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ญ์ ํ์ฌ ๋๋กญ๋ค์ด ๋ฆฌ์คํธ์ ์ ๊ทผํ ์ ์์๋ค.
2. FastAPI๋ก API ๊ฐ๋ฐ
๋ค์์ผ๋ก, ํฌ๋กค๋ง ๋ฐ์ดํฐ๋ฅผ FastAPI ๊ธฐ๋ฐ API๋ก ์ ๊ณตํ๋๋ก ๊ตฌํํ๋ค. ์๋๋ API์ ์ฃผ์ ๊ธฐ๋ฅ์ด๋ค.
- ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ ธ๋ ์ ๋ชฉ๊ณผ ๊ฐ์ ์ด๋ฆ์ ์ ๋ ฅ๋ฐ๋๋ค.
- Spotalike์์ ์ถ์ฒ ๊ณก ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์จ๋ค.
- ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ JSON ํ์์ผ๋ก ๋ฐํํ๋ค.
project/
โโโ main.py # FastAPI ์๋ฒ์ ์ง์
์
โโโ spotalike/ # Spotalike ๊ด๋ จ ๋ก์ง
โ โโโ scraper.py # Selenium ์คํฌ๋ํผ ๊ตฌํ
โโโ utils/ # ์ ํธ๋ฆฌํฐ ๋ชจ๋
โ โโโ logger.py # ๋ก๊น
๊ธฐ๋ฅ ๊ตฌํ
โโโ requirements.txt # Python ํจํค์ง ์์กด์ฑ ๋ชฉ๋ก
โโโ .env # ํ๊ฒฝ ๋ณ์
โโโ README.md # ํ๋ก์ ํธ ์ค๋ช
from fastapi import FastAPI, HTTPException
from spotalike.scraper import get_spotalike_tracks
app = FastAPI()
@app.get("/api/spotalike")
def fetch_spotalike_tracks(song_title: str, artist_name: str):
"""
Spotalike์์ ์ถ์ฒ ๊ณก์ ๊ฐ์ ธ์ค๋ API.
"""
try:
tracks = get_spotalike_tracks(song_title, artist_name)
if not tracks:
raise HTTPException(status_code=404, detail="No tracks found")
return {"tracks": tracks}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))3. Selenium์ ํ์ฉํ ํฌ๋กค๋ง ์ฝ๋
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager # type: ignore
import logging
import json
from dotenv import load_dotenv
import os
load_dotenv()
BASE_URL = os.getenv("SPOTALIKE_BASE_URL", "https://spotalike.com/en")
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def get_spotalike_tracks(song_title: str, artist_name: str):
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
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
tracks = []
try:
logging.info("Opening Spotalike...")
driver.get(BASE_URL)
wait = WebDriverWait(driver, 10)
logging.info(f"Searching for: {song_title}")
search_field = wait.until(EC.presence_of_element_located((By.ID, "search")))
search_field.send_keys(song_title)
dropdown_locator = (By.CSS_SELECTOR, "ul[class^='Dropdown_dropdown']")
wait.until(EC.visibility_of_element_located(dropdown_locator))
first_item = driver.find_element(By.CSS_SELECTOR, "ul[class^='Dropdown_dropdown'] li button")
first_item.click()
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
def extract_tracks(table_body):
tracks = []
track_rows = table_body.find_elements(By.TAG_NAME, "tr")
for row in track_rows:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) >= 3:
title_cell, artist_cell, duration_cell = cells[:3]
tracks.append({
"title": title_cell.text.strip(),
"artist": artist_cell.text.strip(),
"duration": duration_cell.text.strip()
})
return tracks๊ฒฐ๋ก ๋ฐ ๋ค์ ๋จ๊ณ
ํ์ฌ Spotalike ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ๊ณผ FastAPI ์๋ฒ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ๊ธฐ๋ฅ๊น์ง ๊ตฌํํ๋ค. ์ดํ ๋ชฉํ๋ ๋ค์๊ณผ ๊ฐ๋ค.
-
Graph View ๊ตฌํ
- ๊ณก๋ค ๊ฐ์ ๊ด๊ณ๋ฅผ ์๊ฐ์ ์ผ๋ก ํํ.
- ์ฌ์ฉ์๊ฐ ๋๋๊ทธ ์ค ๋๋กญ์ผ๋ก ์ฌ์๋ชฉ๋ก์ ์กฐ์ ํ ์ ์๋๋ก UI ๊ฐ๋ฐ.
-
ํ๋ก ํธ์๋ ๊ฐ๋ฐ
- Graph View์ API๋ฅผ ํตํฉํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ.
- React.js ๋๋ Vue.js๋ฅผ ํ์ฉํ ์ธํฐํ์ด์ค ๊ตฌ์ฑ.
-
์ ์ ์นํ์ ์ธ ์ฌ์๋ชฉ๋ก ๊ด๋ฆฌ ๊ธฐ๋ฅ ์ถ๊ฐ
- ์ ์ฅ๋ ์ฌ์๋ชฉ๋ก์ ํ์ผ๋ก ๋ด๋ณด๋ด๊ธฐ/๋ถ๋ฌ์ค๊ธฐ.
- YouTube์ ์ฐ๋ํ์ฌ ์ฌ์๋ชฉ๋ก ์๋ ์ ๋ก๋ ๊ธฐ๋ฅ ์ ๊ณต.
์ถ๊ฐ์ ์ผ๋ก ๊ณ ๋ฏผํด๋ณด๋ฉด ์ข์ ์ง๋ฌธ๋ค
Q1: Selenium์ ์ฌ์ฉํ์ง ์๊ณ Spotalike ๋ฐ์ดํฐ๋ฅผ ๋ ํจ์จ์ ์ผ๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ์์๊น?
Q2: Graph View๋ฅผ ๊ตฌํํ๊ธฐ ์ํ ์ ํฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ ์์ํฌ์๋ ์ด๋ค ๊ฒ๋ค์ด ์์๊น?
Q3: ์ถ์ฒ๋ฐ์ ๊ณก๋ค ๊ฐ์ ์ฐ๊ด์ฑ์ AI ๋ชจ๋ธ๋ก ๋ถ์ํ์ฌ ์๊ฐํํ๋ค๋ฉด ์ด๋ค ๋ฐฉ์์ด ํจ๊ณผ์ ์ผ๊น?