nan + nan = 2nan

[Python3][Selenium] 셀레니움을 이용해서 멜론 일간 차트 크롤링해보기 본문

Python

[Python3][Selenium] 셀레니움을 이용해서 멜론 일간 차트 크롤링해보기

2nan 2021. 9. 1. 20:46
728x90

이전 파이썬 강의 때 짧게 크롤링을 경험해본 적이 있다. 

그렇지만 촉박한 강의 일정과 프로젝트로 인해 3주 전 기억이 삭제되었다..

평소 이 교육을 듣지 않을 때에도,

크롤링을 시도해 본 적이 있다.

취업 준비를 하면서 내 입맛에 맞는 경제 기사들을 따로 모아서 보고 싶었는데,

언어를 잘 알지 못하다보니, 어려운 부분이 많았다..

지금도 조금 어렵긴 하지만,

기술을 익혀놔서

내 입맛에 맞는 정보들을 내 스스로 모을 수 있는 능력을 키우고 싶다!

우선 SeleniumBeautifulSoup 크게 두 가지의 프레임워크를 배웠다.

그중 멜론은 두 가지 다 이용 할 예정이다.

html의 요소들을 호출하는 방법은 request를 이용해서 직접 url을 호출하는 방법

Selenium으로 경로를 지정해줘서 직접 들어갈 수 있게 하는 방법이 있다.

근데, 아직은 잘 모르지만 경험에 기반했을 때 대량의 데이터를 가져오는 경우에는

Selenium은 조금 빡세다는 생각이 들었다.

직접 사람이 손으로 하는 것처럼 데이터를 하나 하나 뽑아오는 느낌이랄까.

그래도 자동으로 척척 해나가는 모습은 신기했다.

 

이론적으로 완전히 정확하지는 않지만, 내가 하면서 느끼는대로 표현해보았다.

그럼 멜론 차트 크롤링을 단계별로 해보자.

 

1) 우선 import할 목록을 살펴보자.

 

from selenium import webdriver
from bs4 import BeautifulSoup as bs
import pandas as pd 
from collections import OrderedDict
import time

 

selenium을 import하기 위해서는 webdriver가 필요하다. 브라우저 별로 다양하게 있지만,

대부분 chrome을 사용하고 나도 chrome 유저이기 때문에, chrome으로 진행해보겠다.

chromewebdriver를 검색해서 우선 설치를 해주어야 한다.

https://chromedriver.chromium.org/downloads

 

ChromeDriver - WebDriver for Chrome - Downloads

Current Releases If you are using Chrome version 93, please download ChromeDriver 93.0.4577.15 If you are using Chrome version 92, please download ChromeDriver 92.0.4515.107 If you are using Chrome version 91, please download ChromeDriver 91.0.4472.101 For

chromedriver.chromium.org

다운로드는 해당 링크에서 가능하다.

자신의 크롬 버전에 맞게 다운로드를 하면 되는데,

 

나의 크롬 버전을 알기 위해서는

크롬에서 오른쪽 맨 위 세 점을 누르고 설정에 들어가 맨 왼쪽 네비 바를 클릭하면 해당 메뉴가 뜨고,

자신의 크롬 버전 정보를 파악할 수 있다.

단,  chromewebdriver의 압축을 푸는 파일의 경로가 일정한 곳에 지정되어 있는 것이 편하다.

계속해서 driver라는 변수를 선언할 때,

webdriver의 설치 장소를 입력해야 하기 때문에, 보통 작업을 하는 폴더 내에 위치하는 것이 편하다.

나는 c:드라이브에 pydata라는 폴더를 생성하고 그 안에 설치를 해주었다.

BeautifulSoupParsing을 도와주는 역할을 담당한다. 

html 내 source를 끌어와서, 경로를 지정해서 데이터를 정렬하고 시각화하는데 도움을 주는 역할을 한다.

as bs는 하위에서 해당 모듈을 선언할 때 편하게 하게끔 줄임말로 만드는 역할을 한다.

pandas도 시각화에 도움을 주는 녀석인 것 같다.

pandas 내에 여러 프레임을 이용해서 해당 결과 값을 이쁘게 표로 출력해주거나 하는 역할을 담당한다.

OrderedDict는 몰랐는데, 중복되는 값을 없애기 위해 구글링으로 만난 녀석이다.

(사실 위에서 경로를 제대로 설정해주었으면 이 놈을 설정할 필요가 없었다.)

time은 각 명령 가운데서 시간 차를 조정해서 발생할 수 있는 오류를 없애주는 역할을 맡는다.

 

2) Selenium을 이용해서 '멜론 일간 차트'에 진입하기

 

url = 'https://www.melon.com/'
driver = webdriver.Chrome('c:\pydata\chromedriver.exe')
driver.implicitly_wait(10)
driver.get(url)

# 셀레니움으로 일간 페이지까지 찾아 들어가기
driver.find_element_by_xpath('//*[@id="gnb_menu"]/ul[1]/li[1]/a/span[2]').click()
time.sleep(2)

driver.find_element_by_xpath('//*[@id="gnb_menu"]/ul[1]/li[1]/div/ul/li[2]/a/span').click()
time.sleep(2)

 

 

우선 urldriver를 선언한다.

url에는 request로 호출하는 것이 아니기 때문에, 첫 홈페이지 주소를 입력했고, driver는 자동으로 프로그래밍 된대로 들어가게끔 만들어주는 놈을 불러오는 느낌이다. 뒤에는 절대경로로 해당 파일의 위치를 입력했다. 상대경로도 되는 것으로 알고 있지만, 문제가 발생할 우려가 있었던가.. 알았는데 까먹었다..  무튼 절대경로가 편하기 때문에 절대경로를 사용하겠다

그리고 implicitly wait을 사용해서 명시적으로 대기하는 Term을 만들어준다. 시간 조정 없이 모든 명령들이 한꺼번에 입력이 되면 충돌할 우려가 있기 때문에, 해당 홈페이지에 진입하고 driver를 실행하는 시간을 벌어준다.

그리고 driver.get 함수를 통해 해당 url로 접근한다.

그리고 홈페이지를 살펴보면, 멜론 차트에 대한 버튼에 대해 경로를 알 수 있다.

F12를 이용해 개발자 모드로 진입하고, 해당 버튼의 속성을 파악하는 것이 중요하다. 

이 과정에서는 속성이 겹치는 경우도 많기 때문에, 꼼꼼하게 살펴 볼 필요가 있다.

나는 해당 고유의 Class나 id가 보이지 않아서.. xpath라는 놈을 사용했다.

해당 버튼을 클릭 후, 개발자 모드에 음영처리 된 부분을 오른쪽 클릭을 한 다음

Copy > Copy Xpath 로 경로를 복사해서 붙여넣는다.

아마도? 절대경로로서 내가 선택한 부분이 어느 위치에 있는지 절대적인 경로를 나타내는 것 같다.

나는 이것을 활용해서

 

driver.find_element_by_xpath('해당 복사한 거 붙이기').click()

 

이라는 식을 만들었다.

xpath의 요소로 찾아가고, 뒤의 click 함수를 통해 해당 버튼을 클릭하게끔 명령하는 것이다.

멜론 차트에 들어가서, 일간 버튼을 클릭할 때도 똑같은 논리로 접근했다.

여기까지는 쉽게 도출할 수 있었다.

그리고 중간 중간 time.sleep으로 잠깐 잠깐 Term을 벌려주었다.

 

 

3) 이제 Beautiful한 soup을 사용할 때가 왔다.

 

# 해당 페이지 사용. bs를 활용
html = driver.page_source
soup = bs(html, 'html.parser')
song_soup = soup.select('tbody > tr')

 

html이라는 변수를 생성해서 driver를 실행할 곳을 만들고 bs를 활용해 html을 파싱해주는 변수인 soup을 생성한다.

해당 변수 내에서 나는 일간 차트 내에 곡 정보, 앨범, 좋아요 수를 끌어올 예정이다.

이를 위해서 해당 class와 id 구성을 보았을 때, tbody 안의 tr로 움직인다.

내가 봤을 땐 가장 처음 tbody가 해당 차트가 위치한 자리였기 때문에,

해당 위치의 정보들을 끌어올 수 있다,

 

4) 각 요소마다 해당 경로를 파악해 입력해주고 정보를 끌어온다.

 

song_lst = []

for song in song_soup:
    song_rank = song.find('span', class_= 'rank')
    song_title = song.find('div', class_= 'ellipsis rank01').get_text()
    song_artist = song.find('span', class_= 'checkEllipsis').get_text()
    song_album = song.find('div', class_= 'ellipsis rank03').get_text()
    song_likes = song.find('button', class_ = 'button_etc like').get_text()
    
    
    # artist가 2번씩 출력되는 것을 막기 위해 사용
#     song_artist1 = ''.join(OrderedDict.fromkeys(song_artist))
   
    # list에 append하면서 쓸데없는 문자, 공백 replace를 통해 삭제
    song_lst.append([song_rank, song_title.replace('\n', ''), song_artist.replace('\n', ''), song_album.replace('\n', ''), song_likes.replace('\n', '').replace('좋아요총건수', '')])

 

우선 해당 변수들을 한꺼번에 모아줄 list를 생성하고,

해당 요소들을 한 개씩만 끌어오는게 아니라, 1위부터 100위까지 끌어와야 하기 때문에

for 반복문을 활용해서 끌어온다.

song_soup으로 이어진 경로 내에서 각각 class 변수와 이어지는 경로들을 설정하고,

뒤에 .get_text( ) 를 이용해서 text 상태로 가져오게끔 만들었다.

요소들을 다 가져왔다면, 만들어 둔 list에 append해준다. 이 때, 요소들이 내가 원하는 txt만 오는게 아니라,

특수문자나 여러 가지 요소들이 같이 딸려오는 경우가 있으니 .replace 함수를 이용해서 이를 함께 제거시키면서 

append를 해주었다.

 

5) DataFrame을 씌워주어 가져온 Data를 깔끔하게 보여주자

 

# Chrome창 종료    
driver.quit()

df = pd.DataFrame(song_lst, columns = ['rank', 'title', 'artist', 'album', 'likes'])
df.head(50)

 

driver.quit( ) 명령을 이용해서 파싱이 끝난 드라이버를 자동으로 종료시켜준다.

그다음 df라는 변수에 Pandas를 활용해서 list에 담은 Data와 해당 Data에 표현 할 열이름을 설정해준다.

그리고 .head( ) 명령어를 사용해 위에서부터 괄호 내의 수만큼 Data를 표시할 수 있다.

 

그렇게 하면, 결과 값이 이렇게 생성된다.

 

 

 

물론 더 간편한 코드가 있을거지만, 내가 배운 내에서

할 수 있는만큼 표현해보았다.

이걸 활용해서 웹사이트를 만드는 곳에 한 번 올려봐야겠다.

조금 더 간다면 그동안 생각만 해봤던 경제 기사 크롤링을 웹 사이트에 게재하는 

개인적인 미션까지 가봐야겠따

 

etc) 전체 코드

 

from selenium import webdriver
import time
from bs4 import BeautifulSoup as bs
# from urllib.request import urlopen
import pandas as pd 
from collections import OrderedDict
import re


url = 'https://www.melon.com/'
driver = webdriver.Chrome('c:\pydata\chromedriver.exe')
driver.implicitly_wait(10)
driver.get(url)

# 셀레니움으로 일간 페이지까지 찾아 들어가기
driver.find_element_by_xpath('//*[@id="gnb_menu"]/ul[1]/li[1]/a/span[2]').click()
time.sleep(2)

driver.find_element_by_xpath('//*[@id="gnb_menu"]/ul[1]/li[1]/div/ul/li[2]/a/span').click()
time.sleep(2)

# 해당 페이지 사용. bs를 활용
html = driver.page_source
soup = bs(html, 'html.parser')
song_soup = soup.select('tbody > tr')



song_lst = []

for song in song_soup:
    song_rank = song.find('span', class_= 'rank')
    song_title = song.find('div', class_= 'ellipsis rank01').get_text()
    song_artist = song.find('span', class_= 'checkEllipsis').get_text()
    song_album = song.find('div', class_= 'ellipsis rank03').get_text()
    song_likes = song.find('button', class_ = 'button_etc like').get_text()
    
    
    # artist가 2번씩 출력되는 것을 막기 위해 사용
#     song_artist1 = ''.join(OrderedDict.fromkeys(song_artist))
   
    # list에 append하면서 쓸데없는 문자, 공백 replace를 통해 삭제
    song_lst.append([song_rank, song_title.replace('\n', ''), song_artist.replace('\n', ''), song_album.replace('\n', ''), song_likes.replace('\n', '').replace('좋아요총건수', '')])
    
# Chrome창 종료    
driver.quit()

df = pd.DataFrame(song_lst, columns = ['rank', 'title', 'artist', 'album', 'likes'])
df.head(50)

 

Comments