[Metacritic 포켓몬 S/S 리뷰 분석 프로젝트] 2. 데이터 수집
데이터 분석 프로젝트의 두번째 단계는 타겟 데이터의 수집이다. 현업에서는 주로 DB에서 SQL등의 데이터베이스 언어를 통해 데이터를 불러오지만 타겟 데이터가 수집되어 있지 않은 경우에는 직접 크롤링을 통해 데이터를 확보해야 한다. 본 프로젝트는 메타크리틱에서 유저 리뷰를 크롤링한다.
메타크리틱 웹 페이지의 포켓몬 소드 항목이다. 왼쪽에는 유저가 매긴 평점이 나타나고 아이디와 리뷰 내용이 표시되어 있다. 본 프로젝트의 경우 리뷰에 대한 분석과 감성분석을 목적으로 하기 때문에 사용자 아이디를 제외한 평점과 리뷰 내용을 크롤이 해야 한다. (만약 사용자 아이디에 따른 추천시스템 프로젝트를 목표로 한다면 아이디 항목을 통해 구현해 볼 수 있을 것이다.)
크롤링 작업의 경우 웹 페이지 마다 접근 방법이 상이할 수 밖에 없고 많은 방법으로 시도해보는 소위 노가다 작업이 좀 필요하다. 또한 javascript를 이용한 동적 웹 페이지의 경우 그러한 부분 까지 고려해야 하므로 작업이 더 까다로울 수 있다. 우선 크롤링한 타겟 데이터가 담긴 페이지를 보면서 발생할 수 있는 크롤링 및 전처리 이슈에 대해 고민해보는 것이 좋다. 또한 타겟 데이터의 수가 많을수록 코드를 통해 자동화하는 부분이 많아져야 데이터 수집이 용이해진다.
본 웹 페이지의 경우 request, xml 다른 방식은 모두 막아놓았기 때문에 selenium을 쓸 수 밖에 없었다.
타겟 데이터 살펴보기
※ 크롤링 이슈정리
expand 버튼의 클릭
상기 메타크리틱 페이지의 경우 특정 글자 수 이상의 리뷰에 대해서는 자동으로 접혀있기 때문에 리뷰 전문을 크롤링 하기 위해서 expand 버튼을 일일이 클릭해줘야 한다. expand를 클릭하면 위의 부모 태그가 바뀌기 때문에 같은 css셀렉터로 크롤링 할 수가 없었다. 결국 expand를 모두 클릭하고 전체를 크롤링한 뒤 전처리 단계에서 해결하는 방식을 취했다.
마지막 페이지의 인덱스를 넣어 자동으로 수집
첫 페이지는 url이 달랐기 때문에 별도로 크롤링 했으며 이 때 리뷰의 마지막 페이지를 가져와 while문으로 자동화할 실마리를 마련했다. 마지막 페이지까지 자동으로 크롤링하기 위해서 인덱스 변수를 적절히 조절한다. 첫페이지의 경우 url에 페이지 숫자가 포함되지 않는 형태였기 때문에 첫 페이지만 따로 크롤링해서 last_page 변수에 페이지 수를 할당하는 방식을 적용해 보았다.
알 수 없는 이유의 서버 응답 불가
다음 페이지로 넘어가는 next 버튼 클릭 방식으로 전체 코드를 완성하였으나 왠지 4페이지 이상부터는 서버에서 webdriver를 거부했다는 메시지가 뜨고 페이지가 로딩되지 않았다. 메타크리틱 웹 자체에서 막아놓은 것인지 명확히 해결하지 못하였다. 그렇다면 혹시 웹 드라이버 창에서 너무 많은 자동 명령을 실행해서 거부되는 것일까? 이에 페이지 별로 창을 따로 열고 크롤링하고 닫고 다시 다음 페이지 창을 여는 방법으로 바꾸었다.
또한 왠지 페이지가 열리는 속도가 매우 느려 크롤링에 실패하기 일쑤였다. 첫페이지부터 쭉 크롤링을 진행하다가 중간에 서버에서 응답이 되지 않는 오류가 발생하였다. 메타크리틱 페이지의 경우 응답속도가 상대적으로 느렸기 때문에 time.sleep을 최대한 길게 가져가서 안정적으로 데이터를 수집하기로 하였다.
크롤링 코드 작성
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import chromedriver_autoinstaller
import time
from tqdm import tqdm_notebook
import re
import warnings
warnings.filterwarnings('ignore')
##### 첫 페이지 크롤링 #####
keyword1 = 'pokemon-sword'
keyword2 = 'pokemon-shield'
# 크롬 웹 브라우저 실행 (keyword 검색결과)
chrome_path = chromedriver_autoinstaller.install()
driver = webdriver.Chrome(chrome_path)
driver.get('https://www.metacritic.com/game/switch/{}/user-reviews'.format(keyword1)) # Query parameter로 직접 keyword 넣는 방식
# 페이지 수 가져오기
last_page=driver.find_element_by_css_selector('.page.last_page').text
last_page=int(re.sub('[\D]','',last_page)) # 정규표현식으로 비숫자 제거
# Expand 클릭
a = driver.find_elements_by_css_selector('.toggle_expand_collapse.toggle_expand')
for j in range(0,len(a)):
a[j].click()
time.sleep(0.1)
# grade 수집
grade_list=[]
grade_raw = driver.find_elements_by_class_name('review_grade')
del grade_raw[-3:] # 뒤의 세개의 grade는 critic grade 이므로 삭제
for l in grade_raw:
grade_int = int(l.text)
grade_list.append(grade_int)
print('grade 수집/정제 완료')
time.sleep(1)
# reivew 수집
review_list = []
review_raw=driver.find_elements_by_css_selector('.body.product_reviews .review_body')
time.sleep(1)
for k in tqdm_notebook(review_raw):
review_text = k.text
review_list.append(review_text)
print('review 수집/정제 완료')
time.sleep(1)
driver.quit()
##### 나머지 페이지 크롤링 #####
i=1 # url에서 page1 이 실제 두번째 페이지를 나타냄
while i < last_page:
# Expand 클릭
chrome_path = chromedriver_autoinstaller.install()
driver = webdriver.Chrome(chrome_path)
driver.get('https://www.metacritic.com/game/switch/{}/user-reviews?page={}'.format(keyword1,i))
time.sleep(10)
a = driver.find_elements_by_css_selector('.toggle_expand_collapse.toggle_expand')
for j in range(0,len(a)):
a[j].click()
time.sleep(0.1)
# grade 수집/정제
grade_raw = driver.find_elements_by_class_name('review_grade')
del grade_raw[-3:] # 뒤의 세개의 grade는 critic grade 이므로 삭제
for l in grade_raw:
grade_int = int(l.text)
grade_list.append(grade_int)
print('grade 수집/정제 완료')
time.sleep(1)
# review 수집/정제
review_raw=driver.find_elements_by_css_selector('.body.product_reviews .review_body')
time.sleep(1)
for k in tqdm_notebook(review_raw):
review_text = k.text
review_list.append(review_text)
print('review 수집/정제 완료')
time.sleep(1)
print(i+1,'page 작업 완료')
if i+1 == last_page:
print('#####모든 작업이 완료되었습니다.#####')
i += 1
driver.close()
time.sleep(5)
데이터프레임으로 만들기
import pandas as pd
df_sword['Game'] = 'sword' # Game 컬럼에 sword 항목 추가
df_sword.isnull().sum()
df_shield['Game']='shield' # Game 컬럼에 shield 항목추가
df_shield.isnull().sum()
df_all = pd.concat([df_sword,df_shield],ignore_index=True) # concat 으로 합치기
df_all.reset_index(drop=True,inplace=True) # 인덱스 초기화 하고 대체
df_all.info()
Sword와 Shield 모두 크롤링 한 뒤 pandas 를 이용해 게임 컬럼을 추가하여 dataframe 형태로 만들었다. 게임 컬럼을 추가하여 소드와 실드 중 어떤 리뷰에 해당하는 지 식별할 수 있으므로 하나의 데이터프레임으로 합쳐 타겟 데이터를 확보하였다.
이제 데이터 전처리를 위한 기본 준비가 완료되었다.
상기 데이터는 데이터 분석 공부를 목적으로 수집하였으며 프로젝트 완료 후 폐기하였습니다