빅데이터/BeautifulSoup

BueatifulSoup 기초 및 활용 하기 03 - 네이버 영화 스크래핑

H-V 2021. 12. 15. 15:25

유투브 '이수안컴퓨터연구소' 참조

 

  • 네이벼 영화로 들어가 평점-리뷰 부분을 스크래핑 할 예정

 

 

* 기본 세팅

from bs4 import BeautifulSoup as bs
import requests
import pandas as pd
from urllib.parse import quote

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36'}

▶ 이번 강의에서는 'from urllib.parse import quote' 라는것을 처음 써본다 

 

 

 

01 URL 분석

  • 하나의 평점-리뷰를 클릭하면 이런 화면으로 이동하면서 빨간색 박스부분의 주소가 추가 된다
  • 특히 'sword=208077' 부분이 변경이되면 영화가 바뀌면서 평점-리뷰도 바뀌는걸 알 수 있다

 

- URL 만들기

def get_movie_reviews(mcode):
    url = "https://movie.naver.com/movie/point/af/list.naver?st=mcode&sword="+ str(mode) + "&target=after"
  • 이렇게 세팅을하면 어떠한 숫자가 파라미터(mcode)로 들어가면 'str(mode)'에 반영되고 각 숫자에 맞는 영화를 찾아 올 수 있다

 

 

02 사이트 분석

  • 들고 오고자하는 데이터를 분석하자

 

 

1차 코드

def get_movie_reviews(mcode, page_num=12):
    url = "https://movie.naver.com/movie/point/af/list.naver?st=mcode&sword="+ str(mode) + "&target=after"
    
    for _ in range(0, page_num): # 페이지를 위한 포문
        movie_page = requests.get(url, headers=headers)
        movie_page_soup = bs(movie_page.content, 'html.parser')
        
        review_list = movie_page_soup.find_all("td", {"class","title"})
        for review in review_list: #리뷰를 위한 포문
            title = review.find('a', {'class':'movie color_b'}).get_text()
            score = review.find('em').get_text()
  1. 페이지를 돌리기 위한 포문이 필요하고 이 페이지 포문을 통해서 'requests'와 'beautifulsoup'을 세팅 한다
  2. 리뷰-평점이 포함되어있는 전체 태그 <td class="title">를 들고오기 위한 find_all
  3. find_all은 모든 데이터가 리스트 형식이니 포문을 돌리면서 <td> 태그안의 태그들을 들고오면 된다.
  4. 타이틀/평점은 직관적으로 나와있기때문에 find로 돌리면 된다

 

2차 코드 (리뷰가 어렵다)

  • 강의에는 'report'를 찾아 진행이 가능하지만 현재 날짜로는 'onclick' 속성으로 변경 되었다. 다르게 처리해야한다. 
  • <br>은 태그라고 볼 수 없다. <br>은 한칸 띄우기 용이기때문에 찾을 수 가 없다
  • 그렇다면 저 내용이 들어가 있는 <a>태그를 들고와서 리뷰 내용 부분을 'split' 해줘야 한다
def get_movie_reviews(mcode, page_num=12):
    url = "https://movie.naver.com/movie/point/af/list.naver?st=mcode&sword="+ str(mcode) + "&target=after"
    
    for _ in range(0, page_num): # 페이지를 위한 포문
        
        #DF만들기
        movie_review_df = pd.DataFrame(columns=("Title", "Score", "Review"))
        idx = 0 #DF 내용 인덱스
        
        movie_page = requests.get(url, headers=headers)
        movie_page_soup = bs(movie_page.content, 'html.parser')
        
        review_list = movie_page_soup.find_all("td", {"class","title"})
        for review in review_list: #리뷰를 위한 포문
            title = review.find('a', {'class':'movie color_b'}).get_text()
            score = review.find('em').get_text()
            
            review_text = review.find_all('a')[1]['onclick']
            review_text = review_text.replace("report(", "").split("', '")[2]

            movie_review_df.loc[idx] = [title, score, review_text] #한칸씩 내려가면서 내용 채움
            idx += 1 #칸 이동용 인덱스
            
            print("#", end=" ") #진행사항 확인
  1. 'review_text'를 통해 <br>이 아닌 유의미한 정보를 가진 <a>태그 모두를 들고옴
  2. 그 중 [1]번째 <a> 태그를 들고 오면서 'onclick' 속성을 들고 옴
  3. 거기서 "report(" 부분을 공백으로 처리
  4. 다시 스플릿을 하면 다음으로 변경 됨
    onclick="dlxo****', '1LKO8L8ZijUCDoiPeyD9pfXWBCjvQaia58AKt903mhQ=', '처음부터 함께해온 스파이더맨 다시 그 시작을 알리는 영화였다엔딩 크레딧 뒤에 쿠키 하나 더 있습니다', '17846032', 'point_after');"
    0 1 [2] 부분을 들고 옴
  5. 참고로 nth로 하면 인덱싱[1]은 1부터 시작, 배열기준이면 [1]은 2번째를 가르킨다

 

 

 

3차 코드 (페이지 이동)

페이지 이동을 눌려보면 이런 형태로 반복 된다
하지만 10페이지 이후 다음 페이지의 형태가 다르다&amp;amp;amp;amp;amp;nbsp;

  • 페이지 이동은 다음 버튼으로 하는게 가장 적절하다
  • 또한 리뷰가 끝나는 지점이 오면 오류가 걸린다. 이에대한 처리도 필요하다
def get_movie_reviews(mcode, page_num=10):
    
    movie_review_df = pd.DataFrame(columns=("Title", "Score", "Review"))
    idx = 0 #DF 내용 인덱스
    url = "https://movie.naver.com/movie/point/af/list.naver?st=mcode&sword="+ str(mcode) + "&target=after"
    
    for _ in range(0, page_num): # 페이지를 위한 포문
        
        #DF만들기
  
        
        movie_page = requests.get(url, headers=headers)
        movie_page_soup = bs(movie_page.content, 'html.parser')
        
        review_list = movie_page_soup.find_all("td", {"class","title"})
#         print(len(review_list))
        
        for review in review_list: #리뷰를 위한 포문
            title = review.find('a', {'class':'movie color_b'}).get_text()
            score = review.find('em').get_text()
            
            review_text = review.find_all('a')[1]['onclick']
            review_text = review_text.replace("report(", "").split("', '")[2]

            movie_review_df.loc[idx] = [title, score, review_text] #한칸씩 내려가면서 내용 채움
#             print(movie_review_df.loc[idx])
            idx += 1 #칸 이동용 인덱스
            
#             print("#", end=" ") #진행사항 확인
            
        try: #리뷰가 끝나는 지점 처리
            url = "https://movie.naver.com" + movie_page_soup.find('a', {'class':'pg_next'}).get('href')
            print(url)
        except:
            break
            
    return movie_review_df

 

 

03 DF 만들기

*원하는 영화 번호를 넣으면 된다 

movie_review_df = get_movie_reviews(208077, 1) *(영화번호, 페이징 수)
movie_review_df

 

※ 인덱싱 위치때문에 마지막에 해맸다. 인덱싱이 어디서 되는지 또 어디서 초기화가 되는지 잘 봐야 한다.
    앞으로는 모든 문장 마다 실행하고 진행을 해야겠다!

 

 

 

 

 

04 현재 상영작으로 파싱 해보기

 

 


1) 페이지 분석

명확해 보인다. &amp;amp;amp;amp;amp;lt;select&amp;amp;amp;amp;amp;gt; + &amp;amp;amp;amp;amp;lt;option&amp;amp;amp;amp;amp;gt; 을 손보면 될 것 같다

▶ <option> 태그를 보면 영화의 번호와 영화 제목이 같이 들어 있는걸 볼 수 있다. 이를 이용 하자!

 

1차 코드

url = "https://movie.naver.com/movie/point/af/list.naver"
naver_movie = requests.get(url, headers=headers)
soup = bs(naver_movie.content, 'html.parser')

select = soup.find('select', {'id':'current_movie'})

movies = select.find_all('option')

# print(movies)

movies_dict = {}
for movie in movies[1:]:
    movies_dict[movie.get('value')] = movie.get_text()
  1. <select> 태그를 들고오면 버튼의 모든 내용이 들어 있다
  2. 딕셔너리 변수를 만든다 (키:밸류 로 쉽게 꺼내고 쓸 수 있도록)
  3. [0]<option> 은 필요없으므로 포문을 [1]번 부터 돌도록 한다
  4. 'value' 가 영화 번호를 들고 있으므로 딕셔너리의 키로 들어가고 영화 타이틀인 텍스트는 딕셔너리의 밸류로 들어가도록 한다

 

2차 코드

url = "https://movie.naver.com/movie/point/af/list.naver"
naver_movie = requests.get(url, headers=headers)
soup = bs(naver_movie.content, 'html.parser')

select = soup.find('select', {'id':'current_movie'})

movies = select.find_all('option')

# print(movies)

movies_dict = {}
for movie in movies[1:]:
    movies_dict[movie.get('value')] = movie.get_text()

movie_revie_df = pd.DataFrame(columns=("Title", "Score", "Review"))
for mcode in movies_dict:
    df = get_movie_reviews(mcode, 1)
    movie_review_df = pd.concat([movie_review_df, df])
    
movie_review_df
  1. 원래 만들어 놓았던 함수에 이제 대입만 하면 된다
  2. 데이터 프레임을 선언한 뒤
  3. movies_dict에 mcode를 뺄수 있으니 그대로 빼와서 넣는다 (mcode, 페이징번호)
  4. .concat을 활용하여 각기 다른 DF를 합쳐 준다

▶ 딕셔너리를 포문으로 쓸 경우 키값이 바로 온다는것을 기억 하자!

 

 

 

 

번외

- 이번에는 혼자서 네티즌 리뷰를 해볼 예정!

 

* 기본 세팅

from bs4 import BeautifulSoup as bs
import requests
import pandas as pd
from urllib.parse import quote

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36'}

 

1) 1차 영화 제목 뽑기

  • 역시 강의만듣고 넘어가면 절대로 안된다. 직접해봐야 어디가 문제인지 나온다.
def get_movie_reviews(mcode, page_num=10):
	url = "https://movie.naver.com/movie/board/review/list.naver?st=code&sword="+ str(mcode) + "&target=after"
    
        for i in range(0, page_num):
        
        movie_page = requests.get(url, headers=headers)
        movie_page_soup = bs(movie_page.content, 'html.parser')
        
        movie_list = movie_page_soup.find_all('div', {"class":"choice_movie_info"})
  1. 강의를 따라만 하니까 타이틀 들고 오는것 부터 막혔다 그 이유가 네티즌 리뷰 첫페이지의 타이틀 내용과 스크래핑시에 진행되는 타이틀 내용이 영화 번호를 받아서 바뀐다는거를 전혀 인지를 못했다. 만약 강의만 듣고 넘어갔으면 절대로 몰랐을 문제
  2. 즉 다시말해 아래 두 페이지는 다르다는것!
    번호를 받아서 스크래핑이 이동하여 진행되는데 이때 가지고 있는 속성이 다르다. 이것때문에 계속해서 위의 사진 태그로 진행을하니 'none'을 내 뱉었다! 어디서 스크래핑이 진행되는지 알고 있어야 한다!
    ▶ 빈칸이나 None 리턴시에는 url 이 정확한지 꼭 확인하자!

 

2) 타이틀

 

 

3) 작성자 및 날짜

- 굉장히 어려웠다. 특이 작성자 및 날짜 부분에 <br> 및 엄청난 공백때문에 초보자에게는 멘붕이었다

이렇게 나오는것을 볼 수 있다.

- 여기서 많이 해맸다. 아래와 같이 해결이 가능 하다.

for author in author_lists:
    aa = author.find('a').get_text() # 작성자
    bb = author.get_text().strip().split('\n')[-1].strip() #날짜

 

 

▶ 초보자에게는 정말 어려운 고난이도였다. 직접 해보는데 1주일이 걸렸다.. 글을 1주일 만에 다시 쓴다..

  • 완성을 다 했는데도 불구하고 초보자인 내가 해결할 수 없는 부분에서 막혔다. 분명히 똑바로 소스를 다 가져왔는데도 페이지당 1개의 소스만 가져오고 바로 그다음 페이지로 넘어가는 오류가 계속 해서 났다
  • 이 오류의 문제는 바로 아래와 같다

테이블로 전체로 받아버리니 결과의 길이가 1개로 나오는 오류가 계속해서 나왔었다

다음 코드를 보면

def get_movie_reviews(mcode, page_num=10):
    
    movie_review_df = pd.DataFrame(columns = ("Movie Title", "Comment", "Writer", "Date", "Star", "Thums-Up", "Veiw"))
    idx = 0
    url = "https://movie.naver.com/movie/board/review/list.naver?st=code&sword="+ str(mcode) + "&target=after"

    for _ in range(0, page_num):

        movie_page = requests.get(url, headers=headers)
        movie_page_soup = bs(movie_page.content, 'html.parser')
        
        
        
        title = movie_page_soup.find('span',{"class":"choice_txt"}).get_text() #제목
    
        info_list = movie_page_soup.find_all('table', {"class":"list_table_1 list_h48"})
  • 'table' 자체를 들고와버려서 뽑고자하는 소스가 한줄씩 돌지않고 한줄이 곧 한 페이지가 되도록 처리가 되버린것

 

 

▶ 완성 코드를 보면!

from bs4 import BeautifulSoup as bs
import requests
import pandas as pd
from urllib.parse import quote

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36'}



def get_movie_reviews(mcode, page_num=10):
    
    movie_review_df = pd.DataFrame(columns = ("Movie Title", "Comment", "Writer", "Date", "Star", "Thums-Up", "Veiw"))
    idx = 0
    url = "https://movie.naver.com/movie/board/review/list.naver?st=code&sword="+ str(mcode) + "&target=after"

    for _ in range(0, page_num):

        movie_page = requests.get(url, headers=headers)
        movie_page_soup = bs(movie_page.content, 'html.parser')
        
        
        title = movie_page_soup.find('span',{"class":"choice_txt"}).get_text() #제목
    
        info_list = movie_page_soup.find('table', {"class":"list_table_1 list_h48"}).find("tbody").findAll("tr", recursive=True)
#         print(len(info_list))

        
        for info in info_list: #리뷰 리스트
#             print(info)
            comment = info.find('td', {"class":"title"}).get_text().strip() #코멘트
            author = info.find('td',{"class":"author num"}).find('a').get_text()
            date = info.find('td', {"class":"author num"}).get_text().strip().split('\n')[-1].strip() #날짜
            star_list = info.select("div.point_type_1 > div.mask > img")
            star = star_list[0].attrs['alt'] #별점
            recommend = info.find('td', {"class":"num c_ff4200"}).get_text().strip()#추천
            view = info.find_all('td', {"class":"num"})[2].get_text()#조회.
            print(view)

            
 
            movie_review_df.loc[idx] = [title, comment, author, date, star, recommend, view]
            idx += 1
            print("#",end=" ")
        
        try: #리뷰가 끝나는 지점 처리
            url = "https://movie.naver.com" + movie_page_soup.find('a', {'class':'pg_next'}).get('href')
            print(url)
        except:
            break
            
    return movie_review_df
 
 
movie_review_df = get_movie_reviews(208077, 1)
movie_review_df

 

  1. 가장 중요 포인트는
    'info_list = movie_page_soup.find('table', {"class":"list_table_1 list_h48"}).find("tbody").findAll("tr", recursive=True)'
    이렇게 하위 태그들에 어떻게 접근하는지가 중요하다
  2. 나머지는 소스값이 나와있는데로 접근만 하면 된다