본문 바로가기
text/Python

네이버 영화평 가져오기

by hoonzii 2021. 3. 29.
반응형

네이버 영화평 가져오기 설명

들어가기 전 네이버의 robots.txt 에 대해 먼저 숙지하자.

네이버의 robots.txt

 

 

사용 언어 및 모듈
    - python 3.7
    - request = request 요청을 보내 html 값을 가져오기
    - bs4 (BeautifulSoup) = 받은 html 값을 요소별로 구분하기
    - pandas = 구분한 값을 보기 편하게
    - tqdm = 얼마나 진행되었는지 보기 위해
    - random = 요청 보내는 시간을 불규칙하게 조절
    - time = 한번 요청을 보내고 잠시 대기 하기 위해

 

네이버 영화평 corpus 가 이미 존재한다.

(ref. github.com/e9t/nsmc)

네이버 영화평 설명

총 20만개로 다들 이걸로 모델도 만들고, 감성분석도 수행하지만...

나는 데이터가 좀 더 많이 있었으면 좋겠다고 생각했다.

 

그래서 다들 하는 것처럼 수집을 해보기로 했다.

 

데이터를 가져올 url은 여기다.

url : https://movie.naver.com/movie/point/af/list.nhn?&page=1

 

평점 : 네이버 영화

네티즌 평점과 리뷰 정보 제공

movie.naver.com

네이버 영화 한줄 평

여기서 내가 원하는 부분은 저 한줄평을 전부 가져오는것이다.

예시로 request를 날려보자.

 

import requests
from bs4 import BeautifulSoup

url = 'https://movie.naver.com/movie/point/af/list.nhn?&page=1'
html = requests.get(url)
soup = BeautifulSoup(html.content, 'html.parser')
subject_list = soup.find_all('td',{'class':'title'})

날라온 결과값을 살펴보자.

원하는 부분과 그렇지 않은 부분이 모두 포함되어 있다.

이제 필요한 부분만 챙겨보자.  한줄 평만 가져가고 싶기 때문에 soup(tag).text 함수를 이용하고 싶지만

제목과 평점 등의 다른 결과까지 함께 가져오게 된다.

그렇기 때문에

subject_list[0].find('a').extract() #'a' tag 부분을 제거
subject_list[0].find('div').extract() #'div' tag 부분을 제거
subject_list[0].text # 이제 text 추출

결과는 

원하는 부분으로 좁혀졌다. 이제 필요없는 문자를 제거하자.

필요없는 문자들이 너무 많기 때문에 제거해준다.

 

필요없는 부분을 제거

대충 만드느라 replace문이 거지같지만 이걸 읽는 분들은 더 이쁘고 아름답게 제거 할것이라 믿는다.

그럼 이제 page 1개가 아니라 될 수 있는 만큼 가져와보자

 

import pandas as pd
import time

page_num = 1
review_list = []
url = 'https://movie.naver.com/movie/point/af/list.nhn?&page={}' #page 에 따라 바뀌어야 함
num = 1 # 가져온 한줄평이 몇개나 됐는지 카운트
while(True):
    try:
        req_url = url.format(page_num) #page 숫자 넣어주기
        page_num +=1 # 넣은 뒤엔 page_num 하나 더해주기
        
        #해당 page내 한줄평 가져오기
        html = requests.get(req_url)
        soup = BeautifulSoup(html.content, 'html.parser')
        temp_subject_list = soup.find_all('td',{'class':'title'})
        for subject in temp_subject_list:
            subject.find('a').extract()
            subject.find('div').extract()
            text = subject.text.replace("\n\n\n","").replace("\n\t","").replace('\t\t',"").replace("신고\n","")
            review_list.append(text)
		
        # 읭? 이건 무슨코드지? 싶은 부분은 아래 설명
        if(len(review_list) >= 10000):
            df = pd.DataFrame(review_list, columns=['review'])
            df.to_pickle('naver_review_small.pkl')
            break
        else:
            if(len(review_list) / 100 > num):
                print("제목수 : ",len(review_list))
                num+=1
        # 요청을 한꺼번에 너무 많이 하는 경우 비정상 접속으로 차단할 수 있기 때문에
        # 뜸들이기 0.5초
        time.sleep(0.5)
    except:
        print("error occured!")
        break

설명이 필요한 부분이 생겼다.

중간에 review_list 변수, 한줄평의 개수가 1만이 넘어가면 멈추고 pandas dataframe으로 저장하게 되어있다.

네이버 영화평의 경우 불러올 수 있는 page를 1000개로 고정시켜놓았다. 

 

page=1001일 경우 경고 창

한 페이지에 한줄 평 10개, page limit 1000개 도합 1만 개의 평 밖에 얻을 수 없다.

방법이 없을까? 있다. 

 

두번째 방법

전체 영화 한줄평으로 들어가는 것이 아닌, 한 영화에 대한 한줄평으로 들어가면 조금 더 많이 볼 수 있게 된다.

 

url : movie.naver.com/movie/point/af/list.nhn?st=mcode&sword=203288&target=after

 

평점 : 네이버 영화

네티즌 평점과 리뷰 정보 제공

movie.naver.com

영화 예시 댓글

한 영화에 대한 한줄평 역시 page 최대 1000개로 고정되어 있는건 동일하지만, 어벤져스 영화급이 아니라면

생각보다 1만 개의 한줄평을 넘는건 많지 않아보인다.

 

그렇다면 영화별로 접근을 할 수 있어야 한다.

전체 영화평과 특정 영화의 영화평 페이지 url에 힌트가 있다.

전체 영화평 url :         movie.naver.com/movie/point/af/list.nhn?&page=1

특정 영화 영화평 url :  movie.naver.com/movie/point/af/list.nhn?st=mcode&sword=203288&target=after

 

url parameter 부분으로 page 정보 대신 ?st=mcode&sword=203288&target=after 가 추가 되어있다.

그렇다면 저 부분을 추가하면 특정 영화로 접근가능할 것 같은데... 저 정보는 어디있지?

 

처음에 본 전체 영화평의 데이터 모습이다. 잘보면 영화 제목 a tag에 href 값으로 우리가 원하는 정보가 들어있는 것을 알 수 있다.

 

가져오기 예시

한줄 평 가져오기 로직이 수정되게 된다.

1. 영화 url을 전부 가져오기 (추가)

2. 가져온 url을 통해 한줄평 긁어오기 (기존과 동일)

 

먼저 1번 과정 코드

page_num = 1
movie_list = set()
url = 'https://movie.naver.com/movie/point/af/list.nhn?&page={}'
num = 1
while(True):
    try:
        req_url = url.format(page_num)
        page_num +=1
        html = requests.get(req_url)
        soup = BeautifulSoup(html.content, 'html.parser')
        temp_subject_list = soup.find_all('td',{'class':'title'})
        for subject in temp_subject_list:
            movie_url = subject.find('a').get('href') # 한줄평 대신 영화 url을 가져오기
            movie_list.add(movie_url)
            
        if(page_num >= 1000):
            break
        else: 
            if(len(movie_list) / 100 > num): # 몇개쯤 가져왔나 체크하기
                print("영화수 : ",len(movie_list))
                num+=1
        time.sleep(0.5)
    except:
        print("error occured!")
        break
        
df = pd.DataFrame(movie_list, columns=['movie'])
df.to_pickle('movie_url.pkl')

이렇게 구성한 url을 통해 데이터를 가져온 결과

네이버 영화 url

코드를 잘 살펴보면 movie_list 가 list 타입이 아닌 set 타입이다.

전체 한줄평의 경우 한 영화에 다른 사용자가 평을 남길 경우가 존재하기 때문에 중복을 배제하기 위한 조치 이다.

 

2번 과정 코드

df = pd.read_pickle('./movie_url.pkl') # url 저장된 pickle 파일 불러오기
movie_list = df['movie'] 
url = "https://movie.naver.com/movie/point/af/list.nhn" # 기본 url
for i, movie_url in enumerate(movie_list): #반복문을 순회하면서...
    try:
    	# "https://movie.naver.com/movie/point/af/list.nhn" + movie_url 
    	# url 을 붙혀 특정영화 한줄평으로 이동
    	target_url = url+movie_url
    	target_url += "&page={}" # page를 옮기기
    	page_num = 1
    	review_list = []

		# do something!
    except:
        print("error occured_2")
        break

#do something 부분을 채워 넣어야 하는데 이 부분은 앞서 만들어 보았다. 해당 부분을 채워넣은 코드로 다시 보자

 

df = pd.read_pickle('./movie_url.pkl')
movie_list = df['movie']
url = "https://movie.naver.com/movie/point/af/list.nhn"
for i, movie_url in enumerate(movie_list):
    if(i < 2092):
        continue
    try:
        target_url = url+movie_url
        target_url += "&page={}"
        page_num = 1
        review_list = []
        
        
        # do something
        
        # 마지막 페이지에 도달할 경우 계속 같은 페이지가 로딩 되기때문에
        # 지금 가져온 한줄평과 이전 한줄평이 같은 데이터인지 비교위해 선언
        pre_temp_subject_list = [] 
        num = 1
        while(True):
            try:
                req_url = target_url.format(page_num)
                page_num += 1
                html = requests.get(req_url)
                soup = BeautifulSoup(html.content, 'html.parser')
                temp_subject_list = soup.find_all('td',{'class':'title'})

                for subject in temp_subject_list:
#                     subject.find('a').extract()
#                     subject.find('div').extract()
                    score = subject.find('em').text
                    subject.find('a').extract()
                    subject.find('div').extract()
                    text = subject.text.replace("\n\n\n","").replace("\n\t","").replace('\t\t',"").replace("신고\n","")
                    if(text == '\n' or text == " "):
                        continue
                    review_list.append([text, score])

                if(page_num >= 1000): # page가 1000 이상일 경우 exit
                    break 
                elif(len(temp_subject_list) < 10): # 마지막 page 한줄평이 10개 안될경우 exit
                    break
                elif(pre_temp_subject_list == temp_subject_list): # 가져온 평과 이전 평이 같은 경우 exit
                    break
                else:
                    if(len(review_list) / 1000 > num):
                        num+=1
                pre_temp_subject_list = list(temp_subject_list)
                time.sleep(random.uniform(0.1,0.25)) # 0.1 ~ 0.25초 사이로 랜덤하게 쉬게 하기 위한 부분
            except Exception as e:
                print("error occured_1", e)
                break
        # 영화 하나가 완료될때 마다 해당 영화의 index 값으로 한줄평 dataframe으로 저장
        df = pd.DataFrame(review_list, columns = ['review','score'])
        df.to_pickle('E:/movie_review/movie_{}_pkl'.format(i))
        
        # do something end
        
    except:
        print("error occured_2")
        break

 

나의 경우 약 2천개의 영화를 긁어오는데 4~5일 정도 걸린것 같다.

 

결과

영화 약 2천개에서 총 한줄평 약 7백만개의 데이터를 얻을 수 있었다.

 

아쉬운점

- 시간이 너무 오래 걸렸다.

  -> 빠르게 하기 위해선 여러개의 프로세스를 띄워 동작하게 만들면 가능하지 않을까?

 

- 전체 영화에 대해 수행 한 것이 아니다.

  -> 네이버 영화엔 년도별 영화 정리 페이지가 존재했었던 것 같은데 해당 페이지를 통해 더 많은 정보 조회가 가능할 것 같다.

 

소감

- 나름 데이터 수집으로 수행했지만 이렇게 많이 될줄 몰랐다. 딥러닝으로 활용하려면 더 많아야 한다지만 개인적 실험 용도로는 충분할 것 같다.

 

댓글 환영

반응형

댓글