ํ์ด์ฌ ์น ์คํฌ๋ํ - ๋ฉ๋ก ์์ ์ถ์ถ
์์ํ๊ธฐ์ ์์, ํ์ด์ฌ์ ์ต๊ทผ์ ๊ณต๋ถํ๋ฉด์ ์คํฌ๋ํ์ด๋ผ๋ ๋จ์ด๋ฅผ ์ฒ์ ๋ค์๋ค.
ํฌ๋กค๋ง์ ๋ค์ด๋ดค๋๋ฐ.. ์คํฌ๋ํ์ ๋ฌด์์ธ๊ฐ..?
ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์, ํฌ๋กค๋ง ์
๋ฌด๋ฅผ ๋งก์ ์ ์ด ๊ฝค ์๋๋ฐ ๋ด๊ฐ ํ๋ ๊ฑด ๋๋ถ๋ถ ์คํฌ๋ํ์ด์๋ ๊ฒ ๊ฐ๋ค..
๊ทธ๋ฆฌ๊ณ , ์คํฌ๋ํ์ ์ ๋ฆฌํ ์ธ์ด๊ฐ ํ์ด์ฌ์ด๋ผ๊ณ ํด์ ์ด๋ ๊ฒ ๊ธ์ ์์ฑํ๊ฒ(?) ๋์๋ค.
ํ๋ก์ ํธ ์ ์ ๊ณผ์
์ ํ๋ธ ํ๋ฆฌ๋ฏธ์์ ์ ๋ฃ ๊ตฌ๋
์ ํ๊ฒ๋๋ฉด ์ ํ๋ธ ๋ฎค์ง์ ๋ฌด๋ฃ๋ก ์ด์ฉํ ์ ์๋ค. ์ด๋ฏธ ๋ฉ๋ก ์คํธ๋ฆฌ๋ฐ ์๋น์ค๋ฅผ ์ ๋ฃ๋ก ์ฌ์ฉํ๊ณ ์๋ ๋์๊ฒ ๊ณ ๋ฏผ์ด ์ฐพ์์๋คโฆ
์ฝ 2016๋
๋ถํฐ 7~8๋
๊ฐ 3,000๊ณก์ ํ๋ ์ด๋ฆฌ์คํธ๊ฐ ๋ฉ๋ก ์ ์ ์ฅ๋์ด์๋ค.
์์ ํ๋ ์ด๋ฆฌ์คํธ๋ฅผ ์ค์ฐจ ์์ด ์ ํ๋ธ ๋ฎค์ง์ผ๋ก ์ฎ๊ธฐ๊ฒ ๋๋ค๋ฉดโฆ
๋ง์ ์ด์ (์ ์ฝ? ๋๊ธฐํ? ๋ฑ)์ด ์์ ๊ฒ ๊ฐ์ ๋ฉ๋ก ์ ์์๋ค์ ์ ํ๋ธ ๋ฎค์ง์ผ๋ก ์ด๊ดํ๋ ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ธฐ๋ก ๊ฒฐ์ฌํ์๋ค.
ํ๋ก์ ํธ ์๊ตฌ ์ฌํญ
- ๋์ ๋ฉ๋ก ๊ณ์ ์ ํฌํจ๋์ด์๋ ํ๋ ์ด๋ฆฌ์คํธ ์ถ์ถ ๊ธฐ๋ฅ
- ์ถ์ถํ ํ๋ ์ด๋ฆฌ์คํธ์ ์์ ์ถ์ถ ๊ธฐ๋ฅ
- ์ถ์ถํ ํ๋ ์ด๋ฆฌ์คํธ์ ์์์ ๋ํ ์ ๋ณด๋ฅผ ์์ ํ์ผ ์์ฑ ๋ฐ ์ ์ฅ ๊ธฐ๋ฅ
- ์์ฑํ ์์ ์ ์์์ ์ ํ๋ธ ์ฌ์๋ชฉ๋ก์ ์ ์ฅ
์ ํ๋ธ์ ๊ฒฝ์ฐ, ํ๊ณ๊ฐ ์์ด ๊ตฌํํ์ง ๋ชปํ์๋คโฆ ์์ธํ ๋ด์ฉ์ ๋ฐ์์ ์ค๋ช ํ๊ฒ ๋ค.. ๋ค์ ํธ์ ๊ณ์(!?)
๋ฉ๋ก ์ ์น ๊ตฌ์กฐ ํ์
ํ๋ ์ด๋ฆฌ์คํธ ์กฐํ ํ์ด์ง URL : https://www.melon.com/mymusic/playlist/mymusicplaylist_list.htm?memberKey=56389814
- https://www.melon.com/mymusic/playlist/mymusicplaylist_list.htm ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก
memberKey
๋ฅผ ๋ฐ๋๋ค. - ๋ฉ๋ก ์ ์ ์ ํ๋กํ์ ๊ณต์ ๋ฐ์
memberKey
์ ๊ฐ์ ์ป๋๋ค.
- https://www.melon.com/mymusic/playlist/mymusicplaylist_list.htm ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก
ํ๋ ์ด๋ฆฌ์คํธ์ ํฌํจ๋ ์์ ์กฐํ ํ์ด์ง URL : https://www.melon.com/mymusic/playlist/mymusicplaylistview_inform.htm?plylstSeq=533264243
- https://www.melon.com/mymusic/playlist/mymusicplaylistview_inform.htm ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก
plystSeq
๋ฅผ ๋ฐ๋๋ค. - ์์ ํ์ด์ง์์ ๊ฐ ํ๋ ์ด๋ฆฌ์คํธ๋ฅผ ํด๋ฆญํ๋ฉด
plystSeq
์ ๊ฐ์ ์ป์ ์ ์๋ค.
- https://www.melon.com/mymusic/playlist/mymusicplaylistview_inform.htm ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก
ํ๋ ์ด๋ฆฌ์คํธ ์กฐํ / ํ๋ ์ด๋ฆฌ์คํธ์ ํฌํจ๋ ์์ ์กฐํ ๊ฐ๊ฐ์ ํ ํ์ด์ง๋น ๋ ธ์ถ ๋๋ ์์ดํ ๊ฐ์
- ํ๋ ์ด๋ฆฌ์คํธ ์กฐํ : ํ ํ์ด์ง ๋น ์ด 20๊ฐ
- ํ๋ ์ด๋ฆฌ์คํธ์ ํฌํจ๋ ์์ ์กฐํ : ํ ํ์ด์ง ๋น ์ด 50๊ฐ
ํ๋ ์ด๋ฆฌ์คํธ ์กฐํ / ํ๋ ์ด๋ฆฌ์คํธ์ ํฌํจ๋ ์์ ์กฐํ ํ์ด์ง ์ด๋
- ํ์ด์ง ๋ฒํผ์ ๋ถ์ด์๋ ์คํ ํจ์ :
javascript:pageObj.sendPage(offset)
์ผ๋ก ํ์ด์ง ์ด๋์ ํ๋ค.
- ํ์ด์ง ๋ฒํผ์ ๋ถ์ด์๋ ์คํ ํจ์ :
์ผ๋ฐ์ ์ธ ํ์ด์ง์ ๊ฒฝ์ฐ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก
page
์offset
์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ๊ตฌ์ฑ๋์ด ์์ด ํจํด ์์ธก์ด ์ฌ์ฐ๋, ๋ฉ๋ก ์ ๊ฒฝ์ฐ ํจํด์ ์ ์ถ ํ ์ ์์ด ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
ํ๋ก๊ทธ๋จ ๊ตฌํ - melon.py
[์ฃผ์ ํจํค์ง]
webdriver
: ์น ๋ธ๋ผ์ฐ์ ์ ์ด๋ / ์คํฌ๋ฆฝํธ ์คํ / ์์ ์ฐพ๋ ๊ธฐ๋ฅBeautifulSoup
: HTML ์ ๋ณด ์์งpandas
: ์์งํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐ ์กฐ์
[์์]
# ์์ ์ ์
WAIT_TIME = 1 # ์น ๋๋ผ์ด๋ฒ ๋๊ธฐ ์๊ฐ
TODAY_DATE = datetime.today().strftime('%Y%m%d') # ์์
ํ์ผ ์ด๋ฆ์ ๋ค์ด๊ฐ๋ ์ค๋ ๋ ์ง
MUSIC_COLUMNS = ['์ ๋ชฉ', '์ํฐ์คํธ', '์จ๋ฒ'] # ์์
ํค๋ ์ปฌ๋ผ
PLAYLIST_URL = 'https://www.melon.com/mymusic/playlist/mymusicplaylist_list.htm' # ํ๋ ์ด๋ฆฌ์คํธ URL
MUSIC_URL = 'https://www.melon.com/mymusic/playlist/mymusicplaylistview_inform.htm' # ์์ URL
EXCEL_PATH = "../excel" # ์์
ํ์ผ ๊ฒฝ๋ก
[ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ๊ตฌํ]
๋ฉ์ธ
ํ์ ์ ์ฒด ํ๋ ์ด๋ฆฌ์คํธ ์คํฌ๋ํ
# ํ์ ํค๋ก ์ ์ฒด ํ๋ ์ด๋ฆฌ์คํธ ์์ ๊ฐ์ ธ์ค๊ธฐ def member(member_key): driver = init() # ์น ๋๋ผ์ด๋ฒ ์ธํ driver.get(PLAYLIST_URL + '?memberKey=' + member_key) # ํ๋ ์ด๋ฆฌ์คํธ ์กฐํ ํ์ด์ง ์ด๋ playlist_total_count = int(driver.find_element(By.CSS_SELECTOR, '.no').text) # ํ์ด์ง ์ด๋์ ํ์ํ "์ด35๊ฐ"์ ์์์ "35" ์ฆ ์ซ์ ๊ฐ์ ๊ฐ์ ธ์จ๋ค. playlist_seqs = get_playlist_seqs(driver, playlist_total_count) # ์ ์ฒด ํ๋ ์ด๋ฆฌ์คํธ plystSeq ๊ฐ์ ๊ฐ์ ธ์จ๋ค. data_frame_list = scrape_music_data(driver, playlist_seqs) # ์์ ์ ๋ณด ์ถ์ถ write_excel(data_frame_list, 'member_' + member_key + '_' + TODAY_DATE + '.xlsx') # ์์ ์์ฑ driver.quit()
๋จ์ผ ํ๋ ์ด๋ฆฌ์คํธ ์คํฌ๋ํ
# ํ๋ ์ด๋ฆฌ์คํธ ํค๋ก ์์ ๊ฐ์ ธ์ค๊ธฐ def playlist(playlist_key): driver = init() playlist_seqs = [playlist_key] # ๋จ์ผ ํ๋ ์ด๋ฆฌ์คํธ data_frame_list = scrape_music_data(driver, playlist_seqs) # ์์ ์ ๋ณด ์ถ์ถ write_excel(data_frame_list, 'playlist_' + playlist_key + '_' + TODAY_DATE + '.xlsx') # ์์ ์์ฑ driver.quit()
์น ๋๋ผ์ด๋ฒ ์ธํ
# Selenium ๋๋ผ์ด๋ฒ ์ธํ def init(): chrome_options = Options() chrome_options.add_argument("--headless") # Chrome ๋ธ๋ผ์ฐ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์คํ ์ต์ : ์๋ ๊ฐ์ driver = webdriver.Chrome(options=chrome_options) return driver
ํ๋ ์ด๋ฆฌ์คํธ ํค ๊ฐ์ ธ์ค๊ธฐ
# ํ๋ ์ด๋ฆฌ์คํธ ํค ๊ฐ์ ธ์ค๊ธฐ def get_playlist_seqs(driver, playlist_total_count): playlist_seqs = [] for offset in range(1, playlist_total_count + 1, 20): # 20์ฉ ํ์ด์ง ์ ๋งํผ ๋ฐ๋ณต driver.execute_script("javascript:pageObj.sendPage('" + str(offset) + "')") # ํ์ด์ง ์ด๋ ์คํฌ๋ฆฝํธ ํธ์ถ time.sleep(WAIT_TIME) playlist_links = driver.find_elements(By.CSS_SELECTOR, 'dt a') # ํ๋ ์ด๋ฆฌ์คํธ ์์ธ ํ์ด์ง ์ด๋ url ๊ฐ์ ธ์ค๊ธฐ for link in playlist_links: playlist_seq = re.findall(r'\d+', link.get_attribute('href'))[1] # URL์์ plystSeq ๊ฐ๋ง ์ถ์ถ playlist_seqs.append(playlist_seq) # ๋ฐฐ์ด์ ์ ์ฅ return playlist_seqs
์์ ๋ฐ์ดํฐ ์ ์ถ์ถ
# ์์ ์คํฌ๋ํผ def scrape_music_data(driver, playlist_seqs): data_frame_list = [] for playlist_seq in playlist_seqs: driver.get(MUSIC_URL + '?plylstSeq=' + playlist_seq) # plystSeq๋ก ์์ ์กฐํ ํ์ด์ง ์ด๋ time.sleep(WAIT_TIME) playlist_title = driver.find_element(By.CSS_SELECTOR, '.more_txt_title').text # ํ๋ ์ด๋ฆฌ์คํธ ํ์ดํ ์์ ๊ฐ์ ธ์ค๊ธฐ (์์ ์ํธ ๋ช ) music_total = int(re.search(r'\d+', driver.find_element(By.CSS_SELECTOR, '.title .cnt').text).group()) # ํ์ด์ง ์ด๋ ์ํ "์๋ก๊ณก(12)"์ "12"๋ฅผ ์ถ์ถ music_data = [] for offset in range(1, music_total, 50): # 50์ฉ ํ์ด์ง ์ ๋งํผ ๋ฐ๋ณต driver.execute_script("javascript:pageObj.sendPage('" + str(offset) + "')") # ํ์ด์ง ์ด๋ ์คํฌ๋ฆฝํธ ํธ์ถ time.sleep(WAIT_TIME) soup = BeautifulSoup(driver.page_source, 'lxml') # HTML ํ์ฑ ์ํ BeautifulSoup ์ ํ tr_tags = soup.find_all('tr') for tr in tr_tags: td_tags = tr.find_all('td', class_='t_left') if td_tags and td_tags[0].find(class_='fc_gray'): title = td_tags[0].find(class_='fc_gray').text.strip() # ์์์ ์ ๋ชฉ ์ถ์ถ artist = td_tags[1].find(id='artistName').text.strip() # ์์์ ์ํฐ์คํธ๋ช ์ถ์ถ album = td_tags[2].find(class_='fc_mgray').text.strip() # ์์์ ์จ๋ฒ๋ช ์ถ์ถ print("Title : ", title, " / ", "Artist : ", artist, " / ", "Album : ", album) # ์ถ์ถํ ๋ฐ์ดํฐ ์ถ๋ ฅ music_data.append([title, artist, album]) df = pd.DataFrame(music_data, columns=MUSIC_COLUMNS) # pandas ๋ฐ์ดํฐ ์ฒ๋ฆฌ data_frame_list.append({'sheet': playlist_title, 'data': df}) return data_frame_list
์์ ์์ฑ
# ์์ ์ฐ๊ธฐ def write_excel(data_frame_list, filename): if not os.path.exists(EXCEL_PATH): os.makedirs(EXCEL_PATH) # excel ๋๋ ํ ๋ฆฌ ์์ฑ with pd.ExcelWriter("excel/" + filename) as writer: for data_frame in data_frame_list: sheet_name = data_frame.get('sheet') # ์ํธ ๋ช ๊ฐ์ ธ์ค๊ธฐ m = re.compile(r'[\\*?:/\[\]]').search(sheet_name) # ํ์ฉ๋์ง ์๋ ์ํธ ๋ช ๊ฒ์ฌ if m: sheet_name = '์ ์ ์์' data_frame.get('data').to_excel(excel_writer=writer, sheet_name=sheet_name, index=False) # ์์ ํ์ผ ์์ฑ
[ํ์ ์ ์ฒด ํ๋ ์ด๋ฆฌ์คํธ ์คํฌ๋ํ]
- ์คํ ํ์ผ -
member.py
from melon import member # https://www.melon.com/mymusic/playlist/mymusicplaylist_list.htm?memberKey=56389814 member('56389814')
- ์คํ ์คํฌ๋ฆฝํธ
python ./member.py
- ๊ฒฐ๊ณผ ์์
ํ์ผ
ํ๋ ์ด๋ฆฌ์คํธ ๋ณ๋ก ์ํธ๊ฐ ์์ฑ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
[๋จ์ผ ํ๋ ์ด๋ฆฌ์คํธ ์คํฌ๋ํ]
- ์คํ ํ์ผ -
playlist.py
from melon import playlist
# https://www.melon.com/mymusic/playlist/mymusicplaylistview_inform.htm?plylstSeq=503394650
playlist('503394650')
- ์คํ ์คํฌ๋ฆฝํธ
python ./playlist.py
- ๊ฒฐ๊ณผ ์์
ํ์ผ
์ด๋ ๊ฒ ๊ฐ๋จํ๊ฒ ์น ์คํฌ๋ํ์ ํ ์ ์๋ ์ฝ๋๋ฅผ ์์ฑํด๋ดค๋ค.
๋ง์ฝJava
๋ก ์์ฑ ํ์ผ๋ฉด ๋ ๋ณต์กํ ์ฝ๋์ด์ง ์์์๊น...?๋ผ๋ ์๊ฐ์ ํด๋ณธ๋ค.
์ ํ๋ธ ํ๊ณ
์๋ ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์๋ ์ ํ๋ธ ๋ฎค์ง์ผ๋ก ์คํฌ๋ํ์ผ๋ก ์ถ์ถํ ์์์ ์ด๊ด์ ํด์ผ๋๋ ์์ ์ด ์์ง๋ง ์๋์ ์ฌ๋ฌ๊ฐ์ง ์ด์ ๋ก ์ ๊น ๋ณด๋ฅ(?) ํ๊ฒ ๋ค.
- ์ ํ๋ธ ํ์ด์ง์ ์คํฌ๋ํ์ ํ๊ณ : HTML ์๋ช ์ฃผ๊ธฐ๊ฐ ๋ถํน์ ํ๋ฉฐ, ์ ํํ ์์์ ๊ฒ์ํ๋ ๋ฐ์ ์์ด ์ด๋ ค์์ด ์๋ค.
์ ํ๋ธ์ ์ ์ ์์์ ์ฐพ์๋ ค๋ฉด keyword๋ฅผ ([์ ๋ชฉ] [์ํฐ์คํธ๋ช ] [์จ๋ฒ๋ช ] ****topic)์ด๋ผ๊ณ ๋ช ์ ํ๋ฉด ์ ์์์์ด ์๋จ์ ๋ ธ์ถ๋๋ค. โ ํญ์ ๊ทธ๋ ์ง๋ ์๋ค. ์์๋ ๋ณ๋ก ์๊ณ โฆ
- ์ ํ๋ธ API๋ฅผ ํตํด ๊ฒ์ ๋ฐ ์ฌ์๋ชฉ๋ก ์์ฑ / ์์ ์ถ๊ฐ ๋ฑ์ ๊ตฌํํ๋ ค๊ณ ํ ๋, ํ๋ฃจ์ ์ต๋ 200๊ณก๋ง ์ฌ์๋ชฉ๋ก์ ์ ์ฅ ํ ์ ์๋ API ์ ํ ๋๋ฌธ์ ํ๊ณ๊ฐ ์๋ค.
์ด๋ฌํ ํ๊ณ ๋๋ฌธ์ ๋ค๋ฅธ ์์ด๋์ด๋ฅผ ๊ณ ์ ์ค์ ์๋ค...
ํ๋ก์ ํธ ๋ฐฐํฌ
ํด๋น ํ๋ก์ ํธ๋ฅผ ํจํค์ง ํํ์ฌ Pypi์ ๋ฐฐํฌ ํ๋ ค๊ณ ํ์ผ๋ ์๋ ์ด๋ฏธ์ง ์ฒ๋ผ ํ์ฌ๋ ๋ถ๊ฐํ๋ค.
๋ค์์ ์์๋ณด๋๋ก ํ์โฆ.
๋ณด์
ํ์ด์ฌ ํ๋ก๊ทธ๋จ์ ์ฒ์ ์์ฑํ๋ค๋ณด๋, ์์ฃผ ์ฌ์ฉํ๋ Java
์ ๋นํด ์ฌ๋ฌ๊ฐ์ง์ ์ด๋ ค์(์๋์์ฑ? ํ์
? ๋ฌธ๋ฒ ๋ฑ)์ด ์์์ผ๋ ๋ฐ์ด๋ ์์ฐ์ฑ์ ๋๋ผ์ ๋ค.
์์ผ๋ก AI ์๋์ ๋ง๊ฒ ํ์ด์ฌ์ ๋งค๋ ฅ์ ๋ ๋๋ผ๊ธฐ ์ํ์ฌ ๊พธ์คํ ํ์ตํ ์์ ์ด๋ค.
์์ ํ๋ก์ ํธ๋ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์์ฑํ์๋ค. ํ์ด์ฌ๋ OOP
๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์์ ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋ง์ ํด ๋ณผ ์๊ฐ์ด๋ค.
๋ ํ์ฌ๋ ๋ถ๊ฐํ์ง๋ง, ํ๋ก์ ํธ ๋ฐฐํฌ๋ ํ๋ฒ ๊ฒฝํ์ ํด๋ณด๊ณ ์ถ๋ค.
์ฐจํ์ ๊ธฐํ๊ฐ ๋๋ฉด ๐คฃ, ์ ํ๋ธ ์์ ์ด๊ด๊น์งํ์ฌ ์๋ ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์ ๋ง๋ ํ๋ก๊ทธ๋จ์ ์์ฑ ํด์ผ๋์ง ์์ ๊น ์ถ๋ค..
์์ ์์ค๋ ๊นํ๋ธ์ ๋ณ๋๋ก ์ฒจ๋ถ ํฉ๋๋ค. ํ์ํ์ ๋ถ์.. ๊ฐ์ ธ๋ค ์ฐ์ ๋ ๋ฉ๋๋ค.. ๐
https://github.com/discphy/melon-scrap
๋!