이번에는 웹툰 제목, 회차별 등록일, 회차별 조회수 ,회차별 별점, 댓글, 댓글 좋아요수, 댓글싫어요수를 크롤링 할거다.
크롤링할때 먼저 정식웹툰만 쭉 크롤링하고, 랜덤함수로 웹툰 몇페이지만 뽑아서 비정식웹툰을 크롤링 할거다.
가져올 데이터
정식웹툰 크롤링
- 웹툰 제목, 회차별 등록일, 회차별 조회수 ,회차별 별점, 댓글, 댓글 좋아요수, 댓글싫어요수
from selenium import webdriver as wb
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup as bs
import pandas as pd
import time
먼저 위의 라이브러리를 임폴트 해주세요
driver = wb.Chrome()
titleList = [] # 제목
titleList2 = [] # 댓글 하나당 제목 하나씩 가져오기 위한 리스트
date_list = [] # 회차별 날짜
count_list = [] # 회차별 조회수
star_list = [] # 회차별 별점
comment_list = [] # 댓글
comment_date_list = []
comment_like_list = []
comment_hate_list = []
viewList = [] # 회차제목
viewList2 = [] # 댓글 하나당 회차제목 하나씩 가져오기 위한 리스트
roundList = [] # 회차
roundList2 = [] # 댓글 하나당 회차 하나씩 가져오기 위한 리스트
list1 = [5, 7] # div태그 자식 선택자 순서
list2 = [1, 3, 5] # td태그 자식선택자 순서
for m in range(1, 131): # 페이지
url="https://comic.naver.com/genre/bestChallenge.nhn?m=main&order=ViewCount&page="+str(m)
driver.get(url)
time.sleep(1)
for k in list1: # div태그 자식 선택자 순서
for i in range(1,5): # 고정값 바꾸면 안됨, tr태그 자식선택자 순서, #1,5
for j in list2: # td태그 자식선택자 순서
if m==19 and k == 5 and i == 3 and j== 5:
continue
div = driver.find_element_by_css_selector("div:nth-child("+str(k)+") > table > tbody > tr:nth-child("+str(i)+") > td:nth-child("+str(j)+") > div.fl > a")
try:
div2 = div.find_element_by_css_selector('span.mark_serial') # 정식연재 선택자
print(div2)
if div2 != None:
div.click()
except :
continue
time.sleep(1)
cnt_first = driver.find_element_by_class_name("first")
cnt_first.click()
time.sleep(1)
back = 0
num = 0
for page in range(0, 5): # 다음화 넘어가기 반복문
soup = bs(driver.page_source,'lxml')
# 웹툰 제목
title = soup.select("div.detail")[0].text
titleList.append(title.split("\t")[0].split("\n")[1])
# 회차
num += 1
roundList.append(str(num))
# 회차제목
view = soup.select("div.tit_area > div.view")[0].text
viewList.append(view.split("\n")[1])
# 등록일
date = soup.select("dd.date")[0].text
date_list.append(date)
# 조회수
count = soup.select("dd.date")[1].text # 조회수
count_list.append(count)
# 별점
star = soup.select("#topPointTotalNumber > strong")[0].text
star_list.append(star)
driver.switch_to.window(driver.window_handles[-1]) # 댓글 iprame 시작
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser") # 댓글 iprame 여기까지
time.sleep(1)
# 전체 댓글 클릭
all_click = driver.find_element_by_tag_name("#cbox_module_wai_u_cbox_sort_option_tab2 > span.u_cbox_sort_label")
all_click.click()
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
for d in range(4,500): # 댓글 넘기기 반복
for c in range(0,15): # 한페이지 안의 댓글
try:
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
comment = soups.select("span.u_cbox_contents")[c].text
comment_list.append(comment)
comment_date = soups.select("span.u_cbox_date")[c].text
comment_date_list.append(comment_date)
comment_like =soups.select("em.u_cbox_cnt_recomm")[c].text
comment_like_list.append(comment_like)
comment_hate = soups.select("em.u_cbox_cnt_unrecomm")[c].text
comment_hate_list.append(comment_hate)
titleList2.append(title.split("\t")[0].split("\n")[1])
viewList2.append(view.split("\n")[1])
roundList2.append(str(num))
except:
print("댓글정보 가져오기 끝")
try:
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
two_click = driver.find_element_by_tag_name("#cbox_module > div > div.u_cbox_paginate > div > strong + a")
two_click.click()
# time.sleep(0.5)
except:
print("댓글넘기기버튼끝")
break # 댓글페이지 넘기기 멈춰
try:
driver.switch_to.window(driver.window_handles[-1]) # 다음화 페이지 넘어갈때
next_click = driver.find_element_by_tag_name("div.btn_area > span.next > a")
next_click.click()
back += 1
except:
print("다음화버튼끝")
break
for ba in range(0, back+2): # 뒤로가기 반복문
driver.back()
코드가 길어서 설명은 아래에서 따로 할께요.
view = {"웹툰 제목": titleList,"회차 제목":viewList,"회차": roundList, "등록일" : date_list, "조회수": count_list, "회차별 별점": star_list}
view = pd.DataFrame(view)
commentDic = {"웹툰제목":titleList2,"회차 제목":viewList2, "회차": roundList2,"댓글": comment_list, "댓글 등록일" : comment_date_list, "댓글 좋아요": comment_like_list, "댓글 싫어요": comment_hate_list}
commentDic = pd.DataFrame(commentDic)
리스트 길이가 달라서 하나의 데이터 프레임으로 만들수가 없어요. 그래서 데이터 프레임 2개로 나눠서 저장했습니다.
# csv파일로 저장
view.to_csv('view100.csv', encoding = "")
commentDic.to_csv('commentDic100.csv', encoding = "")
그다음 csv파일로 저장했어요.
랜덤페이지 크롤링
- 웹툰 제목, 회차별 등록일, 회차별 조회수 ,회차별 별점, 댓글, 댓글 좋아요수, 댓글싫어요수
from selenium import webdriver as wb
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup as bs
import pandas as pd
import time
# 걸러야되는 페이지 10, 15, 19
import random
randomList = [] #리스트생성
rnum = random.randint(1,130) #1부터130까지 추출
for i in range(15): #숫자15개 뽑기
while rnum in randomList: #뽑은 숫자들이 중복되지않도록 list에 이미 있는 숫자인지 검토
rnum = random.randint(1,130) #중복되면 다시 뽑기
randomList.append(rnum) #list에 추가
randomList.sort()
for j in randomList:
print(j, end = ' ')
우리가 할 프로젝트는 웹툰 승격확률 예측 프로젝트이므로 정식웹툰수가 228개인데 비정식웹툰은 모두 크롤링하면 2000개가 넘기때문에 편향이 생겨서 예측이 잘 안될 수 있다. 따라서 15페이지만 뽑아서 그페이지중 정식웹툰을 삭제하여 비정식웹툰데이터를 만들거다. 그러면 15페이지 * 한페이지에 있는 웹툰수 24 = 360개의 웹툰이 뽑힌다.
정식웹툰을 삭제하는건 전처리때 할건데 360개에서 정식웹툰을 삭제해도 350여개 가까이 될거라 추측한다.
driver = wb.Chrome()
titleList = [] # 제목
titleList2 = [] # 댓글 하나당 제목 하나씩 가져오기 위한 리스트
date_list = [] # 회차별 날짜
count_list = [] # 회차별 조회수
star_list = [] # 회차별 별점
comment_list = [] # 댓글
comment_date_list = []
comment_like_list = []
comment_hate_list = []
viewList = [] # 회차제목
viewList2 = [] # 댓글 하나당 회차제목 하나씩 가져오기 위한 리스트
roundList = [] # 회차
roundList2 = [] # 댓글 하나당 회차 하나씩 가져오기 위한 리스트
list1 = [5, 7] # div태그 자식 선택자 순서
list2 = [1, 3, 5] # td태그 자식선택자 순서
for m in randomList: # 페이지
# try :
url="https://comic.naver.com/genre/bestChallenge.nhn?m=main&order=ViewCount&page="+str(m)
driver.get(url)
time.sleep(1)
# try :
for k in list1: # div태그 자식 선택자 순서
for i in range(1,5): # 고정값 바꾸면 안됨, tr태그 자식선택자 순서, #1,5
for j in list2: # td태그 자식선택자 순서
div = driver.find_element_by_css_selector("div:nth-child("+str(k)+") > table > tbody > tr:nth-child("+str(i)+") > td:nth-child("+str(j)+") > div.fl > a")
div.click()
time.sleep(1)
cnt_first = driver.find_element_by_class_name("first")
cnt_first.click()
time.sleep(1)
back = 0
num = 0
for page in range(0, 5): # 다음화 넘어가기 반복문
soup = bs(driver.page_source,'lxml')
# 웹툰 제목
title = soup.select("div.detail")[0].text
titleList.append(title.split("\t")[0].split("\n")[1])
# 회차
num += 1
roundList.append(str(num))
# 회차제목
view = soup.select("div.tit_area > div.view")[0].text
viewList.append(view.split("\n")[1])
# 등록일
date = soup.select("dd.date")[0].text
date_list.append(date)
# 조회수
count = soup.select("dd.date")[1].text # 조회수
count_list.append(count)
# 별점
star = soup.select("#topPointTotalNumber > strong")[0].text
star_list.append(star)
driver.switch_to.window(driver.window_handles[-1]) # 댓글 iprame 시작
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser") # 댓글 iprame 여기까지
time.sleep(1)
# 전체 댓글 클릭
all_click = driver.find_element_by_tag_name("#cbox_module_wai_u_cbox_sort_option_tab2 > span.u_cbox_sort_label")
all_click.click()
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
for d in range(4,500): # 댓글 넘기기 반복
for c in range(0,15): # 한페이지 안의 댓글
try:
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
comment = soups.select("span.u_cbox_contents")[c].text
comment_list.append(comment)
comment_date = soups.select("span.u_cbox_date")[c].text
comment_date_list.append(comment_date)
comment_like =soups.select("em.u_cbox_cnt_recomm")[c].text
comment_like_list.append(comment_like)
comment_hate = soups.select("em.u_cbox_cnt_unrecomm")[c].text
comment_hate_list.append(comment_hate)
titleList2.append(title.split("\t")[0].split("\n")[1])
viewList2.append(view.split("\n")[1])
roundList2.append(str(num))
except:
print("댓글정보 가져오기 끝")
try:
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
two_click = driver.find_element_by_tag_name("#cbox_module > div > div.u_cbox_paginate > div > strong + a")
two_click.click()
# time.sleep(0.5)
except:
print("댓글넘기기버튼끝")
# if two_click == None: # 만약 더이상 댓글페이지가 없으면
break # 댓글페이지 넘기기 멈춰
try:
driver.switch_to.window(driver.window_handles[-1]) # 페이지 넘어갈때
next_click = driver.find_element_by_tag_name("div.btn_area > span.next > a")
next_click.click()
back += 1
except:
print("다음화버튼끝")
break
for ba in range(0, back+2): # 뒤로가기 반복문
driver.back()
# except :
# print("종료되었습니다")
view = {"웹툰 제목": titleList,"회차 제목":viewList,"회차": roundList, "등록일" : date_list, "조회수": count_list, "회차별 별점": star_list}
view = pd.DataFrame(view)
commentDic = {"웹툰제목":titleList2,"회차 제목":viewList2, "회차": roundList2,"댓글": comment_list, "댓글 등록일" : comment_date_list, "댓글 좋아요": comment_like_list, "댓글 싫어요": comment_hate_list}
commentDic = pd.DataFrame(commentDic)
# csv파일로 저장
view.to_csv('viewRandom.csv', encoding = "")
commentDic.to_csv('commentDicRandom.csv', encoding = "")
이제 전체 파일을 올렸으니 코드 분석을 해볼거다.
정식연재크롤링이랑 랜덤페이지 크롤링은 코드가 비슷하다.
일단은 차이점을 설명하자면 정식웹툰 크롤링은 다음과 같은 부분이 있는데
div = driver.find_element_by_css_selector("div:nth-child("+str(k)+") > table > tbody > tr:nth-child("+str(i)+") > td:nth-child("+str(j)+") > div.fl > a")
try:
div2 = div.find_element_by_css_selector('span.mark_serial') # 정식연재 선택자
print(div2)
if div2 != None:
div.click()
except : continue
웹툰을 클릭해서 들어가는데 만약 정식연재 태그가 있다면 클릭하고 없으면 다음반복으로 넘어가라는 코드다.
그리고 랜덤페이지 크롤링은 페이지를 랜덤으로 뽑아서 하는거 말고는 차이점이 없다.
이에대해서는 위에서 자세하게 설명했으므로 넘어가겠다.
이제 공통적인 코드부분을 설명할건데 이부분이 길다.
아래 코드로 웹툰의 첫화눌러서 첫화로 이동한다.
cnt_first = driver.find_element_by_class_name("first")
cnt_first.click()
그다음 안에서 웹툰 제목 , 회차제목, 회차 등록일, 회차 조회수, 회차 별점등을 가져오고
웹툰 회차제목에 1화인지 아닌지가 안적힐때가 있으므로
아래와 같이 반복문이 돌아갈때마다 num값을 1씩 증가시키도록 짠다.
num += 1
roundList.append(str(num))
그리고 다음에 댓글 데이터를 가져올건데 댓글데이터는 먼저 전체댓글 더보기를 클릭한다.
all_click = driver.find_element_by_tag_name("#cbox_module_wai_u_cbox_sort_option_tab2 > span.u_cbox_sort_label")
all_click.click()
그리고 댓글 내용, 댓글별 좋아요수, 댓글별 싫어요수를 가져오면 된다.
댓글페이지는 아이프레임으로 되어있어서 가져오는 코드 작성하기전에 꼭 아래 코드를 작성하고 들어가야된다.
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
그러면 이제 댓글 페이지가 1페이지, 2페이지 .....이런데 댓글이 많은 웹툰은 300페이지가 넘기도 하다.
따라서 넉넉하게 499번 반복하게 만들었다.
(참고로 코드 분석해서 설명하는거는 인용문으로 작성하려 했는데 코드가 길어지니 불편해서 그냥 코드 블록으로 설명하도록 하겠다.)
for d in range(1,500): # 댓글 넘기기 반복
try:
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
two_click = driver.find_element_by_tag_name("#cbox_module > div > div.u_cbox_paginate > div > strong + a")
two_click.click()
except:
break # 댓글페이지 넘기기 멈춰
선택자는 #cbox_module > div > div.u_cbox_paginate > div > strong + a를 가져와서 반복했는데
현재가 1페이지면 strong태그 바로 옆 a태그는 2페이지를 가리킨다.
현재가 2페이지면 strong태그 바로 옆 a태그는 3페이지를 가리킨다.
그리고 현재가 10페이지면 다음버튼을 눌러야되는데 이때도 strong태그 바로 옆 a태그이다.
이런식으로 다음으로 넘어갈건 모두 strong태그 바로 옆 a태그이라서 반복문을 그냥 1~500으로 두어도 500페이지까지 이동한다. (499번반복인데 500페이지인 이유는 첫1페이지는 반복에 포함이 아니기때문이다.)
그리고 위의 댓글 페이지 반복문 안에 댓글 정보를 가져오는 반복문을 짠다.
댓글 정보를 가져오는 반복문은 한페이지 안의 댓글은 15개로 고정이므로 범위를 (0, 15)로 두었다.
for c in range(0,15): # 한페이지 안의 댓글
try:
driver.switch_to.window(driver.window_handles[-1])
driver.switch_to.frame("commentIframe")
soups = bs(driver.page_source,"html.parser")
comment = soups.select("span.u_cbox_contents")[c].text
comment_list.append(comment)
comment_date = soups.select("span.u_cbox_date")[c].text
comment_date_list.append(comment_date)
comment_like =soups.select("em.u_cbox_cnt_recomm")[c].text
comment_like_list.append(comment_like)
comment_hate = soups.select("em.u_cbox_cnt_unrecomm")[c].text
comment_hate_list.append(comment_hate)
titleList2.append(title.split("\t")[0].split("\n")[1])
viewList2.append(view.split("\n")[1])
roundList2.append(str(num))
except:
print("댓글정보 가져오기 끝")
이제 댓글 정보 가져오기도 끝나고 댓글 넘기기도 끝났으므로 다음화로 넘어갈거다.
다음화 넘어가기 반복문은 첫화보기 클릭하는 코드 아래에 작성해야한다.
이제 화수는 5화만 가져올거므로 반복문을 range(0, 5)로 작성한다.
for page in range(0, 5): # 다음화 넘어가기 반복문
아래 부분은 아까의 댓글 넘기기 코드 밑에 작성한다.
다음화로 넘어가기 위해서는 다음화버튼을 클릭해줘야한다.
try:
driver.switch_to.window(driver.window_handles[-1]) # 페이지 넘어갈때
next_click = driver.find_element_by_tag_name("div.btn_area > span.next > a")
next_click.click()
back += 1
except:
print("다음화버튼끝")
break
이렇게 한웹툰의 5회차까지의 정보를 가져왔으면 이제 뒤로가기를 해서 다음윕툰으로 넘어가야한다.
이때 뒤로가기는 한번만 작성하면 안되고 우리가 다음화 버튼을 누른 만큼+2번을 해줘야한다.
왜냐하면 다음화 버튼을 누른 만큼만 뒤로가면 웹툰은 첫화 페이지로 가고
거기서 +1번더 뒤로가면 웹툰의 목록으로간다.
따라서 다음화 버튼을 누른만큼+2번을 해줘야 우리가 원하는 홈으로 가진다.
다음화버튼을 얼마나 눌렀는지를 세기위해 위의 다음화버튼 누르는 코드에서 back += 1을 해주었다.
for ba in range(0, back+2): # 뒤로가기 반복문
driver.back()
이렇게하면 이제 코드분석이 모두 끝났다!!!
스마트인재개발원에서 진행된 수업내용입니다.
'프로젝트' 카테고리의 다른 글
웹툰 승격 확률 예측 시스템 프로젝트 - 감정분석2와 단어구름 [광주인공지능학원] (0) | 2021.07.11 |
---|---|
웹툰 승격 확률 예측 시스템 프로젝트 - kosac사전을 이용한 감정분석 [광주인공지능학원] (0) | 2021.07.11 |
웹툰 승격 확률 예측 시스템 프로젝트 - 크롤링1 [스마트인재개발원] (0) | 2021.07.04 |