본문 바로가기

IT/크롤링

로튼 토마토 평점 크롤링 하기

2019/03/23 - [IT/크롤링] - 크롤링

앞서 포스팅한 크롤링에서 이제 구현 단계를 포스팅하도록 하겠습니다. 

대상은 영화 평점 사이트인 로튼토마토에서 영화 제목/개봉일/관객 평점을 크롤링하도록 하겠습니다.

로튼토마토를 대상으로 잡은 이유는 다른 사이트와 내부적 동작이 조금 달라 보여서 대상으로 잡았습니다.

해당 내용은 아래에서 설명하겠습니다.

 

자 그럼 시작 하겠습니다.

 

PS: 해당 포스팅은 파이선을 이용한 크롤링입니다. 이점 참고하여 주세요.

1. 사이트 분석하기

 일단 크롤링을 하기 위해서는 대상 사이트의 html 구조를 분석해야 합니다. 이 구조를 파악하기 좋은 게 각 브라우저에 있는 개발자 도구입니다. 사이트에서 F12 키를 누르면 개발자 도구가 나오게 됩니다. 아래처럼 나오게 됩니다.

 

로튼토마토 메인

해당 페이지를 보면 각 메뉴별 최대 10개 정도만 보입니다. 이건 수가 너무 적으니 다른 페이지에서 크롤링하기로 결정합니다. 매출 순위로 정렬된 top box office 메뉴로 들어가 보겠습니다. Top Box Office 아래의 "Viwe All" 클릭하여 들어가 봅니다.

 

로튼 토마토의 top box office

이제 사이트 구성 요소(html 태그)들을 확인해야 할 차례입니다. 위에서 언급한 개발자 도구를 이용하여 확인하면 됩니다. 일단 제일 앞에 있는 덤보 제목에서 우클릭 후 검사 메뉴를 선택합니다.

요소 검사

위 과정을 거쳐서 알 수 있는 것은 다음과 같습니다.

  1. 영화 제목을 감싸고 있는 태그는 "h3"에 "movieTitle" 클래스를 가지고 있다.
  2. 영화 평점을 감싸고 있는 태그는 "span"에 "tMeterScore" 클래스를 가지고 있다.
  3. 영화 평점은 동일한 태그 두 개로 나누어져 있으며, 전문가 평점이 먼저 나오고, 관객 평점이 뒤에 온다.
  4. 개봉일을 감싸고 있는 태그는 "p" 태그에 "release-date" 클래스를 가지고 있다.
  5. 이 모든 정보를 영화별로 감싸고 있는 태그는 "div"에 "movie_info"클래스를 가지고 있다.

2. 크롤링 구현하기

영화 정보가 담겨 있는 태그는 아래의 구조로 되어 있는 것을 확인할 수 있습니다.

<div class="movie_info">
		<h3 class="movieTitle">영화제목</h3>
		<div>
			<span class="tMeterIcon tiny">
				<span class="icon tiny rotten"></span>
				<span class="tMeterScore">전문가평점</span>
			</span>
			<span class="tMeterIcon tiny">
				<span class="icon tiny popcorn"></span>
				<span class="tMeterScore">관객평점</span>
			</span>
		</div>
		<p class="release-date">In Theaters 개봉일</p>
</div>

 

이제 앞 포스팅에서 언급한 데로, selenium을 이용하여 html 소스를 가져오고 해당 html을 beautifulsoup을 이용하여 파싱 하도록 하겠습니다. 

 

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException,StaleElementReferenceException
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import datetime
from dateutil.parser import parse
import sys


driver = webdriver.PhantomJS('팬텀JS 드라이버 경로')
driver.get('https://www.rottentomatoes.com/browse/in-theaters?minTomato=0&maxTomato=100&minPopcorn=0&maxPopcorn=100&genres=1;2;4;5;6;8;9;10;11;13;18;14&sortBy=popularity')
try:
	WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME , "movie_info")))
	html = driver.page_source
	soup = BeautifulSoup(html, "html.parser")
	info = soup.find_all("div", {"class" : "movie_info"})
	for item in info:
		name = item.find("h3", {"class" : "movieTitle"})
		point = item.find_all("span", {"class" : "tMeterScore"})
		releasedatetag = item.find("p", {"class" : "release-date"})
		releasedate = releasedatetag.text
		releasedate = releasedate.replace("In Theaters ", "")
		rdate = parse(releasedate)
		if len(point) == 0:
			pointtext = 'None Score'
		else:
			pointtext = point[len(point) - 1].text.strip()

		print('{:50}'.format(name.text.strip()) + '{:15}'.format(pointtext) + rdate.strftime("%Y-%m-%d"))
finally:
	print("===================END==============")
		
driver.close()

위 코드는 selenium의 PhantomJS를 이용하여 https://www.rottentomatoes.com/browse/in-theaters 페이지를 요청하여, class 명이 "movie_info" 란 태그가 나올 때까지 최대 10초를 대기합니다.

 

클래스 명이 "movie_info"인 태그가 나오면 해당 페이지의 html 소스를 가져옵니다.

html = driver.page_source
soup = BeautifulSoup(html, "html.parser")

html 소스를 가져와서 beautifulsoup을 이용하여 html 파싱을 요청하는 구문입니다.

info = soup.find_all("div", {"class" : "movie_info"})

"div" 태그에 "movie_info" 클래스를 가진 모든 태그를 리스트 형태로 가져오는 구문입니다.

for item in info:
	name = item.find("h3", {"class" : "movieTitle"})
	point = item.find_all("span", {"class" : "tMeterScore"})
	releasedatetag = item.find("p", {"class" : "release-date"})
	releasedate = releasedatetag.text
	releasedate = releasedate.replace("In Theaters ", "")
	rdate = parse(releasedate)
	if len(point) == 0:
		pointtext = 'None Score'
	else:
		pointtext = point[len(point) - 1].text.strip()

for문 구문은 다음과 같은 절차로 구현되어 있습니다.

  1. "h3" 태그에 "movieTitle" 클래스를 가진 항목을 찾아서 "name"이라는 변수에 저장
  2. "span" 태그에 "tMeterScore"클래스를 가진 모든 항목을 찾아서 "point"에 리스트 형태로 저장
  3. "p" 태그에 "release-date" 클래스를 가진 항목을 찾아서 "releasedatetag"라는 변수에 저장
  4. "releasedatetag"에 저장된 html 태그에서 표시 문구를 추출하여, "releasedate"라는 변수에 저장
  5. 추출된 "releasedate"에서 "In Theaters "을 빈 공백으로 치환하여 "releasedate"에 저장
  6. 최종 추출된 문자를 dateutil의 parse 함수를 이용하여 datetime 객체를 생성하여, rdate에 저장
  7. 2번에서 추출된 항목이 없으면 pointtext를 "None Score"로 저장
  8. 2번에서 추출된 항목이 있으면 point 리스트의 마지막 항목의 표시문구를 공백 제거하여 pointtext에 저장

그리고, 날짜가 "Mar 29"로 표시되는 것을 python의 datetime 객체로 변환을 위해서  dateutil.parser 패키지의 parse 함수를 사용했습니다. 해당 패키지는 python의 기본 패키지가 아니므로, 별도 설치가 필요합니다.

pip install python-dateutil

이제 마지막 단계입니다. 

가져온 값을 화면에 출력하도록 하겠습니다.

print('{:50}'.format(name.text.strip()) + '{:15}'.format(pointtext) + rdate.strftime("%Y-%m-%d"))

제목을 50글자로 좌 정렬하면서 빈 공백을 제거하고 출력시키고, 평점을 15글자로 좌정렬 출력하고, 개봉일을 년-월-일 형식으로 출력시키는 구문입니다.

 

 이렇듯 크롤링이 그렇게 어렵지는 않습니다. html 형식을 파악하고, 원하는 데이터가 존재하는 태그를 찾아서 해당 태그에서 값들을 추출하는 것이 다입니다.

 

제가 selenium을 이용한 이유에 대한 설명을 안 했네요.

python에서 http 요청을 하여 html을 응답받는 것은 requests라는 것도 존재합니다만, 해당 모듈로 로튼토마토를 요청하였을 경우, html 파싱이 정상적으로 안됩니다.

안 되는 원인은 개발자 도구로 확인 시, movie_info 클래스가 보이지만, 실제 소스를 보면 해당 태그가 보이지 않습니다.

그 원인은 로튼 토마토의 경우, 화면이 그려질 때, 영화 정보를 가져오는 것이 아니라, 기본화면을 먼저 그리고, 영화 정보를 json으로 가져오고 그 json 정보를 이용하여 화면을 그리기 때문인 듯합니다.

 selenium의 경우, 웹페이지를 테스트하는 프로그래밍을 위해 만들어진 모듈로, html 소스를 응답받는 것이 아니라, 브라우저가 화면이 그려진 상태의 html 태그들을 읽어 들이기 때문에 ajax 처럼 추후에 데이터를 가져와서 화면을 구성하는 내역도 가져올수 있습니다. 하여 requests 같은 http 요청을 하는 모듈 보다는 selenium을 이용하여 크롤링을 하는 것이 더 유리합니다.

 

모두 크롤러를 하나씩 만들어 보면 좋을 듯합니다.

2019/03/23 - [IT/크롤링] - 크롤링

'IT > 크롤링' 카테고리의 다른 글

크롤링  (0) 2019.03.23