전체 피드백

3주차 과제

Important Feedback

  • 나중엔 주피터 노트북이 아닌 파이썬 파일로 만들어 사용하게 되기 때문에 print를 통해 로그를 보여주는 습관을 기르는 것이 좋습니다.
    • 로그란?
    • 코드의 진행과정을 출력으로 확인해볼 수 있게 하는 것
  • 처음은 print로 연습했다가 나중에 logger라이브러리를 활용해보는 것을 추천합니다.
      • 로딩 바를 나타내주는 tqdm도 있습니다.
  • 과제 내용 이외에 완성된 데이터셋을 가지고 어떤 것을 할 수 있을지, 미리 전처리한다면 어떻게 해볼 수 있을 지 생각해 보는 것을 추천합니다.
    • 보통 자신이 손님일 때 이 결과물을 받으면 어디가 신경 쓰일까를 먼저 생각해봅니다.
    • 이에 대한 예시
      • Ex1) 좋아요는 숫자이니까 datatype을 int로 바꾸고 빈칸은 0으로 대체
      • Ex2) id 앞에 공통적으로 @가 존재하니까 제외하여 활용성을 높임
      • Ex3) 최종 데이터프레임의 열 이름을 영어/한글 통일시키기, 컬럼 위치 재조정하기

크롬 창 생성 및 유튜브 주소로 접속하기

1. 중간 피드백

  • 코드가 어떤 행동을 취하는 것인지 바로 파악이 힘든 경우에는 가독성을 위해 함수화 해주는 것도 좋습니다.
def pause(driver):
        ActionChains(driver).send_keys('k').perform()
  • 자바 스크립트로 접근하는 방법입니다.
def pause(driver):
        video = driver.find_element(By.TAG_NAME, "video")
        ispause = driver.execute_script("return arguments[0].paused;", video)
        if not ispause:
            driver.execute_script("arguments[0].pause();", video)

과제3. 전체 함수 만들기

2. 중간 피드백

  • 크롤링하는 함수로 개발하는 과정을 담았습니다.
  • “이러한 방법으로도 할 수 있구나”라고 이해하고, 적용하고 싶은 부분만 따로 정리하시면 될 것 같습니다.

연구원님의 크롤링하는 함수 개발하는 과정

STEP 1. 순차적 개발

# Setting
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
from urllib.parse import parse_qs
 
url = "https://www.youtube.com/watch?v=0BWScn_OWPk"
  • 유튜브 영상 주소로 들어가면 영상이 자동으로 실행되는 경우와 안되는 경우가 있다.
  • 이를 변수로 지정해 boolean으로 실행한다면 멈추고 멈춰있으면 그대로 두는 코드를 짠다.
# 크롬 창 열기
options = Options()
driver = webdriver.Chrome(options = options)
 
  
 
# 창모드 전체화면으로 크기 늘리기
driver.maximize_window()
 
  
 
# 로딩시간: 페이지 로딩
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.ID, "below")))
 
  
 
# 유튜브 주소 접속하기
driver.get(url)
 
  
 
# 로딩시간: div마다 로딩되는 시간이 다르다는 것을 확인, 제일 마지막에 나오는 below요소를 기준으로 함.
# 로딩될 때 영상이 먼저 나오고 마지막으로 댓글이 나온다.
# 그래서 댓글창까지 다 나오면 로딩완료라는 뜻을 하렬고 ID, below까지 하신거다
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.ID, "below")))
 
  
 
# 비디오 멈추기: 자동실행 될 때가 있고, 안될 때가 있다.
 
video = driver.find_element(By.TAG_NAME, "video")
# `return arguments[0].paused;`는 영상이 멈춰있는지 아닌지 True, False로 반환해준다.
# JavaScript 언어다.
ispause = driver.execute_script("return arguments[0].paused;", video)
 
print(ispause)
 
if not ispause:
	# `pause()`는 영상을 멈추게 하는 JavaScript 함수이다.
    driver.execute_script("arguments[0].pause();", video)
# 스크롤 다운
# 댓글창이 보여야 활성화 되기 때문에 살짝 내리는 과정 필요
driver.execute_script("window.scrollTo(0, 280);")
time.sleep(1)
last_height = driver.execute_script("return document.documentElement.scrollHeight")
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
  • 로그를 보여줌으로서 코드의 실행 과정이 어느정도 진행되었는지 알 수 있다.
  • 이러한 로그 프린트는 알아두는게 좋을 거 같다.
  • 또한 중간 중간마다 print()를 통해 값이 제대로 저장되었는지 확인과 출력했을 때 이 숫자 또는 출력값이 무엇을 뜻하는지 알아보기 쉽게 되어 있어서 좋다.
# 추출하기
# 데이터를 담을 빈 딕셔너리 만들기
comment_data_dict = {"UserID": [], "Comment":[],"Likes":[]}
 
# 댓글 불러오기
number_comments = driver.find_elements(By.CLASS_NAME, 'style-scope ytd-comment-view-model')
# `print()`를 통해 불러올 댓글 수를 알아보기 쉬워 결과값을 볼 때 한눈에 보인다.
print(f"불러올 댓글 수: {len(number_comments)}")
 
# loop를 통해 데이터 받기 및 저장하기
cnt = 0
print("START", end=" ")
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[1:])
    comment_data_dict["Comment"].append(comment_text)
    comment_data_dict["Likes"].append(num_likes)
 
    # 로그 프린트
    cnt += 1
    if cnt > 0 and cnt % 20 == 0:
        print(cnt, end=" > ")
print("End")
 
print(comment_data_dict)
  • parse_qs()는 지정된 쿼리스트링을 해석하여 dict()로 반환합니다.
  • unquote 정리노트
print(url.split('?')[-1])
 
# 출력값
v=0BWScn_OWPk
# videoid 추출: url에서 v의 value 추출하기
query = parse_qs(url.split('?')[-1])
print(query)
videoid = query["v"][0]
# 데이터 프레임 만들기
df_comments = pd.DataFrame(comment_data_dict)
 
# videoID column 추가
df_comments["video_ID"] = videoid
 
# .csv파일로 저장
df_comments.to_csv("./youtube_comments_crawling.csv")

  • 로그
  • for문의 range에 맞춰 cnt % 20 == 0을 통해 20, 40, 60, 80, … 으로 출력되게 하였다.
cnt = 0
print("START", end=" ")
 
for i in range(len(number_comments)):
		pass
 
	cnt += 1
    if cnt > 0 and cnt % 20 == 0:
        print(cnt, end=" > ")
# 출력값
START 20 > 40 > 60 > 80 > 100 > 120 > End

STEP 2. 계획 설정

  1. 전체 함수 이름: get_youtube_comment(url, n_scroll, save_path='./')
  2. 덩어리별로 분류
    • 크롬 창 열고, 동영상 재생중이라면 일시정지 open_chrome(url) output : driver
    • 댓글 스크롤 다운 scroll_down(driver, n_scroll) output : driver
    • 정보 추출 crawling_comment(driver) output : dataframe
    • 데이터 프레임 저장

STEP 3. 최종 코드 정리

  • 전체적으로 쓰이는 함수
  • 다른 곳에서도 자주 쓰이는 함수들을 만들어 놓으면 편할거 같다.
  • 연구원님이 말씀하신 함수를 만들어두라는게 무엇인지 알거 같다.
# Fuction 정리
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
from urllib.parse import parse_qs
 
 
# Functions: 자주 사용되는 함수
def loading(driver, element=(By.TAG_NAME, "body")):
    """페이지가 로딩될 때까지 기다리는 함수"""
    # 로딩시간
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_element_located(element))
 
def video_pause(driver):
    video = driver.find_element(By.TAG_NAME, "video")
    ispause = driver.execute_script("return arguments[0].paused;", video)
    if not ispause:
        driver.execute_script("arguments[0].pause();", video)
 
def scroll_down(driver, n_scroll):
    """페이지를 스크롤다운 하는 함수"""
    # 댓글창이 보여야 활성화 되기 때문에 살짝 내리는 과정 필요
    driver.execute_script("window.scrollTo(0, 280);")
    time.sleep(2)
    last_height = driver.execute_script("return document.documentElement.scrollHeight")
    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_videoid(url:str):
    """URL에서 videoid 추출하는 함수"""
    query = parse_qs(url.split('?')[-1])
    videoid = query["v"][0]
    return videoid
  • 이번 과제에서 사용할 함수
# Functions: 이번 과제에서 사용할 함수
def open_chrome(url):
    """크롬 창을 생성하여 url로 이동하는 함수"""
    # 크롬 창 열기
    options = Options()
    driver = webdriver.Chrome(options = options)
  
    # 창모드 전체화면으로 크기 늘리기
    driver.maximize_window()
 
    # 로딩시간
    loading(driver)
 
    # 유튜브 주소 접속하기
    driver.get(url)
 
    # 유튜브 로딩시간
    loading(driver, element=(By.ID, "below"))
 
    # 동영상 재생되면 멈춤
    video_pause(driver)
 
    return driver
 
  
 
def crwaling_comment(driver):
    """댓글 데이터프레임을 만드는 함수"""
    # 데이터를 담을 빈 딕셔너리 만들기
    comment_data_dict = {"UserID": [], "Comment":[],"Likes":[]}
 
    # 댓글 불러오기
    number_comments = driver.find_elements(By.CLASS_NAME, 'style-scope ytd-comment-view-model')
    print(f"-불러올 댓글 수: {len(number_comments)}")
 
    # loop를 통해 데이터 받기 및 저장하기
    cnt = 0
    print("-START", end=" ")
    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[1:])
        comment_data_dict["Comment"].append(comment_text)
        comment_data_dict["Likes"].append(num_likes)
 
        # 로그 프린트
        cnt += 1
        if cnt > 0 and cnt % 20 == 0:
            print(cnt, end=" > ")
    print("End")
 
    return comment_data_dict
  • 메인 함수
# 메인 함수
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
from urllib.parse import parse_qs
 
def get_youtube_comment(url, n_scroll=5, save_path="./"):
    # video id 추출
    video_id = get_videoid(url)
    print(f"YOUTUBE ID: {video_id}")
 
    # 크롬 창 열기
    print(f"1. 크롬 창을 열고 있습니다.", end=" ")
    driver = open_chrome(url)
    print(f">>>> 완료")
 
    # 스크롤 다운
    scroll_down(driver, n_scroll)
 
    # 딕셔너리 만들기
    print(f"2. 댓글을 가져오는 중입니다.")
    comment_data_dict = crwaling_comment(driver)
 
    # 데이터 프레임으로 만들기
    print(f"3. 데이터프레임으로 변환 중입니다", end=" ")
    df_comments = pd.DataFrame(comment_data_dict)
    df_comments["videoID"] = video_id
    print(">>>> 완료")
 
    # .csv파일로 저장
    df_comments.to_csv(save_path + "youtube_comments_crawling.csv")
 
    return df_comments
  • 실행 코드
# 실행 코드
url = "https://www.youtube.com/watch?v=0BWScn_OWPk"
youtube_comments = get_youtube_comment(url)
 
# 실행 결과
YOUTUBE ID: 0BWScn_OWPk
1. 크롬 창을 열고 있습니다. >>>> 완료
2. 댓글을 가져오는 중입니다.
-불러올 댓글 수: 120
-START 20 > 40 > 60 > 80 > 100 > 120 > End
3. 데이터프레임으로 변환 중입니다 >>>> 완료

(번외) python 함수로 만들기

# 파일 구조
selenium_tool/
│   ├── __init__.py : 모듈을 불러오면 자동으로 실행됨
│   ├── utils.py : 자주 쓰이거나 활용이 많은 함수들의 모임
│   └── youtube_crawling.py : 유튜브 크롤링에만 쓰이는 함수들의 모임
main.py
  • __init__.py
  • selenium_tool 이라는 모듈을 만들어 뿌리를 만든다.
from selenium_tool.utils import *
from selenium_tool.youtube_crawling import *
  • utils.py
# utils.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
from urllib.parse import parse_qs
 
def loading(driver, element=(By.TAG_NAME, "body")):
    """페이지가 로딩될 때까지 기다리는 함수"""
    # 로딩시간
    wait = WebDriverWait(driver, 10)
    wait.until(EC.presence_of_element_located(element))
 
def video_pause(driver):
    video = driver.find_element(By.TAG_NAME, "video")
    ispause = driver.execute_script("return arguments[0].paused;", video)
    if not ispause:
        driver.execute_script("arguments[0].pause();", video)
 
def scroll_down(driver, n_scroll):
    """페이지를 스크롤다운 하는 함수"""
    # 댓글창이 보여야 활성화 되기 때문에 살짝 내리는 과정 필요
    driver.execute_script("window.scrollTo(0, 280);")
    time.sleep(2)
    last_height = driver.execute_script("return document.documentElement.scrollHeight")
 
    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_videoid(url:str):
    """URL에서 videoid 추출하는 함수"""
    query = parse_qs(url.split('?')[-1])
    videoid = query["v"][0]
 
    return videoid
  • youtube_crawling.py
# youtube_crawling.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium_tool.utils import *
 
def open_chrome(url):
    """크롬 창을 생성하여 url로 이동하는 함수"""
    # 크롬 창 열기
    options = Options()
    driver = webdriver.Chrome(options = options)
 
    # 창모드 전체화면으로 크기 늘리기
    driver.maximize_window()
    # 로딩시간
    loading(driver)
    # 유튜브 주소 접속하기
    driver.get(url)
    # 유튜브 로딩시간
    loading(driver, element=(By.ID, "below"))
 
    # 동영상 재생되면 멈춤
    video_pause(driver)
    return driver
 
def crwaling_comment(driver):
    """댓글 데이터프레임을 만드는 함수"""
    # 데이터를 담을 빈 딕셔너리 만들기
    comment_data_dict = {"UserID": [], "Comment":[],"Likes":[]}
 
    # 댓글 불러오기
    number_comments = driver.find_elements(By.CLASS_NAME, 'style-scope ytd-comment-view-model')
    print(f"-불러올 댓글 수: {len(number_comments)}")
 
    # loop를 통해 데이터 받기 및 저장하기
    cnt = 0
    print("-START", end=" ")
    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[1:])
        comment_data_dict["Comment"].append(comment_text)
        comment_data_dict["Likes"].append(num_likes)
 
        # 로그 프린트
        cnt += 1
        if cnt > 0 and cnt % 20 == 0:
            print(cnt, end=" > ")
    print("End")
 
    return comment_data_dict
  • main.py
  • [[과제 공부용#if-namemain-| if __name__ == "__main__"에 대한 노트]]
# main.py
import pandas as pd
from selenium_tool import *
 
def get_youtube_comment(url, n_scroll=5, save_path="./"):
    # video id 추출
    video_id = get_videoid(url)
    print(f"YOUTUBE ID: {video_id}")
 
    # 크롬 창 열기
    print(f"1. 크롬 창을 열고 있습니다.", end=" ")
    driver = open_chrome(url)
    print(f">>>> 완료")
 
    # 스크롤 다운
    scroll_down(driver, n_scroll)
 
    # 딕셔너리 만들기
    print(f"2. 댓글을 가져오는 중입니다.")
    comment_data_dict = crwaling_comment(driver)
 
    # 데이터 프레임으로 만들기
    print(f"3. 데이터프레임으로 변환 중입니다", end=" ")
    df_comments = pd.DataFrame(comment_data_dict)
    df_comments["videoID"] = video_id
    print(">>>> 완료")
 
    # .csv파일로 저장
    df_comments.to_csv(save_path + "youtube_comments_crawling.csv")
 
    return df_comments
 
# 터미널에서 파이썬을 직접 실행하면 아래의 코드가 실행됨
# python main.py
if __name__ == "__main__":
    url = "https://www.youtube.com/watch?v=0BWScn_OWPk"
    youtube_comments = get_youtube_comment(url)