본문 바로가기

프로젝트 정리

국비 데이터 분석 3차 _ 2 데이터 수집 _ part 1

일단 주제를 '틱톡에서 인기있는 곡들 중 빌보드에 올라가는 곡들과 못 올라가는 곡을 구별하자.'로 잡았다.

틱통에 인기있는 곡들이 일관성이 있는지, 비슷한 특징이 있는지는 잘 모르겠지만, 또 틱톡 인기곡 중 빌보드에 진입한 곡이 얼마나 될 지 모르겠지만, 시간관계상 시작하기로 했다.

 

데이터 수집을 파트를 나눠서 진행했다. 1. 빌보드 차트인 한 데이터 찾아오기, 2. 틱톡의 인기곡들 찾아오기, 3. 그 곡들의 정보를 담아 새로 정리하기. 이렇게 구분했다.

나는 이 중 3번을 작업했다.

팀원 한명이 1번을 다음의 형식으로 보내주었다.

더보기

{1: [{'title': 'Like Crazy',
   'artist': 'Jimin',
   'rank': '1'},
  {'title': 'Flowers',
   'artist': 'Miley Cyrus',
   'rank': '2'},
...

나는 우선 'tunbat.com'이라는 사이트에서 데이터를 수집하기로 했다.

이 사이트는 코드, bpm, popularity, 의 데이터를 가져와 아래처럼 넣어주기로 했다.

더보기

{1: [{'title': 'Like Crazy',
   'artist': 'Jimin',
   'rank': '1',
   'harmony': 'Major',
   'bpm': '120',
   'populality': '88'},
  {'title': 'Flowers',
   'artist': 'Miley Cyrus',
   'rank': '2',
   'harmony': 'Major',
   'bpm': '118',
   'populality': '100'},
... 

크롤링 툴로 selenium을 사용하기로 했다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup as bs
import requests
import time
import os
import urllib.request
import re
import pickle

검색 조건을 설정하는 함수를 만들고

def searchSong(song_name, song_artist):
    # 검색 조건
    return  song_artist + ' ' + song_name

사이트에서 정보를 가져오는 함수를 만들었다.

여기서 고민했던 것이. 검색에 아티스트 + 제목을 넣어서 나오는 결과를 가져오는 것인데, 우선 틱톡 인기곡 중 tunbat사이트에서 검색이 되지 않는 것들이 많았고, 각종 이모지들이 제목에 있는 경우가 많았다. 그래서 이런 값은 결측값으로 처리하기 위해 코드를 수정했다.

def className(song_name,song_artist):
    # 타이틀, 가수명 재정리
    try :
        artist = wd.find_elements(By.CSS_SELECTOR, '.ant-typography-single-line')
        title = wd.find_elements(By.CSS_SELECTOR, '.ant-typography-ellipsis-multiple-line')
        
        if not song_artist :
            if not song_name :
                return 'name_is_not_define', 'artist_is_not_define', 0, 0, 0
            return song_name, 'artist_is_not_define', 0, 0, 0
        elif not song_name :
            return 'name_is_not_define', song_artist, 0, 0, 0
        
        # notion[0] 메이저,마이너, [1] bpm, [2] camelot scale : 필요 없음, [3] : popularity
        notion = wd.find_elements(By.CSS_SELECTOR, '.lAjUd')

        # 음역 key 삭제
        key = notion[0].text.split(' ')[1]

        return title[0].text, artist[0].text, key, notion[1].text, notion[3].text
    
    except Exception as e:
        print(f'{e} : // {song_artist} 의 {song_name} 는 없거나, 자료 없음')
        return song_name, song_artist, 0, 0, 0
        

    # 전처리해서 불러오지 말고 불러온 후에 전처리 시키는게 오류 안 집힐 듯,
    
    # i = 0
    # while i <= 10:
    #     re_artist = artist[i].text.lower().replace(' ','')
    #     re_title = title[i].text.lower().replace(' ','')
    #     re_song_artist = song_artist.lower().replace(' ','')
    #     re_song_name = song_name.lower().replace(' ','')
    #     # print(re_artist,re_title,re_song_artist,re_song_name)
    #     if (re_artist == re_song_artist) and (re_title == re_song_name):
    #         key = notion[4*i].text.split(' ')[1]
    #         # print(title[i].text, artist[i].text, key, notion[4*i+1].text, notion[4*i+3].text)
    #         return title[i].text, artist[i].text, key, notion[4*i+1].text, notion[4*i+3].text
    #     i += 1

위에서 받은 데이터를 딕셔너리에 넣기 위해 함수를 만들었다.

def newDict(classname, rank):
    # 사전 정의
    new_dict = {}
    if classname :
        new_dict = {
            'title' : classname[0], 
            'artist' : classname[1],
            'rank' : rank,
            'harmony' : classname[2],
            'bpm' : classname[3],
            'populality' : classname[4]
        }
        return new_dict

검색을 실행하는 함수를 만들었다.

이모지는 검색이 안되기에 우선 이모지를 제거하는 코드를 짜고(인터넷에서 가져왔다.)

예외처리를 해주었다.

def searchBar(song_name,song_artist,rank):
    global wd
    try:
        # 크롤링 선택자
        el = wd.find_element(By.CSS_SELECTOR, '.J4uKH .ant-input-lg.ant-input')
        search_song = searchSong(song_name,song_artist)

        song_artist = strip_emoji(song_artist)
        song_name = strip_emoji(song_name)

        el.send_keys(search_song)
        btn = wd.find_element(By.CSS_SELECTOR, '.ant-btn.ant-btn-submit.mWyPe')
        btn.click()

        time.sleep(3)
        print(song_name,song_artist)
        # el.clear()
        el.send_keys(Keys.CONTROL + 'a')
        el.send_keys(Keys.BACKSPACE)
        classname = className(song_name,song_artist)
        new_dict = newDict(classname, rank)
        # print(new_dict)
        return new_dict
    except Exception as e:
        print(f'{e} : // {song_artist} 와 {song_name}에 이모지가 들어가 있음')
        new_dict = {
            'title' : 'name_is_not_define', 
            'artist' : 'artist_is_not_define' ,
            'rank' : rank,
            'harmony' : 0,
            'bpm' : 0,
            'populality' : 0
        }
        return new_dict

이모지 제거하는 코드이다. 인터넷에서 찾아서 가져왔고, 아래 몇개는 직접 추가했다.

# 정규식으로 이모지 제거
import emoji

def strip_emoji(text):
    emoji_pattern = re.compile("["
                                u"\U0001F600-\U0001F64F"  # emoticons
                                u"\U0001F300-\U0001F5FF"  # symbols & pictographs
                                u"\U0001F680-\U0001F6FF"  # transport & map symbols
                                u"\U0001F1E0-\U0001F1FF"  # flags (iOS)
                                u"\U00002500-\U00002BEF"  # chinese char
                                u"\U00002702-\U000027B0"
                                u"\U00002702-\U000027B0"
                                u"\U000024C2-\U0001F251"
                                u"\U0001f926-\U0001f937"
                                u"\U00010000-\U0010ffff"
                                u"\u2640-\u2642" 
                                u"\u2600-\u2B55"
                                u"\u200d"
                                u"\u23cf"
                                u"\u23e9"
                                u"\u231a"
                                u"\ufe0f"  # dingbats
                                u"\u3030"
                               "]+", flags=re.UNICODE)
    return emoji_pattern.sub(r'', text)

빌보드 조건에 맞게 딕셔너리 형태로 넣어주는 코드이다. 틱톡 데이터는 빌보드랑 다른 형태로 보내줘서 이부분은 틱톡과 빌보드 다르게 사용했다.

def doLastDict(weektime, billboard):
    # billboard['weeks']['index'] -> 'title', 'artist', 'rank'
    for j in range(weektime):
        print('----------')
        # print(newlist)
        newlist = []
        
        for i in range(100):
            title = billboard[j][i]['title']
            artist = billboard[j][i]['artist']
            rank = billboard[j][i]['rank']
            # print(title,artist,rank)
            new_dict = searchBar(title,artist,rank)
            # print(new_dict)
            newlist.append(new_dict)
            last_dict[j] = newlist
            print('---', j, i+1, '진행중')
        print(f'{j} --- 진행완료')
    return last_dict

 

 


실행코드는 아래와 같다.

weektime으로 몇주치를 가져올지 정하고, doLastDict으로 한번에 실행했다.

이후 피클 형태로 파일을 저장했다.

wd = webdriver.Chrome()
path1 = 'https://tunebat.com/'
wd.get(path1)
weektime=4
do_last_dict = doLastDict(weektime, billboard)
with open(f'project3_billboard/billboard_JSON_add_final2.pickle', 'wb') as f:
    pickle.dump(billboard3, f, pickle.HIGHEST_PROTOCOL)

추가로 이를 자동화하기 위해 아래처럼 수정해 사용했다.

# 실행코드
last_dict = {}

# 조사 주 입력
weektime = int(input())
weektime += 1

# 크롤링 시작
wd = webdriver.Chrome()
path1 = 'https://tunebat.com/'
wd.get(path1)

time.sleep(5)

# billboard 크롤링 파일 불러오기
with open('project3_billboard/billboard_JSON.pickle', 'rb') as f:
    billboard = pickle.load(f)

# 크롤링
do_last_dict = doLastDict(weektime, billboard)    

# 크롤링 파일 저장
with open(f'project3_billboard/billboard_JSON_add_{weektime}weeks.pickle', 'wb') as f:
    pickle.dump(do_last_dict, f, pickle.HIGHEST_PROTOCOL)

 

주제가 명확하지 않고 무엇이 필요할지 몰라 막무가내 시작했었는데, 이 수집이 끝났을 때쯤, 하루정도? tunbat 사이트가 spotify api를 가져와 사용한 사이트임을 알게됐다. 그러니까 굳이 크롤링으로 수집할 이유가 없었다.  그래도 일단 이 수집을 했으니, 이 데이터에 spotify api로 데이터를 더 붙이기로 했다.


※ 내가 참고하려고 쓰는  tmi

딕셔너리 형태를 서로 묶기 위해 insert 등을 사용해 봤는데

아래같은 형태가 제일 잘 묶였다. 이건 나중에 내가 보고 기억하려는 용도로 쓰는 것

real_last_last_dict = {**billboard3, **billboard4}