- 제출일: 2024년 N월 N일 요일
- 제출자: 배정환 인턴
세부 과제
- n_scroll 변수를 활용하여 스크롤 다운 5번
- 스크롤 다운 함수를 만든다
- Pandas를 이용한 데이터프레임 저장
- columns:
[UserID, 댓글텍스트, 좋아요 수]- 모든 For문이 끝난 후 URL에서 video_id 추출해서 videoID column 추가
- csv로 저장
- 전체 함수 만들기
- URL만 입력하면 바로 저장될 수 있도록 함수를 만든다.
목표
- 스크롤하면서 로딩된 댓글들을 Selenium 크롤링을 이용해 수집하기
팁
크롬 창 생성 및 유튜브 주소로 접속하기
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import pandas as pd# 크롬 창 열기
options = Options()
driver = webdriver.Chrome(options = options)
# 창모드 전체화면으로 크기 늘리기
driver.maximize_window()
# 로딩시간
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
# 유튜브 주소 접속하기
url = "https://www.youtube.com/watch?v=0BWScn_OWPk"
driver.get(url)
# 유튜브 로딩시간
time.sleep(5)
# # 동영상 일시정지를 하여 로딩되는 속도 늘리기
# ActionChains(driver).send_keys('k').perform()과제 1. n_scroll 변수를 활용하여 스크롤 내리기 5번
- 스크롤 다운 함수를 만든다.
# n_scroll variable 만들기
n_scroll = 5
# 스크롤 다운 함수 만들기
def scroll_down(n_scroll):
"""정해진 횟수의 스크롤을 내리는 함수
Args:
n_scroll (integer): 스크롤 하는 횟수
"""
# 첫번째 스크롤 방법
last_height = driver.execute_script("return document.documentElement.scrollHeight")
for i in range(n_scroll):
# 끝까지 스크롤 내리기
driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")
time.sleep(2)
# 스크롤 내린 후 스크롤 높이 다시 가져옴
new_height = driver.execute_script("return document.documentElement.scrollHeight")
if new_height == last_height:
break
last_height = new_height
# 두번째 스크롤 방법 -> page 전체가 내려가는 것이 아니라 스크롤바 크기만큼 내려간다.
# for _ in range(n_scroll):
# # JavaScript를 사용하여 페이지 스크롤
# driver.execute_script("window.scrollBy(0, window.innerHeight);")
# # 각 스크롤 후 약간의 지연을 추가하여 페이지가 로드될 시간을 줍니다.
# time.sleep(1)
# wait = WebDriverWait(driver, 10)
# wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
# 세번째 스크롤 방법
# container=driver.find_element(By.TAG_NAME, "html")
# before_top = driver.execute_script("return arguments[0].scrollTop", container)
# for i in range(n_scroll):
# time.sleep(3)
# driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", container)
# after_top = driver.execute_script("return arguments[0].scrollTop", container)
# before_top = after_top
# 모든 스크롤 내리는 방법에 대한 문제점
# - 댓글이 로딩이 되지 않는다.
# - 스크롤을 위로 올려서 처음 댓글부터 떠야 그다음 댓글들이 나타난다.
# 해결책1 ❌
# - 스크롤을 맨 위로 올린다면 댓글들이 로딩이 되면서 한페이지에 스크롤 5번한 댓글들의 데이터를 잡을 수 있을거 같다. -> 스크롤 한번한 댓글들의 데이터만 얻을 수 있었다.
# driver.execute_script("window.scrollTo(0,0);")
# time.sleep(2)
# 해결책2 - 페이지의 스크롤을 관련영상 5번째까지 올리기 -> 첫 스크롤까지만 데이터가 받아짐
related_video = driver.find_elements(By.CSS_SELECTOR, '#dismissible')[5]
driver.execute_script("arguments[0].scrollIntoView(true);", related_video)
# 해결책2.1 - 스크롤 한번 더 내린다. 이번에 스크롤 길이만큼 내리며
for _ in range(10):
time.sleep(5)
# JavaScript를 사용하여 페이지 스크롤
driver.execute_script("window.scrollBy(0, window.innerHeight);")
# 댓글이 로드될 때까지 기다리기
comments_section = WebDriverWait(driver, 10)
comments_section.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, '#contents')))
scroll_down(n_scroll)-
스크롤을 로딩된 페이지 끝까지 5번 내리니 댓글 로딩속도가 무한정 걸릴 정도로 로딩이 안된다.
-
첫번째 해결책
- 스크롤을 맨위로 올렸더니 댓글들의 로딩이 끝나 있었다.
-
문제점
- 댓글 추출양이 스크롤 한번 내린것 보다 적은 데이터양을 추출했다.
-
두번째 해결책
- 페이지의 스크롤을 오른쪽에 위치한 관련 동영상 5번째 영상까지 올렸다.
- 첫번째까지 올리니 스크롤 맨 위로 올리는 것과 같은 결과가 나왔다.
- 첫번째 댓글까지 스크롤을 올리기는 댓글들이 로딩이 안되면서 첫번째 댓글을 찾지 못해 오류가 났다.
-
세번째 해결책
- 두번째 해결책에서 더 나아간 해결책이다.
- 페이지의 스크롤을 관련 동영상 5번째 영상까지 올리고 스크롤을 한번 더 내린다.
- 로딩된 페이지의 끝까지가 아니라 스크롤 바과 같은 크기로 내렸다.
- 스크롤 바와 같은 크기로 내리면서 대기 시간을 주어 로딩되는 시간을 벌었다.
- 스크롤 바와 같은 크기로 내려가면서 원래 원하던 데이터 양까지 추출할 수 없었다.
- 로딩된 페이지를 5번 스크롤 내리기 한 댓글 개수와 스크롤 바 크기만큼 내려가면서 생기는 데이터 개수차이를 줄이기 위해 스크롤 바와 같은 크기인 것을 횟수를 늘렸다.
-
근본적인 문제점
- 스크롤을 로딩된 페이지 끝까지 5번 내리고 다시 올린 뒤 스크롤 바 크키만큼 내린다는 것은
- 이로 인해 코드의 효율성이 많이 떨어졌다.
- 결국에 데이터를 끌어 모을 때는 두번째로 스크롤 내리는 것 하나만으로도 가능하다.
- 하지만, 과제에서 원한건 스크롤을 5번 내린 뒤 데이터 추출이니 그대로 한다.
- 스크롤을 로딩된 페이지 끝까지 5번 내리고 다시 올린 뒤 스크롤 바 크키만큼 내린다는 것은
최종 해결 방법
-
댓글들을 크롤링할때 댓글창이 로딩이 되기도 전에 스크롤을 내리면 로딩이 안되면서 크롤링이 되지 않는다.
-
이를 해결하기 위한 방법은
- 유튜브 영상으로 넘어간 뒤 댓글 창이 보일때까지 스크롤을 살짝 내린다.
- 내린 뒤 댓글 창은 자동으로 로딩이 되고 이제 스크롤 5번을 하면 된다.
-
해결 코드
# 스크롤 5번하기
# 댓글창이 보이게 스크롤을 살짝 내린 뒤 스크롤 5번을 실행한다.
def scroll_down(n_scroll):
ActionChains(driver).send_keys("k").perform()
last_height = driver.execute_script("return document.documentElement.scrollHeight")
# 댓글창이 보이도록 200픽셀 스크롤을 내린다.
# 수정! 댓글창이 보이도록 280픽셀 스크롤을 내린다.
driver.execute_script("window.scrollTo(0, 280);")
for i in range(5):
driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")
time.sleep(2)
new_height = driver.execute_script("return document.documentElement.scrollHeight")
if new_height == last_height:
break
last_height = new_height과제 2. Pandas를 이용한 데이터프레임 저장
- 데이터 가져오기
# UserID 가져오기
user_id = driver.find_elements(By.CSS_SELECTOR, '#author-text')
# 댓글 텍스트 가져오기
comment_text = driver.find_elements(By.CSS_SELECTOR, '#content-text'
# 좋아요 수 가져오기
num_likes = driver.find_elements(By.XPATH, '//*[@id="vote-count-middle"]')- for문을 통해 데이터 받기
# 데이터를 담을 빈 딕셔너리 만들기
comment_data_dict = {"UserID": [], "댓글텍스트":[],"좋아요 수":[]}
# 댓글 갯수를 나타내는 변수 지정하기
number_comments = driver.find_elements(By.CLASS_NAME, 'style-scope ytd-comment-view-model')
# loop를 통해 데이터 받기 및 저장하기
for i in range(len(number_comments)):
user_id = driver.find_elements(By.CSS_SELECTOR, '#author-text')[i].text
comment_text = driver.find_elements(By.CSS_SELECTOR, "#content-text")[i].text
num_likes = driver.find_elements(By.XPATH, '//*[@id="vote-count-middle"]')[i].text
# comment_data_dict 딕셔너리에 데이터 넣기
comment_data_dict["UserID"].append(user_id)
comment_data_dict["댓글텍스트"].append(comment_text)
comment_data_dict["좋아요 수"].append(num_likes)- 모든 for문이 끝난 후 URL에서
video_id추출
# URL에서 video_id 추출하기
video_id = url.split('?v=')[1]- 데이터프레임 만들기
videoIDcolumn에 추가하기
# 데이터프레임으로 만들기
df_comments = pd.DataFrame(comment_data_dict)
# videoID column 추가
df_comments["video_ID"] = video_id- csv파일로 저장하기
# .csv파일로 저장
df_comments.to_csv("./youtube_comments_crawling.csv")과제 3. 전체 함수 만들기
-
URL만 입력하면 바로 저장될 수 있도록 함수를 만든다.
-
코드 블럭에 따라 함수들을 만들어서 조합한다.
# 코드블럭의 내용들을 한곳으로 모은다.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import pandas as pd
def get_youtube_comment(url:str):
"""유튜브 댓글들, 댓글들의 아이디정보, 좋아요수를 추출하는 함수이다.
Args:
url (str): 추출할 유튜브 동영상 주소이다.
"""
# 크롬 창 열기
options = Options()
driver = webdriver.Chrome(options = options)
# 창모드 전체화면으로 크기 늘리기
driver.maximize_window()
# 로딩시간
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
# 유튜브 주소 접속하기
# url = "https://www.youtube.com/watch?v=0BWScn_OWPk"
driver.get(url)
# videoID 찾기
video_id = url.split('?v=')[1]
# 유튜브 로딩시간
time.sleep(5)
# n_scroll 변수 만들기
n_scroll = 5
# scroll_down 함수 호출해서 스크롤 내리기
scroll_down(driver,n_scroll)
# 데이터를 담을 빈 딕셔너리 만들기
comment_data_dict = {"UserID": [], "댓글텍스트":[],"좋아요 수":[]}
# 댓글 갯수를 나타내는 변수 지정하기
number_comments = driver.find_elements(By.CLASS_NAME, 'style-scope ytd-comment-view-model')
# loop를 통해 데이터 받기 및 저장하기
for i in range(len(number_comments)):
user_id = driver.find_elements(By.CSS_SELECTOR, '#author-text')[i].text
comment_text = driver.find_elements(By.CSS_SELECTOR, "#content-text")[i].text
num_likes = driver.find_elements(By.XPATH, '//*[@id="vote-count-middle"]')[i].text
# comment_data_dict 딕셔너리에 데이터 넣기
comment_data_dict["UserID"].append(user_id)
comment_data_dict["댓글텍스트"].append(comment_text)
comment_data_dict["좋아요 수"].append(num_likes)
# DataFrame으로 만들기
get_dataframe_and_csv(comment_data_dict,video_id)
# 창 닫기
driver.quit()
# =================================================================================
def scroll_down(driver,n_scroll):
"""스크롤를 내리는 함수
Args:
driver (): 현재 창을 말한다.
n_scroll (integer): 스크롤 하는 횟수
Returns:
None:
"""
ActionChains(driver).send_keys("k").perform()
last_height = driver.execute_script("return document.documentElement.scrollHeight")
# 댓글창이 보이도록 280픽셀 스크롤을 내린다.
driver.execute_script("window.scrollTo(0, 280);")
time.sleep(1)
for i in range(5):
driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")
time.sleep(2)
new_height = driver.execute_script("return document.documentElement.scrollHeight")
if new_height == last_height:
break
last_height = new_height
# =================================================================================
def get_dataframe_and_csv(comment_data_dict: dict, video_id:str):
"""받은 데이터를 데이터프레임으로 만들어 csv파일에 저장하는 함수
Args:
comment_data_dict (dict): 유튜브 댓글, 아이디 정보, 좋아요 수를 추출하여 저장 공간이다.
video_id (str): 유튜브 동영상 주소 뒤에 있는 고유 주소이다.
"""
# 데이터프레임으로 만들기
df_comments = pd.DataFrame(comment_data_dict)
# videoID column 추가
df_comments["video_ID"] = video_id
# .csv파일로 저장
df_comments.to_csv("./youtube_comments_crawling.csv")
# 입력칸
url = "https://www.youtube.com/watch?v=0BWScn_OWPk"
get_youtube_comment(url)