컴공과컴맹효묘의블로그

유튜브 댓글 크롤링 Python (feat. take_a_look) 본문

개발/짧

유튜브 댓글 크롤링 Python (feat. take_a_look)

효묘 2020. 4. 11. 01:39
반응형

사용한 파이썬 모듈(라이브러리):

  • matplotlib # 정보 시각화 모듈
  • konlpy # 한국어 정보 처리 모듈
  • BeautifulSoup # 웹 파싱 라이브러리
  • time # 지간 지연을 사용하려고 
  • selenium # 유튜브같은 경우, 사이트를 단순 파싱하면 댓글 로딩이 안 된 상태에서 파싱이 되기때문에 댓글을 로딩하기 위함
  • pandas # 데이터 관리 모듈
  • re # 정규식 연산 모듈. 댓글에 쓸모 없는 이모티코을 줄이기 위함
  • collections # 중복되는 단어를 세기 위한 모듈
  • wordcloud # 중복되는 단어의 빈도수를 이미지 시키기 위한 모듈

 

제가 왜 유튜브 댓글을 파싱하고 있는지 모르겠는데, 심심해서 그랬나봅니다.

주의 : 이런 짓을 처음해서 코드가 많이 더럽습니다.

import matplotlib.pyplot as plt
from konlpy.tag import Hannanum
from bs4 import BeautifulSoup
import time
from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys
import pandas as pd
import re
from collections import Counter
from wordcloud import WordCloud
# Expected chromedriver.exe

* selenium의 Chrom을 사용할 때는 chromedriver가 필요합니다.

 

 

1. 댓글 분석할 유튜브 링크와, 나중에 댓글 빈도수를 시각화 해줄 폰트의 경로를 입력 받습니다. 그리고 유튜브 창에서 얼마나 스크롤을 내릴지 입력받습니다. selenium은 유튜브 댓글을 로딩하기 위해서 실제로 크롬창을 열어 입력된 명령어들을 수행합니다.

url = input('input child url(ex: watch?v=2A6pygV4Z04 ) : ')
url = 'https://www.youtube.com/'+url
font_path_input = 'C:/Windows/Fonts/malgun.ttf'
num_scroll = int(input('input num of scroll (50 recommend) : '))

 

2. 크롬 브라우저 인스턴스를 생성한 후 유튜브 창을 열고 유튜브창 로딩을 위해 2초간 기다려줍니다.

browser = Chrome()
browser.get(url)
browser.implicitly_wait(2)

 

3. 브라우저에서 스크롤 명령을 입력할 위치가 어디인지 html tag로 찾아줍니다. 웹 전체를 스크롤할 것이기 때문에 body 태그를 find_elemetn_by_tag_name을 이용해서 찾아줍니다.

body = browser.find_element_by_tag_name('body')

 

4. id, content, likes의 데이터를 담기위한 임시 리스트를 선언합니다.

yt_id = []
yt_content = []
yt_likes = []

 

5. 아까 킨 웹 창에서 스크롤을 내립니다.

while num_scroll:
    body.send_keys(Keys.PAGE_DOWN)
    time.sleep(0.3)
    num_scroll -= 1

 

6. 스크롤을 충분히 내려 댓글들을 로딩했으니, 현재 html을 파싱합니다. 그리고 브라우저는 닫아줍니다.

html0 = browser.page_source
html = BeautifulSoup(html0, 'html.parser')
browser.close()

 

유튜브에서 댓글을 우클릭하고 검사(N)를 눌러줍니다.

 

 

위 사진에는 살짝 오류가 있었는데 아무튼 우측 tag에 마우스를 갖다 대면은 웹창에 파란색 박스로 해당 태그가 어디를 구현하는지 알려줍니다.

제가 필요한 정보는 '유튜브 닉네임', '댓글 내용', '좋아요 수' 이므로 이 세가지를 모두 포함해야합니다. 이를 만족하는 가장 작은 박스를 찾으라면 우측 회색 박스에 있는 div태그의 id="main"인 부분입니다.

 

주의 할 점이라면, 댓글 이외에 id가 main인 div태그가 존재해서는 안 됩니다. 잘 확인해 주세요.

 

6. 해당 부분을 모두 찾아 result에 넣어줍니다. find_all을 사용하고 반환값은 bs4.element.ResultSet입니다.

result = html.find_all("div", id='main')

 

7. 아까 만들어둔 임시 저장 변수(yt_id,comment,likes)에 저장합니다.

#제가 만든 likes코드에 논리 오류가 있습니다. 귀찮아서 안고쳤으니 주의 바랍니다. 사실 댓글 wordcloud에 필요한건 comment 데이터들 뿐 이니까 상관 안하셔도 무관합니다.

+그리고 왜 index out of range가 나는지 모르겠는데, 암튼 오류가 나서 try문으로 잡아줍니다.

i는 BeautifulSoup으로 인스턴트를 받아온다음에 다시 필요한 부분의 tag를 추출합니다.

사실 특정 tag의 child tag중에 원하는 tag를 찾는 방법을 몰라서 이렇게 야매로 했는데, 더 좋은 방법 있으면 댓글로 알려주시면 감사하겠습니다.

for i in result:
    i = BeautifulSoup(str(i), 'html.parser')
    try:
        yt_id.append(i.find('a', id='author-text').text)
        yt_content.append(i.find('div', id='content').text)
        yt_likes.append(i.find('span', id='vote-count-middle').text)
    except:
        print('Exception!!!!!!!!!')

 

8. pandas 모듈로 데이터로 DataFrame을 만들고, 모든 데이터를 csv로 저장합니다.

df = pd.DataFrame({'id': yt_id, 'content': yt_content, 'likes': yt_likes})
df.to_csv('youtube_crawling.csv', mode='w', encoding='utf-8-sig')

#주의# 인코딩 utf-8로 하면 한글이 깨집니다. utf-8-sig로 해주세요.

 

9. 모든 comment를 하나의 문자열로 합쳐버립시다.

texts = ''
for i in list(df['content']):
    texts += i

 

10. 이모티콘, 의미없는 문자들을 지워줍니다. 코드 출처 : https://github.com/minyong-shin/Bloging/tree/master

# 이모티콘 제거
emoji_pattern = re.compile("["
                           u"\U0001F600-\U0001F64F"  # emoticons
                           u"\U0001F300-\U0001F5FF"  # symbols & pictographs
                           u"\U0001F680-\U0001F6FF"  # transport & map symbols
                           u"\U0001F1E0-\U0001F1FF"  # flags (iOS)
                           "]+", flags=re.UNICODE)

# 분석에 어긋나는 불용어구 제외 (특수문자, 의성어)
han = re.compile(r'[ㄱ-ㅎㅏ-ㅣ!?~,".\n\r#\ufeff\u200d]')

comment_result = []
str_result = ''
for i in texts:
    tokens = re.sub(emoji_pattern, "", i)
    tokens = re.sub(han, "", tokens)
    str_result += tokens
    comment_result.append(tokens)

str_result에 정제된 문자열을 저장합니다.

 

11. konlpy를 이용해서 str_result의 문장들에서 명사만 추출하여 list형태로 저장합니다.

hannanum = Hannanum()
noun_words = list(hannanum.nouns(str_result))

 

12. noun_words로 Counter 인스턴트를 생성해준 후에, count.most_common으로 가장 많이 중복되는 단어를 내림차순으로 정렬해줍니다. 그리고 dict 타입으로 캐스팅 해줍니다.

count = Counter(noun_words)
words = dict(count.most_common())
print(words)

 

13. wordcloud로 시각화 합니다.

+데이터가 많이지면 java.lang.ArrayIndexOutOfBoundsException뜨던데 왜그런지 모르겠네요. 나중에 알아보겠습니다.

words_ = words
words_save = pd.DataFrame(words_, index=[0])
words_save.to_csv("youtube_crawling_words.csv", mode='w', encoding='utf-8-sig')

wordcloud = WordCloud(font_path=font_path_input, background_color='white', width=1600, height=1200)
cloud = wordcloud.generate_from_frequencies(words)
plt.imshow(cloud)
plt.axis('off')
plt.show()

 

실행 결과 :

(link = watch?v=2A6pygV4Z04, scroll = 10)

 

 

 

(link = watch?v=2A6pygV4Z04, scroll = 20)

 

 

사실 댓글만 봐서도 알 수 있지만 전남친, 남친, 언니 등 이런 단어들을 보면 여성 시청자가 많다는 것을 알 수 있습니다. 사실 데이터가 많이 부족하긴 한데 데이터를 너무 많이 불러오면 위에서 말했다시피 java.lang.ArrayIndexOutOfBoundsException가 납니다... 원인을 찾으면 바로 글 수정하도록 하겠습니다.

 

github주소 : https://github.com/yoonki1207/hyomyoBlog/blob/master/youtubeCommentCrawling.py

반응형
Comments