ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • python으로 유튜브 댓글 수집해보기 (feat. 스우파) | openpyxl, selenium
    IT/파이썬 응용 프로그래밍 2021. 10. 27. 00:00
    728x90
    SMALL

    스트릿우먼파이터는 요즘 즐겨보는 프로그램이다. 개인적으로 엠마 댄서분을 굉장히 좋아한다. 사람 몸이 어떻게 그렇게 유연할 수 있는지 너무 신기할 뿐이다. 귀여운 외모에 최근 선보인 배틀 실력까지!! 제일 응원하는 팀이 원트였는데 탈락해서 집에서 보다가 눈물 흘렸다 ㅋㅋ

    아무튼 본론으로 돌아와서, 유튜브에서 여러모로 반응을 분석해보면 재밌겠다 싶어서, 유튜브 댓글들을 스크래핑해보기로 했다. (상업적 사용은 절대 하지 않습니다!!, 그리고 혹시나 조회수 조작 등에 사용될까봐 방영 종료 이후 게재합니다.)

    이제 스크래핑 방법을 알아보자!! (python, jupyter notebook은 설치 되어있다고 가정하고 포스팅합니다)

    1. 필요한 모듈 설치

    우선, 이번에는 DB가 아니라 엑셀 파일에 저장할 것이다. 엑셀 파일에 저장하기 위해서 처음에는 그냥 기존에 있던 파일 입출력 문법으로 사용해보려고 했으나!! 더 쉽게 데이터를 저장할 수 있는 openpyxl이라는 모듈이 있었다. 그래서 이 모듈을 사용해보기로 했다.
    주피터 노트북을 열고, pip install로 설치해주었다.

    pip install openpyxl

    또잉? 난 설치한 적이 없는데 이미 설치가 되어있다고 한다. 특정 버전부터는 기본적으로 설치되어있는 패키지 인 거 같다.

    openpyxl을 설치하였으니, selenium을 설치해보자. 참고로, selenium은 웹 브라우저를 자동화 시켜주는 모듈이다.

    pip install selenium

    요렇게 나오면 설치 끝!

    2. 스크래핑하기

    원래는 유튜브 검색 바에 '스우파'라는 단어를 자동으로 입력하게 하고, 돋보기 모양의 검색버튼을 selenium으로 누르게 해서, 검색 결과를 가져와보려고 했다. 근데 맨 위에 주소 창을 보면 규칙이 있었다. "youtube.com/results?search_query=스우파"를 보면 youtube.com 주소뒤에 results 파라미터로 search_query=(검색할 단어) 라고 붙는 규칙이 있었다. (아마 GET method를 사용해서 일 것이다. REST API에 대해서 알고 싶다면, 이 링크 참고: https://2island.tistory.com/8)

    그래서, 검색할 단어를 변수로 선언 후, youtube.com/results?search_query=(검색할 단어) 링크로 바로 웹 브라우저를 열어볼 것이다. 크롬브라우저를 사용할 것이므로, chrome driver를 미리 설치해 두어야 한다. ( 설치 방법은 이 링크 참고 : https://2island.medium.com/python-selenium%EC%9C%BC%EB%A1%9C-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%98%EA%B8%B0-1-%EC%84%A4%EC%B9%98-c3e3e99de965)

    search_key = "스우파" # 검색할 단어 
    from openpyxl import * 
    excelfile = load_workbook('test.xlsx') # test.xlsx 파일을 load 
    
    excelfile.create_sheet(index=3, title=search_key) # sheet를 검색할 단어 이름으로 생성 
    sheet = excelfile[search_key] # 생성한 시트를 변수에 할당 
    ti = sheet.cell(row=1, column=1) # 시트의 1행, 1열에 "영상 제목"이라는 문자열 삽입 
    ti.value = "영상 제목" 
    
    comment_list = sheet.cell(row=1, column=2) # 시트의 1행, 2열에 "댓글"이라는 문자열 삽입 
    comment_list.value = "댓글" 
    excelfile.save('./test.xlsx') # 파일 저장

    검색할 단어를 search_key라는 변수에 넣고, openpyxl 모듈을 가져와서 xlsx파일, 즉 엑셀 파일을 열어주었다. 엑셀파일은 미리 하나 만들어두고, 이 프로그램을 돌려보는 것을 추천한다. 엑셀파일이 없는데, load_workbook 함수를 실행하면 오류가 난다.

    위의 코드를 실행하면 이렇게 스우파라는 시트가 생기고,

    1행 1열에 영상 제목, 1행 2열에 댓글이라는 문자열이 들어간 것을 확인할 수 있다.
    그리고 주의점은! save를 반드시 해주어야한다. 안그러면 저장이 되지 않으므로 주의하자.

    엑셀 파일은 만들었고, 이제 스크래핑을 해보자.

    from selenium import webdriver 
    import time 
    
    driver = webdriver.Chrome('../Downloads/chromedriver') 
    driver.get('https://www.youtube.com/results?search_query=' + search_key) 
    time.sleep(3)

    우선 selenium 모듈에서 webdriver를 import해준다. 그리고, 크롬브라우저를 사용할 것이므로, chromedriver를 다운로드 해둔 경로를 webdriver.Chrome()안에 넣어준다. 그리고 이제, 유튜브 창을 자동으로 열어줄 것이다. driver.get() 안에 열어줄 유튜브 주소를 넣어주면 된다. 위에서 youtube.com/results?searchkey=스우파 로 넣어주기로 했으므로, 그렇게 넣어준다.

    그리고 여기서 중요한 점은, time.sleep()을 넣어주어야 하는 것이다. 왜냐하면 웹 브라우저가 화면에 element들을 띄우는데 시간이 걸리기 때문에, 코드가 실행되는 속도를 따라가지 못해서 element를 못찾거나 데이터를 제대로 파싱하지 못할 수 있기 때문이다. 그래서 time.sleep으로 3초만 기다리기로 했다.

    유튜브는 검색결과를 아래로 스크롤하면서 검색 결과들을 추가로 보여준다. 그렇기 때문에 아래로 스크롤을 끝까지 할 수 있을 때까지해서, 검색결과에 대한 모든 동영상을 확보할 것이다. (이렇게 하지 않으면, 한 번 스크래핑한 동영상들을 다시 클릭하는 경우가 생길 수 있으므로 이렇게 진행한다.)

    from selenium.webdriver.common.keys import Keys 
    html = driver.find_element_by_tag_name('html') 
    prev_contents_len = 0 
    
    while True: 
    	time.sleep(3) 
        contents = driver.find_elements_by_xpath('//ytd-page-manager[@id="page-manager"]/ytd-search/div[@id="container"]/ytd-two-column-search-results-renderer/div[@id="primary"]/ytd-section-list-renderer/div[@id="contents"]/ytd-item-section-renderer/div[@id="contents"]/ytd-video-renderer') 
        current_contents_len = len(contents) 
        if prev_contents_len == current_contents_len: 
        	break 
        prev_contents_len = len(contents) 
        html.send_keys(Keys.END)

    스크롤은 html.send_keys(Keys.END) 명령어로 내릴 수 있다. send_keys는 키보드 입력을 대신 해주는 것으로, Keys.END 이면 키보드 상의 Page Down 버튼을 의미한다. 따라서 Page Down 버튼을 눌러서 아래로 쭉쭉 내려가면서 스크롤을 계속 해주겠다는 의미이다.
    그런데, 스크롤 다운을 데이터가 더이상 제공되지 않을 때까지 계속 해주어야 하므로, 무한 루프를 돌면서 스크롤을 내려줄 것이다.
    종료 조건을 만들어주어야 무한 루프가 끝이나므로 종료조건을 만들어줄 것이다. 종료조건은 동영상 개수가 스크롤을 내리기 전하고, 후하고 같은지를 비교할 것이다. 스크롤을 내리기 전하고 후하고 동영상 개수가 똑같다면 더이상 스크롤을 내릴 수 없는 상태이기 때문이다.

    동영상은, find_elements_by_xpath 함수를 이용해서 html 노드의 경로를 파라미터로 넣어줘서 찾아준다. (이미지 상에서 오른쪽 html 코드를 보면 ytd-video-renderer 라는 태그에 위치해있는 것을 알 수 있다. html 코드를 보는 방법은 찾고자 하는 element를 우클릭하고 '검사'를 누르거나, F12를 누르면 html 코드를 볼 수 있다.) 동영상이 위치한 element를 찾아서, 그 element의 개수를 비교하는 로직이 되는 것이다.

    find_elements_by_xpath와 같은 html 태그를 검색하는 함수들은 아래와 같은 것들이 있으니 참고하자.

    • find_element_by_id(‘id명’)
    • find_element_by_class_name(‘class명’)
    • find_element_by_xpath(‘xpath’)
    • find_element_by_tag_name(‘tag명’)

    이제 스크롤을 끝까지 다 땡겨서, 확보할 수 있는 동영상을 다 확보했다면, 동영상들을 차례로 클릭해서 들어간 후, 댓글들을 수집하면 된다.

    contents = driver.find_elements_by_xpath('//ytd-page-manager[@id="page-manager"]/ytd-search/div[@id="container"]/ytd-two-column-search-results-renderer/div[@id="primary"]/ytd-section-list-renderer/div[@id="contents"]/ytd-item-section-renderer/div[@id="contents"]/ytd-video-renderer') 
    for c in contents: 
    	c.click() 
        time.sleep(3) 
        title = driver.find_element_by_xpath('//ytd-page-manager[@id="page-manager"]/ytd-watch-flexy/div[@id="columns"]/div[@id="primary"]/div[@id="primary-inner"]/div[@id="info"]/div[@id="info-contents"]/ytd-video-primary-info-renderer/div[@id="container"]/h1[@class="title style-scope ytd-video-primary-info-renderer"]/yt-formatted-string').text 
        time.sleep(5) 
        new_contents = driver.find_element_by_xpath('//ytd-page-manager[@id="page-manager"]/ytd-watch-flexy/div[@id="columns"]/div[@id="primary"]/div[@id="primary-inner"]/ytd-comments[@id="comments"]') 
        time.sleep(5) 
        header = new_contents.find_elements_by_xpath('//ytd-item-section-renderer[@id="sections"]/div[@id="header"]/ytd-comments-header-renderer/div[@id="title"]/h2[@id="count"]/yt-formatted-string/span[@dir="auto"]') 
        time.sleep(5) 
        if len(header) == 0: 
        	driver.back() 
            continue 
            comment_num = int(header[1].text.replace(',', '')) 
            prev_comment_len = 0 
            while True: 
            	time.sleep(3) 
                comments = new_contents.find_elements_by_xpath('//ytd-item-section-renderer[@id="sections"]/div[@id="contents"]/ytd-comment-thread-renderer') 
                if prev_comment_len == len(comments): 
                	break 
                prev_comment_len = len(comments) 
                html.send_keys(Keys.END) 
            comments = new_contents.find_elements_by_xpath('//ytd-item-section-renderer[@id="sections"]/div[@id="contents"]/ytd-comment-thread-renderer') 
            for comment in comments: 
            	sheet.append([title, comment.find_element_by_id('content-text').text]) 
                excelfile.save('./test.xlsx') 
                time.sleep(5) 
                driver.back() 
            driver.quit()


    이 코드를 세부적으로 뜯어보자.
    먼저 동영상들이 있는 element들을 contents라는 변수에 리스트로 다 넣어준다. (find_elements_by_xpath 함수는 리스트로 여러 element를 가져와준다. find_element_by_xpath는 노드가 여러개 있을 경우 하나만 가져온다.)
    동영상들을 이제 for문을 돌면서, 클릭 후, 영상 제목과 댓글을 수집할 것이다.

    click() 함수를 이용해서 동영상을 클릭해서 들어간다.

    유튜브에서 F12로 태그를 확인해보면,
    ytd-page-manager[@id="page-manager"] > ytd-watch-flexy > div[@id="columns"] > div[@id="primary"] > div[@id="primary-inner"] > div[@id="info"] > div[@id="info-contents"] > ytd-video-primary-info-renderer > div[@id="container"] > h1[@class="title style-scope ytd-video-primary-info-renderer"] > yt-formatted-string 태그 경로에 유튜브 동영상의 제목이 들어 있는 것을 알 수 있다.

    아 효진초이님 레전드 무대였는데 너모 아쉽다 ㅠㅠ

    title = driver.find_element_by_xpath('//ytd-page-manager[@id="page-manager"]/ytd-watch-flexy/div[@id="columns"]/div[@id="primary"]/div[@id="primary-inner"]/div[@id="info"]/div[@id="info-contents"]/ytd-video-primary-info-renderer/div[@id="container"]/h1[@class="title style-scope ytd-video-primary-info-renderer"]/yt-formatted-string').text

    찾은 경로로 xpath를 통해 가져와 준 후, .text를 맨 뒤에 붙여서 태크 안에 있는 텍스트를 가져와주면 title이 정상적으로 가져와진다.

    댓글을 보면, 댓글 역시 아까 유튜브 동영상을 가져올 때 처럼, 스크롤을 계속 해줘야하는 것을 알 수 있다. 따라서 스크롤을 댓글이 새로 갱신되지 않을 때까지 해줄 것이다.

    # 댓글이 없는지 검사 
    new_contents = driver.find_element_by_xpath('//ytd-page-manager[@id="page-manager"]/ytd-watch-flexy/div[@id="columns"]/div[@id="primary"]/div[@id="primary-inner"]/ytd-comments[@id="comments"]') 
    time.sleep(5) 
    header = new_contents.find_elements_by_xpath('//ytd-item-section-renderer[@id="sections"]/div[@id="header"]/ytd-comments-header-renderer/div[@id="title"]/h2[@id="count"]/yt-formatted-string/span[@dir="auto"]') 
    time.sleep(5) 
    if len(header) == 0: 
    	driver.back() 
        continue 
        # 스크롤 끝까지 내리기 
        prev_comment_len = 0 
        while True: 
        	time.sleep(3) 
            comments = new_contents.find_elements_by_xpath('//ytd-item-section-renderer[@id="sections"]/div[@id="contents"]/ytd-comment-thread-renderer') 
            if prev_comment_len == len(comments): 
            	break 
            prev_comment_len = len(comments) 
            html.send_keys(Keys.END)

    위에서 동영상 제목을 가져올 때와 마찬가지로, find_elements_by_xpath를 통해서 가져올 것이다. 그 후, while True로 무한 루프를 돌면서 댓글의 개수가 스크롤을 하기 전 후가 같은지를 비교하면서 스크롤을 끝까지 내려줄 것이다. 그런데, 스크래핑을 하다보니 한가지 문제점이 댓글을 막아놨을 경우 오류가 생긴다. 그래서 댓글의 개수가 0개라면 댓글을 스크래핑하지 않고 넘어가는 로직을 무한 루프 위에 넣어줬다.

    마지막으로, 댓글 스크롤을 끝냈으니 이제 댓글들을 전부 다 가져와서 엑셀 시트에 넣어주는 작업이 필요하다. 엑셀 시트에 행 단위로 데이터를 추가하는 것은 append() 함수를 이용하면 다음 행에 데이터가 들어간다.

     comments = new_contents.find_elements_by_xpath('//ytd-item-section-renderer[@id="sections"]/div[@id="contents"]/ytd-comment-thread-renderer') for comment in comments: sheet.append([title, comment.find_element_by_id('content-text').text]) excelfile.save('./test.xlsx') time.sleep(5) driver.back()

    아까 파싱했던 title(영상 제목)과 댓글을 리스트로 만들어서 append 해주면 영상 제목, 댓글 순서대로 추가된다.
    댓글도 역시 xpath로 경로를 탐색해서 가져와 주었다. for문으로 댓글들을 다 돌면서 sheet에 넣어주고, for문이 끝나면 save해준다.
    save한 뒤에는 driver.back()으로 동영상을 클릭하기 전으로 돌아가주면 다시 위의 로직들이 반복되면서 댓글들을 수집할 것이다.
    이렇게 코드를 짜서 실행하고나면,

    이렇게, 영상 제목과 댓글들이 수집된 것을 볼 수 있다.

    그리고 코드 중간중간, time.sleep으로 delay를 주는건 정말 필수다. 그렇지 않으면 속도차이 때문에 제대로 element를 찾지못하는 경우가 생기기 때문이다. 댓글을 수집하다가 보니, 자꾸 element를 찾지 못해서 에러가 계속 나서 한참을 삽질했는데 알고보니 sleep을 넣어주면 해결되는 것도 엄청나게 많았다.

    아무튼, 재밌게 보고 있던 스우파 댓글을 한 번 수집해보았다. 이제 이 수집한 데이터로 반응이 전체적으로 어땠는지 분석을 해보아야겠다.

    728x90
    LIST

    댓글

Designed by Tistory.