본문 바로가기

문돌이 존버/코딩연습

파이썬 selenium을 이용한 댓글 크롤링, feat. 네이버 tv

반응형

크롤링은 안하다 보면 계속 잊어버리는 것 같더군요... 복습 차원에서 연습을 해보았고, 코드를 공유해보려고 합니다. 그전에 selenium에 대해서 다시 한 번 살펴봤고, 함수들 역시 계속 잊어버려 함수(메서드)들을 아래와 같이 정리해봤는데요.

(2023.09.04 수정) 셀레니움 버전이 업그레이드 되면서 메서드 방식에도 변화가 생겼습니다. 일단, 아래 메서드들은 이제 더 이상 사용할 수가 없습니다.

조건을 만족하는 모든 elements 리턴 조건을 만족하는 첫 번째 element 리턴
driver.find_elements_by_css_selector driver.find_element_by_css_selector
driver.find_elements_by_class_name driver.find_element_by_class_name
driver.find_elements_by_id driver.find_element_by_id
driver.find_elements_by_link_text driver.find_element_by_link_text
driver.find_elements_by_name driver.find_element_by_name
driver.find_elements_by_partial_link_text driver.find_element_by_partial_link_text
driver.find_elements_by_tag_name driver.find_element_by_tag_name
driver.find_elements_by_xpath driver.find_element_by_xpath

대신 메서드 방식을 조금만 수정해주면 되는데요. find_element(s) 까지는 동일하고 그 뒤의 속성을 파라미터로 넘기면 됩니다.

find_element(By.ID)
find_element(By.CLASS_NAME)
find_element(By.XPATH)

새롭게 생긴 By 라이브러리는 아래 명령어로 불러옵니다.

from selenium.webdriver.common.by import By

사실 위의 함수에 대한 자세한 내용은 공식 홈페이지를 방문하면 나와있습니다. 그나마 자주 쓰는 것은 아래 리스트가 아닐까 싶은데요.

1. driver.find_elements_by_css_selector -> driver.find_elements(By.CSS_SELECTOR)
2. driver.find_elements_by_xpath -> driver.find_elements(By.XPATH)
3. driver.find_elements_by_class_name -> driver.find_elements(By.CLASS_NAME)
4. driver.find_elements_by_tag_name -> driver.find_elements(By.TAG_NAME)
5. driver.find_elements_by_id -> driver.find_elements(By.ID)

참고로 위의 함수를 사용해서 나온 형태는 selenium의 Element 객체입니다. 이 객체에 어떤 함수가 포함되었는지 일일이 공식 홈페이지를 찾기 귀찮다면 아래 코드로도 확인할 수 있습니다.

dir(Element) 
from selenium import webdriver
import re
import pandas as pd
import time

(2023.09.04 수정) 크롬 드라이버 사용 방법 역시 수정되었습니다. 이전엔 executable_path 파라미터를 설정했는데 이젠 아무것도 기입하지 않아도 됩니다. 관련해서 최근에 글을 올렸으니 같이 참고하면 좋을 것 같습니다.

url = 'https://tv.naver.com/tvchosun.mistertrot/clips'
driver = webdriver.Chrome()
driver.implicitly_wait(10) # 페이지가 로딩되는데 10초까지 기다린다 -> 그 전에 로딩이 완료되면 바로 다음 코드를 수행한다
# 전역 변수로 한 번만 선언하면 이후에 자동 적용됨
driver.get(url)

video_url = [] # 각 동영상 url 저장할 리스트

last_height = driver.execute_script("return document.body.scrollHeight")      
cnt = 0 # while 문 탈출용

while cnt < 1:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 창 높이까지 스크롤바 내리기 
    time.sleep(1)
    elements = driver.find_element(By.XPATH, '//*[@id="cds_flick"]/div/div[3]/div/div/div/div[2]/div[3]/a')
    driver.execute_script("arguments[0].click();", elements) # "아래 화살표" 버튼 클릭 명령(자바스크립트 명령어)
    # (=driver.send_keys('\n')) -> 해당 링크, 명령어에 엔터를 치는 것
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight-50);") # 맨 마지막까지 스크롤하면 바로 아래 리스트가
    # 뜨지 않는 경우가 있는데, 창을 조금 올려서 리스트가 로딩될 수 있도록 함 
    time.sleep(2)
    
    whole = driver.find_element(By.XPATH, '//*[@id="cds_flick"]/div/div[3]/div/div/div/div[2]/div[2]')
    video_lst = whole.find_elements(By.TAG_NAME, 'dt > a')
    
    for video in video_lst:
        video_url.append(video.get_attribute('href'))
        
    cnt += 1
    
driver.quit()

위 코드로는 모든 동영상의 링크를 가져오는 것입니다. 이제 각 링크에 접속해서 댓글과 관련된 정보를 모두 가져와야겠죠. 그런데 네이버tv를 방문해보신 분들은 알겠지만 댓글이 자바스크립트 렌더링으로 이루어져 있어서 페이지가 바뀌지 않습니다. 더 문제가 되는 것은 xpath를 살펴보면 규칙적이지 않다는 것입니다. 

최종적으로 댓글 수집 과정을 모두 4단계로 나눌 수밖에 없었습니다. 이렇게 한 이유는 자세하게 설명하진 않겠지만 간단하게 말하면 xpath의 "구분자"라고 할 수 있는 숫자가 1~6페이지에서는 뒤죽박죽(?)이었습니다. 직접 링크를 방문해서 F12를 통해 확인해보시면 제 고충을 이해할 수 있을 겁니다.. ㅠ

1. 1번째 페이지 댓글
2. 2번째 페이지 댓글
3. 3번째~6번째 페이지 댓글
4. 7번째~끝까지 댓글
# 네이버 tv 웹페이지
# video_url에 있는 모든 동영상을 실현하고 싶은 경우 for문 사용
"""
driver = webdriver.Chrome()
driver.implicitly_wait(10) 
for url in video_url:
    driver.get(url)
    아래 코드와 동일
"""

# 샘플 테스트
url = 'https://tv.naver.com/v/12711849/list/571830'
driver = webdriver.Chrome()
driver.implicitly_wait(10) 
driver.get(url)

# 첫 페이지 댓글 
content_lst1 = []
comments = driver.find_elements(By.XPATH, '//*[@id="cbox_module_wai_u_cbox_content_wrap_tabpanel"]/ul')
body = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_text_wrap') # 실제 댓글 내용
reaction = comments[0].find_elementse(By.CLASS_NAME, 'u_cbox_recomm_set') # 좋아요, 싫어요 모음
date = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_info_base') # 댓글 작성 시간

for item in zip(body, reaction, date):
    content_lst1.append(
    [
        item[0].text,
        item[1].text.split('\n')[2],
        item[1].text.split('\n')[4],
        item[2].text.split('\n')[0]
    ])
    
# 2페이지로 넘어가고 댓글 수집
content_lst2 = []
elements = driver.find_element(By.XPATH, '//*[@id="cbox_module"]/div/div[8]/div/a[1]')
driver.execute_script("arguments[0].click();", elements)
time.sleep(2)

comments = driver.find_elements(By.XPATH, '//*[@id="cbox_module_wai_u_cbox_content_wrap_tabpanel"]/ul')
body = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_text_wrap')
reaction = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_recomm_set')
date = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_info_base')

for item in zip(body, reaction, date):
    content_lst2.append(
    [
        item[0].text,
        item[1].text.split('\n')[2],
        item[1].text.split('\n')[4],
        item[2].text.split('\n')[0]
    ])

# 3~6페이지 댓글
content_lst3 = []
for l in range(3, 6+1):
    elements = driver.find_element(By.XPATH, f'//*[@id="cbox_module"]/div/div[8]/div/a[{l}]')
    driver.execute_script("arguments[0].click();", elements)
    time.sleep(2)
    
    comments = driver.find_elements(By.XPATH, '//*[@id="cbox_module_wai_u_cbox_content_wrap_tabpanel"]/ul')
    body = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_text_wrap')
    reaction = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_recomm_set')
    date = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_info_base')
    
    for item in zip(body, reaction, date):
        content_lst3.append(
        [
            item[0].text,
            item[1].text.split('\n')[2],
            item[1].text.split('\n')[4],
            item[2].text.split('\n')[0]
        ])

# 7페이지 ~ 끝까지  
content_lst4 = []
while True:
    for i in range(3, 7+1):
        elements = driver.find_element(By.XPATH, f'//*[@id="cbox_module"]/div/div[8]/div/a[{i}]')
        driver.execute_script("arguments[0].click();", elements)
        time.sleep(3)

    comments = driver.find_elements(By.XPATH, '//*[@id="cbox_module_wai_u_cbox_content_wrap_tabpanel"]/ul')
    body = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_text_wrap')
    reaction = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_recomm_set')
    date = comments[0].find_elements(By.CLASS_NAME, 'u_cbox_info_base')

    for item in zip(body, reaction, date):
        content_lst4.append(
        [
            item[0].text,
            item[1].text.split('\n')[2],
            item[1].text.split('\n')[4],
            item[2].text.split('\n')[0]
        ])

# 4가지 리스트 합쳐주기 
content_lst = content_lst1 + content_lst2 + content_lst3 + content_lst4
# 데이터프레임 생성
content_info = pd.DataFrame(content_lst, columns=['comment', 'likes', 'dislikes', 'date'])
driver.quit()

아래는 content_info 데이터프레임의 일부입니다. 

728x90
반응형