서론
데브코스 : 데이터 엔지니어링을 수강하면서 동적 웹 스크래핑을 진행하기 위한 Selenium 라이브러리를 배웠다. 강의에서 배운 내용을 다시 한번 활용해 보며, 익숙해지기 위해 해당 프로젝트(?)를 진행하였다. 단지 몇 시간 동안 들은 강의이고, 웹 관련 배경 지식이 없기 때문에 올바른 코드인지 확신할 수는 없지만, 강의 내용을 복습하는 것에 의의를 두려고 한다. 그래서 진행할 프로젝트는 무난하게 진행할 수 있는 영화 사이트의 리뷰를 스크래핑 및 시각화이다. 작성한 코드를 설명하고, 왜 해당 코드를 작성하였는지 설명하는 방식으로 진행할 것이다.
- 개발 환경 : Colab - Python
- 라이브러리 : Selenium, wordcloud, matplotlib, seaborn, collections, konlpy, time
진행 과정
진행 과정을 크게 분류하여 나열하면 다음과 같다.
- 라이브러리, 폰트 설치, 경로 설정, 한글 깨짐 방지
- 라이브러리 불러오기
- Selenium Driver 초기화
- 영화 제목 추출, 영화 선정 및 검색
- 해당 영화의 리뷰 명사 추출
- 시각화
영화 리뷰 사이트 스크래핑 및 시각화
https://www.cgv.co.kr/ << 프로젝트가 진행되는 웹 사이트
리뷰를 스크래핑하기 위해 활용한 사이트는 CGV이다. 웹 사이트의 검색 창을 활용하여 '명탐정 코난'의 리뷰를 스크래핑 및 명사 추출과 wordcloud와 seaborn을 활용해 시각화를 진행해 볼 것이다. 명탐정 코난을 고른 이유는 필자가 좋아하기 때문이다!
라이브러리, 폰트 설치, 경로 설정, 한글 깨짐 방지
pip, apt-get 등 터미널 환경에서 진행되는 코드를 가장 먼저 처리해 주었다.
# 사용할 라이브러리 설치
!pip install selenium
!pip install konlpy
# colab 환경에서 진행하기 위한 selenium 경로 설정
!apt-get update
!apt install chromium-chromedriver
!cp /usr/lib/chromium-browser/chromedriver /usr/bin
# wordcloud을 위한 폰트 설치
!apt-get install fonts-nanum*
!apt-get install fontconfig
# 한글 깨짐 방지
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
라이브러리 불러오기
서론에서 언급했던 프로젝트를 진행하기 위해 필요한 라이브러리를 불러온다.
# Selenium 라이브러리
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import Keys, ActionChains
import time
# 시각화 라이브러리
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
# 단어의 개수를 세기 위한 라이브러리
from collections import Counter
# 명사 추출을 위한 라이브러리
from konlpy.tag import Hannanum
Selenium Driver 초기화
로컬 환경에서의 초기화
만약 Colab 환경이 아닌 로컬 환경이라면, 추가적인 import를 진행하여 아래와 같은 코드를 통해 driver를 활용할 수 있을 것이다. 이렇게 진행하면 웹 페이지가 실제로 열리고, 시각적으로 확인하며 실습을 진행할 수 있다.
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
Colab에서의 초기화
Colab 환경에서는 웹 페이지를 띄울 수 없기 때문에 option을 추가하여 진행해야 한다. 아래의 코드는 웹 페이지를 띄우지 않게 하는 옵션을 추가하고 driver를 초기화한 것이다.
options = webdriver.ChromeOptions()
options.add_argument('--headless') # Head-less 설정
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=options)
영화 제목(명탐정 코난) 추출
이제 driver를 활용하여 영화 제목을 가져올 차례이다. CGV 홈페이지에서 '명탐정 코난'을 검색했을 때, 총 24개의 영화가 나타나고 목록은 다음과 같다.
영화 제목 추출
한 페이지에 6개의 영화가 있어 총 4페이지의 목록의 영화 제목을 추출해야 한다. 해당 페이지는 페이지네이션이 적용되어 url 마지막에 '~?query=명탐정코난&page=1'과 같은 형태로 page를 구분한다. 따라서 숫자만 변경하여 get 요청 후 제목을 추출한다. 영화 제목을 추출하는 방법은 영화 목록을 감싸고 있는 'ul' 태그의 클래스 이름인 'searchingMovieResult_list'를 활용한다. 또한 웹 페이지를 조작하는 중에는 사용자에게 입력을 받는 input()을 실행이 불가능해 driver를 닫아주었다.(영화를 선정할 때, input()을 활용할 것이다.)
movie_name = []
# 영화 이름 추출
for i in range(1, 5):
driver.get("http://www.cgv.co.kr/search/default.aspx?query=%EB%AA%85%ED%83%90%EC%A0%95%EC%BD%94%EB%82%9C&page={}".format(i))
movie_name += driver.find_element(By.CLASS_NAME, "searchingMovieResult_list").text.split('\n')
time.sleep(0.5)
# 페이지 닫기
driver.close()
영화 제목 전처리
위의 코드를 실행하면, 영화 제목뿐만 아니라 사진에 보이는 것처럼 '2023.07.20 개봉'과 같은 문자열이 섞여있다. 그래서 '명탐정'이 포함된 문자열만 추출하여 재저장하고, 사용자가 영화를 더 쉽게 선택할 수 있도록 딕셔너리에 저장하였다.
movie_name = [m for m in movie_name if "명탐정" in m]
movie_dict = dict(zip(range(1, len(movie_name)+1), movie_name))
movie_dict
# result
# {1: '명탐정코난-흑철의 어영',
# 2: '명탐정코난-하이바라 아이 이야기 ~흑철의 미스터리 트레인',
# 3: '명탐정 코난-할로윈의 신부',
# ...
# 23: '명탐정 코난 : 천공의 난파선',
# 24: '명탐정 코난 극장판 13 - 칠흑의 추적자'}
영화 선정 및 검색
영화 선정
try-except 구문을 활용해서 사용자가 올바른 입력을 할 때까지 입력을 진행하도록 하였다.
for k, v in movie_dict.items():
print(k, v)
while True:
try:
movie = movie_dict[int(input("영화를 선택해주세요. (1 ~ 24)\n"))]
break
except:
print("올바른 번호를 입력해주세요")
print("선택한 영화 :", movie)
영화 검색 및 페이지 접속
선정한 영화의 리뷰를 찾기 위해 다시 driver를 실행하고, get을 요청한다. 그리고 검색을 위한 'input' 태그를 가진 요소의 ID인 'header_keyword'에 선정한 영화 제목을 입력하고, 검색 버튼의 ID를 활용하여 클릭하도록 하였다. 검색 후 해당 영화의 상세 정보를 확인하기 위해 클릭하는 작업도 추가해 주었다.
driver = webdriver.Chrome(options=options)
driver.get("http://www.cgv.co.kr/")
# 영화 검색
text_input = driver.find_element(By.ID, "header_keyword")
ActionChains(driver).send_keys_to_element(text_input, movie).perform()
time.sleep(0.5)
search_button = driver.find_element(By.ID, "btn_header_search")
ActionChains(driver).click(search_button).perform()
time.sleep(0.5)
# 페이지 접속
link_button = driver.find_element(By.CLASS_NAME, "searchingMovieResult_list").find_element(By.TAG_NAME, 'img')
ActionChains(driver).click(link_button).perform()
p. 1-10 리뷰 명사 추출
추천순 리뷰로 변경
신뢰성 있는 정보를 얻기 위해 추천순으로 변경 후 리뷰를 추출하도록 한다. '최신순'과 '추천순'을 담고 있는 'ul' 태그의 ID인 'sortTab'과 내부 태그인 'a'를 활용하면, 두 개의 객체가 나오게 된다. '최신순' 링크와 '추천순' 링크이다. 순서대로 리스트에 담기므로 리스트 1번에 '추천순'이 담기게 되며, 이를 클릭하도록 코드를 작성하였다.
rec_button = driver.find_element(By.ID, "sortTab").find_elements(By.TAG_NAME, "a")[1]
ActionChains(driver).click(rec_button).perform()
p. 1-10 리뷰 추출
리뷰 페이지를 넘어갈 때도 페이지네이션을 통해 진행하려고 했지만, 원하는 대로 수행되지 않았다. 페이지에 따라 url이 '~#1'과 같이 # 뒤에 페이지 번호가 나타났다. 하지만 # 뒤의 번호를 바꾸더라도 페이지가 변하지 않아 해당 방법을 적용하지 못했다. 따라서 find_elements가 요소를 저장할 때, 위에서 아래로 저장하는 것을 이용해서 반복문으로 각 페이지의 리뷰를 추출할 수 있도록 하였다.
reviews = []
for i in range(10):
page_button = driver.find_element(By.ID, "paging_point").find_elements(By.TAG_NAME, "a")[i]
ActionChains(driver).click(page_button).perform()
time.sleep(0.5)
comments = driver.find_elements(By.CLASS_NAME, "box-comment")
for c in comments:
reviews += c.text.split('\n')
reviews[:10]
# result
# ['스릴넘치고 전에 개봉한 영화보다 더 재밌었어요! 다음편이 기대되요.',
# '코난은 왜 나이를 먹지 않을까. 짱구와 같은 약을 먹은거 같은데 부럽',
# '그냥 다 최고에요 ㅠㅠㅠㅠ 명탐정코난 전혀 보지도 않고 등장인물에 코난만 아는 제 친구랑 오늘 씨지브이가서 오늘 한국에 개봉한 코난 영화 그 친구도 저처럼 되게 재미있었다네요 !!',
# '코난을 좋아하는 아이들은 재밌게볼거같아요~ 어른들은 약간 지루하실수 있겠네요~^^',
# '코난 이즈 뭔들ㅋㅋ최고예요!',
# '명탐정코난 다른편들보다는 감동이 덜했지만 재미있었어요!!',
# '역대코난중 최고!!!!!!+!!!!',
# '저번에 나왔던거라 이미 스토리는 다알았는데 더빙으로 다시한번보니까 재미있어요!!',
# '좋았어요 카드도 주고ㅎ2장다 주시지 하나 고르라고하니..한번더봐야하나 고민입니다',
# '역시 코난!! 아쉬운건 더빙인것 그래도 언제나 듣던 더빙목소리라 어색하진 않아ㅋㅋ']
리뷰 명사 추출 및 단어 개수 세기
konlpy의 Hannanum을 사용해서 명사 추출을 진행하였다. 원래 한글 형태소 추출이 쉽지 않은 것을 알고 있었지만, 생각보다 제대로 추출이 안 되는 것을 느꼈다. 그래도 여기서는 웹 스크래핑을 배우는 목적이 있으므로 길이가 긴 것을 제외해 준 것 말고는 추가적인 처리 작업을 수행하지 않았다.
hannanum = Hannanum()
words = []
for r in reviews:
nouns = hannanum.nouns(r)
words += [noun for noun in nouns if len(noun) > 1]
words = [word for word in words if len(word) < 10]
counter = Counter(words)
시각화
시각화는 '명탐정 코난-진홍의 수학여행'으로 진행하였다.
상위 10개 단어 시각화
명사 추출이 제대로 되지 않아 ',ㅡ'가 상위 목록에 들어있는 것을 볼 수 있다.
x = [v[0] for v in counter.most_common(10)]
y = [v[1] for v in counter.most_common(10)]
plt.title("상위 10개 단어 목록")
plt.xlabel("단어")
plt.ylabel("빈도")
plt.ylim(0, y[0] + 2)
sns.barplot(x=x, y=y)
워드클라우드 시각화
wordcloud = WordCloud(
font_path='/usr/share/fonts/truetype/nanum/NanumGothic.ttf',
background_color='white',
width=1000,
height=1000
)
plt.axis('off')
img = wordcloud.generate_from_frequencies(counter)
plt.imshow(img)
결론
배운 내용만을 활용하였기 때문에 어려운 작업이 포함된 것도 아니고 시간이 많이 걸리는 작업도 없었다. 간단하게 배운 내용을 활용해 볼 수 있는 시간이었고, 이전부터 생각만 해봤던 작업이었는데 실제로 해보니 재밌었던 시간이었다. 지금처럼만 데브코스를 진행한다면, 여기에 다른 요소를 많이 추가하는 결과와 실력을 얻을 수 있을 거라 확신한다.
참고링크
구글 코랩으로 크롤링하기
https://modulabs.co.kr/blog/google_colab_crawling_tip/
python list to dict 리스트 딕셔너리로 변환하기
한국어 wordCloud 생성
https://velog.io/@dltpal07/%ED%95%9C%EA%B5%AD%EC%96%B4-wordCloud-%EC%83%9D%EC%84%B1
구글 코랩(colab) 한글 깨짐 현상 해결방법
'[프로그래머스] 데이터 엔지니어링 데브코스 3기 > 기타' 카테고리의 다른 글
[3기] 프로그래머스 데브코스 데이터 엔지니어링 수료 후기 (4) | 2024.08.25 |
---|---|
[시행착오] 데브코스 3차 프로젝트(Airflow 활용 ETL) (0) | 2024.06.20 |
데이터 엔지니어링 OT 및 특강 (4) | 2024.03.22 |
[3기] K-Digital Training: 데이터 엔지니어링 데브코스 지원 후기 (0) | 2024.03.11 |