Warning) 아래 코드들은 버전이 바뀌어 정상적으로 작동되지 않을 수도 있습니다. 따로 버전을 명시하지 않았기 때문에 에러가 발생되면 조금씩 수정해 나가면서 사용하시길 바랍니다. 또한, 형편없는 저의 실력으로 코드들이 다소 비효율적일 수 있음을 미리 말씀드립니다. 우연히 이 글을 보게 되신 분들은 참고해주시기 바랍니다. 

naver_shopping_review_scraping.ipynb
0.09MB

STEP 2. 네이버 쇼핑 리뷰 데이터 스크래핑 준비

STEP2에서는 분석 대상인 리뷰 데이터를 수집할 수 있도록 웹스크래핑을 준비하겠습니다. 풀무원, CJ 비비고, 동원 F&B, 노브랜드 4개 브랜드의 8개 제품에 대한 리뷰 데이터를 스크래핑하였으며 1점부터 5점까지의 리뷰를 골고루 가져올 수 있도록 하였습니다. 

(1) Selenium 및 웹 드라이버 설치

먼저, 동적 스크래핑을 위해 Selenium을 설치하고 크롬 웹브라우저를 통해 데이터를 수집할 수 있도록 web driver을 설치하겠습니다. 

!pip install Selenium
!apt-get update
!apt install chromium-chromedriver
!cp /usr/lib/chromium-browser/chromedriver /usr/bin

import sys
sys.path.insert(0, '/usr/lib/chromium-browser/chromedriver')

(2) 필요한 라이브러리 import 및 chrome_options 설정

수집한 데이터를 데이터프레임 형식으로 정리하고 이를 csv파일로 저장하기 위해 pandas를 import하고 pandas 외에 추가적으로 필요한 라이브러리들을  import하겠습니다. 그리고 스크래핑 과정에서 불필요한 창 띄우기를 없애기 위해 웹브라우저에 대한 옵션들을 사전에 지정하겠습니다. 

#필요한 라이브러리 import
import time
import pandas as pd
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, ElementNotInteractableException, NoAlertPresentException, UnexpectedAlertPresentException
from selenium.webdriver.common.by import By
import random

#chrome 웹드라이버 옵션 지정
chrome_options = webdriver.ChromeOptions()

chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')

(3) page_review_scrap(): 해당 페이지 스크랩 함수 

본격적으로 스크래핑을 진행하는 함수를 정의하도록 하겠습니다. 총 2가지 함수를 정의하게 될텐데 첫 번째는 한 페이지에 대한 스크래핑을 진행하는 정적 스크래핑 함수입니다. page_review_scrap()는 한 페이지 내에 있는 20개의 리뷰를 수집하는 함수입니다. 네이버 쇼핑의 경우 아래 사진과 같이 1번, 2번, 3번 등 각 페이지에 20개씩의 리뷰가 있습니다. 따라서, 모든 리뷰를 계속 가져오기 위해서는 페이지를 넘겨가며 해당 페이지에 있는 리뷰를 긁어와야 합니다. 이를 위해 페이지 내 20개의 리뷰를 가져오는  page_review_scrap() 함수와 페이지를 넘겨가며 사전에 정의한 page_review_scrap()을 적용하는 shopping_review_scraping()함수를 각각 따로 정의해주었습니다. 

다시 위 사진을 보게 되면 네이버에서 제공하는 리뷰 데이터에는 평점, 쇼핑몰, 아이디, 날짜, 리뷰 내용, 사진 등의 정보가 담겨 있습니다. 이 중에서 쇼핑몰, 리뷰 내용, 평점 이렇게 3가지 정보만 수집해보도록 하겠습니다. 이후에 진행할 텍스트 분석에서는 리뷰 내용만을 사용하게 되기 때문에 쇼핑몰 정보와 평점 정보는 불필요한 정보이긴 하지만 혹시 모를 상황을 대비해 3가지 정보 모두 수집하였습니다. 

쇼핑몰, 리뷰 내용, 평점에 대한 각각의 XPATH를 확인하여 이에 대한 데이터를 각각의 리스트(mall_list, review_list, rating_list)에 넣어주고 이를 데이터프레임 형식으로 정리하여 최종 20개의 리뷰에 대한 스크래핑 결과를 가져오는 방식으로 진행하였습니다. 

def page_review_scrap(wd):

  try:
    alert = wd.switch_to.alert
    print(alert.text)
    alert.dismiss()
    time.sleep(random.uniform(1, 2))
  
  except:
    pass

  #수집할 데이터를 넣어줄 빈 데이터프레임과 리스트 지정
  review_df = pd.DataFrame()
  mall_list = []
  review_list = []
  rating_list = []
  
  #쇼핑몰, 리뷰 내용, 평점에 대한 각각의 XPATH를 통해 데이터를 수집
  malls = wd.find_elements(By.XPATH, '//*[@id="section_review"]/ul/li/div[1]/span[2]')
  mall_list += [mall.text for mall in malls]
  #print(mall_list)

  reviews = wd.find_elements(By.XPATH, '//*[@id="section_review"]/ul/li/div[2]/div/p')
  review_list += [review.text for review in reviews]
  #print(publication_list)

  ratings = wd.find_elements(By.XPATH, '//*[@id="section_review"]/ul/li/div[1]/span[1]')
  rating_list += [rating.text[-1] for rating in ratings]
  #print(review_list)
  
  #20개 리뷰에 대해 수집한 데이터를 데이터프레임 형식으로 변환
  new_review_df = pd.DataFrame({'Mall': mall_list,
                                'Reveiw': review_list,
                                'Rating' : rating_list})
  review_df = pd.concat([review_df, new_review_df], ignore_index = True)
  return review_df

(4) shopping_review_scraping: 쇼핑몰 리뷰 스크래핑 함수

앞서 정의한 page_review_scrap() 함수가 단일 페이지에 대한 정적 스크래핑 함수였다면 이제 정의하게 되는 shopping_review_scraping() 함수는 페이지를 이동하며 각 페이지에서 page_review_scrap()함수를 적용하는 동적 스크래핑 함수입니다. 이때, 페이지를 이동하기에 앞서 1점부터 5점까지의 리뷰를 골고루 가져오기 위해 각 평점에 대한 페이지로 먼저 이동하도록 하겠습니다. 만약, 각 평점 페이지로 이동하는 것을 따로 정의하지 않게 되면 5점 리뷰에 대해서만 데이터를 수집하게 되기 때문에 추후에 텍스트를 분석할 때 편향된 결과를 얻게 될 수 있습니다. 따라서, 긍정적인 리뷰, 부정적인 리뷰 모두를 분석할 수 있도록 다양한 평점 리뷰를 가져오는 프로세스를 적용해주어야 합니다. 

네이버 쇼핑에서 데이터를 수집할 때 유의해야 할 점 한 가지를 짚고 넘어가겠습니다. 네이버 쇼핑에서는 각 제품에 대한 리뷰를 2000개만 제공하고 있습니다. 따라서, 해당 제품에 대해 리뷰가 2000개 이상이더라도 소비자가 볼 수 있는 리뷰는 2000개로 한정되게 됩니다. 위 사진처럼 전체 리뷰 수가 44,477개이더라도 모든 리뷰를 다 확인할 수는 없다는 것입니다. 스크래핑 과정에서도 이러한 제약이 적용되어 마찬가지로 한 제품 당 2000개의 리뷰만 가져올 수 있게 됩니다. 아마 무분별한 웹크롤링으로 트래픽이 과부화되는 것을 막기 위해서 이러한 제약을 두는 것이 아닐까 생각됩니다. 저도 본격적으로 데이터 수집을 진행하기 전에는 이 부분에 대해 잘 인지하지 못했던 터라 계속 에러가 발생하는 것 때문에 고생하였습니다. 중간 중간 time.sleep()을 랜덤으로 주어가며 수정을 해보기도 했지만 해결이 되지 않다가 결국 수동으로 계속 페이지를 넘어가며 이와 같은 사실을 발견하였습니다. 여튼, 아쉽게도 모든 데이터를 가져오기는 어렵기 때문에 아래 코드에서도 페이지 이동을 100페이지로 제한을 두었습니다. 한 페이지에 20개의 리뷰가 있으니 100페이지면 우리가 가져올 수 있는 2000개 리뷰 제약에 딱 부합하게 됩니다.

def shopping_review_scraping(url, selectable_rate):
  wd = webdriver.Chrome('chromedriver', options = chrome_options)
  wd.implicitly_wait(3)

  # 해당 url로 이동
  wd.get(url)
  time.sleep(random.uniform(1, 2))

  total_review_df = pd.DataFrame()

  selectable_rate = [7-i for i in selectable_rate]
  for rating in selectable_rate:
    rating_xpath = f'//*[@id="section_review"]/div[2]/div[2]/ul/li[{rating}]'
    rating_bt = wd.find_element(By.XPATH, rating_xpath)
    rating_bt.click()
    print('\n****{}점 리뷰페이지로 이동****'.format(7-rating))
    time.sleep(random.uniform(1, 2))

    page_no = 0

    print("[스크래핑 시작]")
    while page_no <= 100:
      try:
        for num in range(1, 12):
          next_xpath = f'//*[@id="section_review"]/div[3]/a[{num}]'
          review_bt = wd.find_element(By.XPATH, next_xpath)
          review_bt.click()
          time.sleep(random.uniform(1, 2))
          page_no += 1
          print('[{}페이지로 이동]'.format(page_no)) #해당 페이지의 리뷰 스크래핑이 완료되면 다음 페이지로 이동
          review_df = page_review_scrap(wd)
          total_review_df = pd.concat([total_review_df,review_df], ignore_index=True)

        while page_no <= 100:
          page_no += 1
          for num in range(3, 13):
            next_xpath = f'//*[@id="section_review"]/div[3]/a[{num}]'
            review_bt = wd.find_element(By.XPATH, next_xpath)
            review_bt.click()
            time.sleep(random.uniform(1, 2))
            page_no += 1
            print('[{}페이지로 이동]'.format(page_no))
            review_df = page_review_scrap(wd)
            total_review_df = pd.concat([total_review_df,review_df], ignore_index=True)

      except ElementNotInteractableException as ex:
        print(ex)
        print("[모든 스크래핑이 완료되었습니다.]")
        break

      except NoSuchElementException as ex:
        review_df = page_review_scrap(wd)
        total_review_df = pd.concat([total_review_df,review_df], ignore_index=True)
        print("[모든 스크래핑이 완료되었습니다.]")
        break
  
  return total_review_df

이상으로 STEP2 리뷰 데이터를 수집하기 위한 사전 준비를 마치도록 하겠습니다. 다음 스텝에서는 지금까지 준비한 함수를 활용해서 4개 브랜드의 8개 제품에 대한 스크래핑을 본격적으로 진행하도록 하겠습니다. 

 

+ Recent posts