프로젝트 소개
웹툰 승격 확률 예측 시스템 프로젝트를 하고 있습니다.
네이버 베도 작품들중 정식웹툰으로 승격된 작품과 승격이 되지 않은 작품들을 비교하여
아직 정식이 되지못한 베도 작품들이 승격될 확률이 얼마나 있는지를 예측해주는 웹사이트를 만들겁니다.
일단 데이터를 수집은 크롤링으로 하고 예측은 머신러닝으로 웹사이트 제작은 자바로 할예정입니다.
역할 분담은 DB, 크롤링, 데이터분석, 머신러닝, 웹디자인, 웹기능구축으로 나뉘는데
저는 크롤링, 데이터분석, 머신러닝 파트를 맡아서 티스토리에는 이 3파트를 정리할 예정입니다.
먼저 데이터를 수집에서 가져올 데이터는 웹툰 제목, 장르, 종합 별점, 종합 좋아요수, 정식연재 유무를 판단하기 위해 정식연재 확정된 작품의 제목, 회차별 조회수, 회차별 제목, 회차별 별점, 회차별 좋아요수, 회차별 등록일, 회차별 댓글, 댓글 좋아요수, 댓글 싫어요수, 정식웹툰 확정일자, 웹툰 썸네일 이미지를 크롤링 할겁니다.
여기서 회차별이란 1화부터 5화까지만 가져올겁니다.
전부 가져오기에는 댓글의 양이 너무 많기도 하고 회차가 100화가 넘는 만화가 있는 반면 5화밖에 안되는 만화도 있고, 베도처럼 웹툰 지망생의 만화의 경우 초기에 얼마나 사람을 끌어잡을 수 있느냐가 정식웹툰이 되는데 영향을 끼칠거라 예상하기 때문입니다.
그럼 먼저 웹툰 장르, 제목, 종합 좋아요수, 종합 별점, 이미지를 크롤링 할겁니다.
웹툰 장르, 제목, 종합 좋아요수, 종합 별점, 이미지 크롤링
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
import requests as req
import os # 파일 시스템을 위한 라이브러리 ex) 파일, 폴더를 생성 삭제
from urllib.request import urlretrieve # 이미지의 경로를 파일로 저장
from tqdm import tqdm_notebook as tq
먼저 위의 라이브러리들을 임폴트 시켜주세요
driver = wb.Chrome()
likeList = [] # 전체 좋아요
starList = [] # 전체별점
titleList = [] # 제목
genreList = [] # 장르
imgSrc = [] # 이미지
list1 = [4, 6] # div태그 자식 선택자 순서
list2 = [1, 3, 5] # td태그 자식선택자 순서
list3 = [ "episode", "omnibus", "story", "daily", "comic", "fantasy", "action","drama", "pure",
"sensibility", "thrill", "historical", "sports"] # 장르별 url
for o in list3: # 장르별 페이지
try :
for m in range(1, 86): # 페이지
url="https://comic.naver.com/genre/bestChallenge.nhn?m="+o+"&order=ViewCount&page="+str(m)
driver.get(url)
time.sleep(1)
# 이미지 저장
soup = bs(driver.page_source, 'lxml')
img = soup.select("div.fl > a > img")
img[0]["src"]
for im in img:
imgSrc.append(im["src"])
for k in list1: # div태그 자식 선택자 순서
for i in range(1,5): # 고정값 바꾸면 안됨, tr태그 자식선택자 순서, #1,5
for j in list2:
star = driver.find_element_by_css_selector("div:nth-child("+str(k)+") > table > tbody > tr:nth-child("+str(i)+") > td:nth-child("+str(j)+") > div.challengeInfo > div.rating_type > strong").text
starList.append(star)
title = driver.find_element_by_css_selector("div:nth-child("+str(k)+") > table > tbody > tr:nth-child("+str(i)+") > td:nth-child("+str(j)+") > div.challengeInfo > h6 > a").text
titleList.append(title)
genre = driver.find_element_by_css_selector("#content > h3.title").text.strip()
genreList.append(genre.split("\n")[0])
time.sleep(1)
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)
like = driver.find_element_by_css_selector("em.u_cnt").text
likeList.append(like)
time.sleep(1)
driver.back()
time.sleep(1)
except :
print("종료되었습니다")
dic = {"장르":genreList,"제목": titleList,"좋아요" : likeList, "별점": starList}
dic = pd.DataFrame(dic)
위의 코드를 설명하면
for o in list3: # 장르별 페이지
try :
for m in range(1, 50): # 페이지
url="https://comic.naver.com/genre/bestChallenge.nhn?m="+o+"&order=ViewCount&page="+str(m)
driver.get(url)
이부분은 크롬드라이버 보고 해당 페이지로 이동하라는 코드인데
네이버 베도의 경우 url주소가 웹툰을 조회순으로 설정하고 페이지가 1페이지면 다음과 같다.
https://comic.naver.com/genre/bestChallenge.nhn?m=main&order=ViewCount&page=1
여기서 이제 장르를 누르면 url이 다음과 같이 된다.
https://comic.naver.com/genre/bestChallenge.nhn?m=episode&order=ViewCount&page=2
위의 episode로 장르를 구별할 수 있고 page=2로 페이지를 구별할 수 있다.
따라서 나는 장르를 list3 = ["episode", "omnibus", "story", "daily", "comic", "fantasy", "action","drama", "pure", "sensibility", "thrill", "historical", "sports"] 리스트로 묶어서 반복시키고 페이지는 최대가 85페이지라 1부터 85까지 반복되게 만들었다.
그리고 장르마다 페이지가 달라서 제일 페이지가 많은 장르는 85페이지지만 3페이지밖에 없는 장르도 있기에 오류가 나지않게 try , except로 예외처리를 하였다.
페이지나 장르의 경우 버튼을 클릭하여 이동하게 만들수도 있었지만 이편이 코드가 더 간단하여서 이렇게 작성하였다.
그리고
# 이미지 저장
soup = bs(driver.page_source, 'lxml')
img = soup.select("div.fl > a > img")
img[0]["src"]
for im in img:
imgSrc.append(im["src"])
는 이미지를 가져오기 위한 코드이다.
imgSrc라는 리스트에 각각의 이미지 경로를 저장하는 코드다.
그리고 이미지를 제외하고 위의 코드에서 크롤링하고자 하는 부분들을 보여주면 다음과 같다.
먼저 페이지 마다 이동하면서 장르, 제목, 별점을 가져온후 썸네일 부분을 클릭하여 들어가서 좋아요수를 크롤링한후 다시 뒤로가기해서 다음 웹툰의 장르, 제목, 별점을 가져오는걸 반복한다.
클릭하여 들어가는 코드는 아래 코드이고
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()
다시 뒤로 돌아가는 코드는 아래 코드다.
driver.back()
참고로 time.sleep(1)은 혹시 컴퓨터가 클릭하여 들어가기 전에 먼저 정보를 가져오려 할까봐 넣어주었는데 굳이 할 필요는 없다.
# 웹툰 제목으로 이미지 저장
# 이미지를 저장할 폴더 생성
if not os.path.isdir("C:/Users/SM2119/Desktop/이미지2") :
print('폴더 생성 완료')
os.mkdir("C:/Users/SM2119/Desktop/이미지2")
else:
print("이미 동일한 이름의 폴더가 있습니다")
# 이미지 제목에 들어갈 웹툰 제목의 특수문자 제거
new_string = []
count=0
for i in titleList:
new_string.append(''.join(filter(str.isalnum, i)))
count += 1
# 파일의 이름 생성
fileNo = 0
for i in tq(range(len(imgSrc))):
urlretrieve(imgSrc[i], "C:/Users/SM2119/Desktop/이미지2/"+str(new_string[i])+".jpg")
fileNo += 1
time.sleep(1)
위의 코드는 아까 이미지 경로를 저장한 리스트를 이용하여 내 바탕화면의 폴더에 이미지를 저장하기 위한 코드다.
내 바탕화면 주소를 넣고 만약 이미지2라는 이름의 폴더가 없으면 폴더를 생성하고 없으면 이미 폴더가 있다고 알려준다.
그리고 웹툰 썸네일을 가져온거이므로 웹툰이름을 이미지 이름으로 설정할건데 웹툰 제목에 특수문자가 들어갈 경우 이미지 이름으로 설정하지 못하니 이름 생성전에 웹툰 제목에서 특수문자를 제거 해준다.
그리고 이미지에 이름을 생성하여 이미지를 이미지2폴더에 저장해준다.
# 중목된 행(row)삭제
dic.drop_duplicates(inplace=True)
이미지를 제외한 아까 위의 크롤링은 dic라는 데이터 프레임으로 만들었었는데
반복을 하다가 실수로 중복이 들어갔다.
따라서 중복제거 함수를 사용하여 중복된 행을 삭제해 주었다.
# csv파일로 저장
dic.to_csv('webtoon.csv', encoding = "")
그다음 dic 데이터 프레임을 csv파일로 저장해주면 된다.
이렇게 웹툰 장르, 제목, 종합 좋아요수, 종합 별점, 이미지 크롤링이 끝났다.
정식연재 작품이름 크롤링
이제 정식연재 작품의 이름만 크롤링 할거다.
정식연재가 된작품인지 아닌지는 베스토 도전 페이지의 썸네일을 보면 저렇게 빨간색 딱지가 붙어있다.
아래 딱지부분을 선택자로 가져와서 크롤링 하면 된다.
검사를 눌러서 선택자르 확인해보면
mark_serial 클래스에 정식연재 작품이라는 태그가 있고 같은 위치에 img태그에 작품의 이름이 있는걸 확인 할 수 있다.
따라서 .mark_serial에서 부모선택자로 가고 부모 선택자에서 자식선택자인 img로가서 title부분을 크롤링 해오면 된다.
driver = wb.Chrome()
# 좋아요
list1 = [4, 6] # div태그 자식 선택자 순서
list2 = [1, 3, 5] # td태그 자식선택자 순서
list3 = ["episode", "omnibus", "story"] # 장르별 url
officialList = [] # 정식웹툰
cnt = 0
for o in list3: # 장르별 페이지
try :
for m in range(1, 86): # 페이지
url="https://comic.naver.com/genre/bestChallenge.nhn?m="+o+"&order=ViewCount&page="+str(m)
driver.get(url)
time.sleep(1)
soup = bs(driver.page_source, 'lxml')
check = soup.select("span.mark_serial") # 정식연재 선택자
for el in check:
chc = el.parent
official = chc.select_one('img')
if official != None:
officialList.append(official['title'])
except :
print("종료되었습니다")
dic2 = {"정식연재":officialList}
dic2 = pd.DataFrame(dic2)
# csv파일로 저장
dic2.to_csv('officialTitle4.csv', encoding = "")
저는 위에서 장르별 페이지 만든 코드를 재사용하였기 때문에 저렇게 작성했지만 장르구별하지않고 메인 페이지에서 하면 더 빠릅니다.
위에서 .mark_serial에서 부모선택자로 가고 부모 선택자에서 자식선택자인 img로가서 title부분을 크롤링한다고 설명했는데 check변수에 .mark_serial선택자를 담고 한 페이지에 정식연재가 여러개이면 check가 리스트 형식일거기에 for문으로 작성하였다.
부모 선택자로 올라가는 함수는 .parent이다. 그렇게 부모 선택자인 a태그로 올라갔을 거다.
그러면 chc.select_one('img')로 a태그에서 img태그로 내려가고 official에서 우리가 필요한 부분은 title이기 때문에 officialList.append(official['alt'])으로 작성해주면 된다.
참고로 혹시 페이지 내에 정식웹툰이 없으면 official값이 None값일거다. 따라서 if문으로 None값이 아닐때라고 조건문을 걸어주었다. 물론 선택자가 없다면 예외처리 문으로 빠지므로 굳이 해줄 필요는 없다. (예외처리문은 꼭 해줘야 한다.)
이부분은 크롤링은 아니지만 두 파일을 크롤링할때 같이 진행하였으므로 여기에 작성하겠다.
다음으로 위에서 크롤링한 두 csv파일을 이용하여 webtoon.csv파일에 정식연재 라는 컬럼을 만들어서 정식연재이면 1, 정식연재가 아니면 0으로 만들어 줄거다.
아래와 같이 하면 된다.
df3 = pd.read_csv('webtoon.csv', encoding = "CP949")
# 정식연재 유무 컬럼 만들기
df3['정식연재'] = 0 #정식연재가 아니면 0으로 할거므로 처음에는 전부 0으로 지정
officialTitle = pd.read_csv("officialTitle4.csv", encoding = "euc-kr")
ofTitle = officialTitle['정식연재'].unique() # 정식연재 이름이 담긴 리스트
# 만약 정식연재 이름이 df3에 있으면 df3의 정식연재 컬럼의 값을 1로 변경
or i in range(0, len(df3)):
if df3.iloc[i,1] in ofTitle :
df3.iloc[i,4] = 1
# 위에서 webtoon.csv파일에서 정식연재 컬럼을 만들고난 df3파일을 '장르함침csv'파일로 저장
df3.to_csv('장르합침.csv', encoding="")
스마트인재개발원에서 진행된 수업내용입니다.
'프로젝트' 카테고리의 다른 글
웹툰 승격 확률 예측 시스템 프로젝트 - 감정분석2와 단어구름 [광주인공지능학원] (0) | 2021.07.11 |
---|---|
웹툰 승격 확률 예측 시스템 프로젝트 - kosac사전을 이용한 감정분석 [광주인공지능학원] (0) | 2021.07.11 |
웹툰 승격 확률 예측 시스템 프로젝트 - 크롤링2 [스마트인재개발원] (0) | 2021.07.04 |