| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- #abc부트캠프 #유클리드소프트 #고용노동부 #대한상공회의소 #미래내일일경험사업
- #abc 프로젝트 멘토링 #유클리드소프트 #고용노동부 #대한상공회의소 #미래내일일경험사업 #공부일지 #멘토링일지
- Today
- Total
나른한 코딩 생활
[12일차] ABC 부트캠프 유튜브 댓글 크롤링 본문
오늘은 로컬 상에서 정적 데이터를 크롤링해보는 방법을 배울 것이다
그런 다음 이를 활용해서 원하는 영상의 유튜브 댓글을 크롤링 해보자
그전에 어제 배웠던 내용중 보충 사항을 집고 넘어가겠다
먼저 전시간에 만들었던 네이버 랭킹 뉴스의 보충을 조금더 해보겠다
많이 본 뉴스와 댓글이 많은 뉴스 명사 Top 10개 비교
각각의 기사제목을 텍스화 시킨다
각각 d_text 는 data 로, m_text는 re_data 로 텍스트로 변환하여 저장한다
# 기사 제목을 텍스트로 변환
d_text = ' '.join(li for li in data['기사제목'].astype(str))
m_text = ' '.join(li for li in re_data['기사제목'].astype(str))
# 명사 단어 추출 -> konlpy
# konlpy 한국어 자연어 처리를 위한 패키지
komoran = konlpy.tag.Komoran() # 형태소 분석기들 = Komoran, Kkma, okt (데이터 형태에 따라)
# 로컬 상황에서 Komoran 실행시 자바가 필요하다.
d_nn = komoran.nouns(d_text) # komoran 으로 명사형 추출
m_nn = komoran.nouns(m_text)
# 데이터 프레임으로 변환
d_word_df = pd.DataFrame({'word':d_nn})
m_word_df = pd.DataFrame({'word':m_nn})

d_word_df['count'] = d_word_df['word'].str.len()
m_word_df['count'] = m_word_df['word'].str.len()
# 새로운 컬럼 count 생성 // 글자의 길이를 알려준다
# 글자수가 2개 이상인 단어만 사용
d_word_df = d_word_df.query('count>=2') # count가 2 이상인
m_word_df = m_word_df.query('count>=2')
print(d_word_df.shape) #글자 수가 달라졌는지 체크
print(m_word_df.shape)

결과를 보면 m_word_df 의 word 개수가 약 500개 가량 줄었음을 확인할 수 있다.
# 많이 본 뉴스와 댓글이 많은 뉴스 명사 Top 10 개 비교
# 단어의 빈도표 만들기
d_group_df = d_word_df.groupby('word',as_index=False).agg(n=('word','count')).sort_values('n',ascending=False)
m_group_df = m_word_df.groupby('word',as_index=False).agg(n=('word','count')).sort_values('n',ascending=False)
지금까지 사용했던 것 처럼 groupby 키워드를 이용한다
word 의 개수를 count 연산한 다음 agg 로 통계, 이를 내림차순으로 정렬하고 그룹화 하여
d_group_df / m_group_df 변수에 저장한다
이후 각각의 그래프를 한곳에 그려 단어의 빈도순으로 그래프를 비교해보자
# 단어 빈도 막대 그래프
fig, axes = plt.subplots(1,2,figsize=(15,5))
plt.suptitle('가장 많이 본 뉴스와 댓글이 많은 뉴스의 단어 비교')
sns.barplot(data=d_group_df.head(10),y='word',x='n',ax=axes[0])
axes[0].set_title('가장 많이 본 뉴스 단어')
sns.barplot(data=m_group_df.head(10),y='word',x='n',ax=axes[1])
axes[1].set_title('댓글이 많은 뉴스 단어')
plt.show()

이렇게 분석한 자료들을 이번에는 워드 클라우드로 비교해보자
# 워드 클라우드 시각화를 위한 딕셔너리 준비
d_word_dic = d_group_df.set_index('word').to_dict()['n']
m_word_dic = m_group_df.set_index('word').to_dict()['n']
# 인덱스로 달라(내놔라) -> 키로 만들기 위한 작업
# 그럼 value 는? 뒤의 'n' 값으로

d_wc= WordCloud(width=1000,height=700,font_path=font_path).generate_from_frequencies(d_word_dic)
m_wc= WordCloud(width=1000,height=700,font_path=font_path).generate_from_frequencies(m_word_dic)
d_wc / m_wc 변수에 워드 클라우드화 시킨 다음 아래 코드를 통해 한 화면에서 비교하였다
fig = plt.figure(figsize=(15,5))
rows = 1
cols = 2
ax1 = fig.add_subplot(rows, cols, 1)
ax1.imshow(d_wc,interpolation='bilinear')
ax1.set_title('언론사별 많이 본 뉴스')
ax1.axis('off')
ax2 = fig.add_subplot(rows, cols, 2)
ax2.imshow(m_wc,interpolation='bilinear')
ax2.set_title('언론사별 댓글이 많은 뉴스')
ax2.axis('off')
plt.show()

유튜브 댓글 크롤링
먼저 유튜브 댓글 페이지는 동적 크롤링으로 할 수 밖에 없다.
페이지 소스 코드를 열더라도 해당 코드들의 내용을 파악하기 어렵기 때문이다.
먼저 자기가 원하는 유튜브의 댓글을 크롤링 하기 위해서 url을 알아야한다
나는 뷰티풀너드의 ' AK47 ' 공식 뮤직비디오의 url을 가져왔다
동적 크롤링은 로컬에서 행해야 하기 때문에 지금까지 작업을 해왔던 코랩에서는 작업 할 수 없다
파이썬 기초 2번째 시간에 설치했던 Anaconda 안에는 Spyder 라는 파이썬 작업 툴이 존재한다
여기에서 우리는 동적 크롤링을 시작하도록 하겠다
패키지 준비
먼저 로컬상에서는 install 과정을 거친 적이 없기 때문에 최초의 install을 해야한다
pip install selenium
pip install webdriver-manager
pip install beautifulsoup4
pip install wordcloud
위 4개의 라이브러리를 사용하기 위해서 install 해준다
이후 가져온 라이브러리 중 사용할 라이브러리들을 import 해주겠다
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service as ChromeService
# from selenium.webdriver.common.by import By
# line3 대신 line4의 코드를 실행할 경우 버전에 따라 Error 가 발생할 수 있다
from bs4 import BeautifulSoup
import time
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
Chrome 탭 생성
options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox') # 보안 기능인 샌드박스 비활성화
options.add_argument('--disable-dev-shm-usage') # dev/shm 디렉토리 사용 안함
service = ChromeService(executable_path=ChromeDriverManager().install())
driver = webdriver.Chrome(service= service,options=options)
driver.set_window_size(800, 800)
webdriver의 ChromeOptions()를 통해 options 변수에 크롬에 대한 드라이버 정보를 넣는다
이후 보안 기능 해제, 디렉토리 사용 권한을 없앤 다음
크롬 드라이버 매니저를 인스톨한다
driver 에는 해당 변수에 할당한 드라이버 매니저가 들어가고
이를 이용해 driver 에 set_window_size를 통해(800, 800) 사이즈의 크롬 윈도우를 생성한다
# 유튜브 영상 접속 https://youtu.be/QW_TSknSbWM?si=XiB-ARJKWefzdznz
driver.get('https://youtu.be/QW_TSknSbWM?si=XiB-ARJKWefzdznz')
driver.implicitly_wait(10) # 쉬는 시간을 통해 렌더링 및 기계적인 공격이라는 오해를 없앰 '중요!'
그렇게 생성한 driver 에 url을 할당하여 링크에 접속하고 10초간의 대기시간을 가진다
#사람 인 척 하기
time.sleep(10)
driver.execute_script('window.scrollTo(0,800)') # 스크롤을 올렸다 내렸다 하는 작업
time.sleep(5)
동적 크롤링의 핵심 중 하나인 사람인 척을 하기 위해 행동 전후로 sleep 함수를 사용해 텀을 주도록 한다.
이러한 작업을 하지 않는 다면 DDos 등의 공격으로 오해 받아 차단 당하거나, 렌더링이 제대로 되지 않을 수 있다.
유튜브 댓글은 일정 수의 댓글이 초기에 렌더링 된 이후 추가로 댓글을 확인하기 위해서는 스크롤을 아래로 내려서 추가적인 로딩을 해줄 필요가 있다. 이러한 과정을 하기 while 문을 사용하여 작성했다
# 댓글 수집을 위한 스크롤 내리기
last_height = driver.execute_script('return document.documentElement.scrollHeight')
while True:
print('스크롤 중...')
driver.execute_script('window.scrollTo(0,document.documentElement.scrollHeight);')
time.sleep(3)
new_height = driver.execute_script('return document.documentElement.scrollHeight')
if new_height == last_height:
break
last_height = new_height
time.sleep(3)
스크롤의 크기를 비교하여 마지막 크기가 로딩 후 크기와 같다면 while 문을 탈출한다
아닐경우 3초의 시간을 가진 뒤 다시 반복한다
# 댓글 크롤링
html_source = driver.page_source
soup = BeautifulSoup(html_source,'html.parser')
# soup 로 html 파싱
# 댓글 태그 리스트 가져오기
comment_list = soup.select('yt-attributed-string#content-text') #
comment_final = []
print('댓글 수 : ', str(len(comment_list)))
위 코드는 이제 스크롤을 끝까지 내렸으니 진정한 크롤링을 시작할 차례이다
여기서부터는 동적 크롤링과 유사하다
parser 를 통한 html 파싱 -> 이후 댓글의 모음과 최종 댓글을 넣을 comment_final 의 리스트를 선언했다
# 댓글 텍스트 추출
for i in range(len(comment_list)):
temp_comment = comment_list[i].text
temp_comment = temp_comment.replace('\n','').replace('\t','').strip()
print(temp_comment)
comment_final.append(temp_comment)
이후에는 뽑아낸 comment_list에서 각각의 텍스트를 임시 저장소인 temp_comment에 저장
temp_comment 에서 띄어쓰기나 엔터( ' \n ', 개행문자 ), 공백( ' \t ', 탭문자 ) 문자들을 제거한후
.strip() 을 통해 양 공백을 제거한다
# 데이터프레임 만드고 저장 (list -> dict -> df)
youtube_dic = {'댓글 내용':comment_final}
youtube_df = pd.DataFrame(youtube_dic)
print('=='*30)
print('크롤링 종료...')
print('=='*30)
comment_final 을 딕셔너리 형태로 youtube_dic 에 저장
이후 한번더 데이터프레임화 시켜 youtube_df 에 저장 한다
# 수집된 데이터 확인하기
print(youtube_df.info())
youtube_df.to_csv('AK47크롤링_20240709.csv',encoding='utf-8-sig',index=False)
print('=='*30)
print('파일 저장 완료...')
print('=='*30)
# 브라우저 닫기
driver.close()
youtube_df.info() 를 통해 이상 없음을 확인하면
파일을 csv 파일로 저장하고 driver.close()를 통해 브라우저를 닫는다

유튜브 댓글 시각화
!pip install konlpy
!pip install koreanize-matplotlib
import pandas as pd
import numpy as np
import konlpy
import matplotlib.pyplot as plt
import plotly.express as px
import koreanize_matplotlib
from PIL import Image
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
# 1) 파일 읽기
youtube_df = pd.read_csv('/content/drive/MyDrive/ABC BootCamp/AK47크롤링_20240709.csv')
# 2) 단어 솎아내기
okt = konlpy.tag.Okt()
word_df = pd.DataFrame({'word':okt.morphs(' '.join(li for li in youtube_df['댓글 내용'].astype(str)))})
# nouns가 아닌 morphs 를 사용하여 명사형이 아닌 단어구분별로 가져왔다
word_df['count'] = word_df['word'].str.len()
word_df = word_df.query('count >= 2') # 단어수 2개
group_df = word_df.groupby('word',as_index=False).agg(n=('word','count')).sort_values('n',ascending=False)
# 3) 단어 집계 현황 파악
px.bar(group_df.head(20), x='word', y='n',text_auto=True, title='단어 집계 결과')

얻은 정보에 따른 워드 클라우드가 제대로 생성되는지 한번 확인해보자
dic_word = group_df.set_index('word').to_dict()['n']
font_path = '/content/drive/MyDrive/ABC BootCamp/BMDOHYEON_ttf.ttf'
plt.subplots(figsize=(25,15))
wc = WordCloud(width=1000,height=700,font_path=font_path).generate_from_frequencies(dic_word)
plt.axis('off')
plt.imshow(wc,interpolation='bilinear')
plt.show()

# 4) 마스킹 이미지 결합
icon =Image.open('/content/akcolor.png')
ak_mask = np.array(icon)
plt.subplots(figsize=(25,15))
wc = WordCloud(width=1000,height=700,font_path=font_path,mask=ak_mask,background_color='white')
wc.generate_from_frequencies(dic_word)
plt.axis('off')
img_colors = ImageColorGenerator(ak_mask,default_color=(255,255,255))
wc=wc.recolor(color_func=img_colors) # 마스킹 이미지 색상으로 설정
plt.imshow(wc,interpolation='nearest') # 선명도 nearest 설정
plt.show()

'ABC 부트캠프' 카테고리의 다른 글
| [14일차] ABC 부트캠프 이미지 크롤링 (0) | 2024.07.13 |
|---|---|
| [13일차] ABC 부트캠프 멜론 연간 TOP30 차트 추출 (0) | 2024.07.13 |
| [11일차] ABC 부트캠프 데이터 크롤링 실전 (1) | 2024.07.09 |
| [10일차] ABC 부트캠프 ESG포럼&세미나 (0) | 2024.07.07 |
| [9일차] ABC 부트캠프 도로교통공단 데이터로 종합적인 데이터 분석 (0) | 2024.07.04 |