+) 주 2회 이상 연재 웹툰 때문에 데이터들 불일치 확인
지금은 해결 정리는 프로젝트 끝나고 할 계획
싸피 5기 2학기 세번째 자율 프로젝트를 진행 중이다.
자율 프로젝트는 내가 좋아하는 동생들인 은지, 성안이와 프로젝트를 시작했고 우리 셋이 팀이 결성되면서 프로젝트를 할 주제도 웹툰으로 결정한 상태였다.
이 주제는 누가 들어도 재밌는 주제이지만
가장 큰 문제는 데이터였다.
작년 비슷한 프로젝트를 하며 네이버 웹툰 댓글 크롤링에 실패했던 경험이 있어서 였다.
워낙 해당 주제가 저작권 관련해 큰 이슈들이 있다보니 개발 과정에서도 조금은 까다롭게 만들어 놓았다.
그래서 최우선적으로 네이버 웹툰 댓글 크롤링을 성안(BE)가 맡아줬.... 는데
취업을 하는 바람에 ㅠㅠ
그 외 핵심적인 크롤링을 내가 맡게 되었다.
고로 그 과정을 포스팅 하겠다.
일단, FE 직무를 희망하고 크롤링을 안한지 어언 반년이 지났다. 그래서 그 때 그 기억들을 꺼내느라 고생을 좀 했다.
그리고 또 나름 트렌디한 크롤링을 하겠다는 생각에 셀레니움을 배제하고 크롤링을 알아보기 시작했다.
=> 결국 셀레니움 씀 ㅋ
그리고 크롤링은 성공 했는데 생각지 못한 문제들이 있었다.
1) 셀레니움으로 일일히 하다보니 한번 크롤링 할때 너무 오래걸렸다.
=> 웹툰 파워 점수를 먹였는데 그 요소 중에 최대 10회 분량의 댓글 참여자 수의 평균을 구하는 요소가 있었다.
=> 이거 때문에 네이버 웹툰 (약 470개) 을 크롤링 하는데 장장 3시간이 걸렸다.
=> 결국 이 요소는 빼기로 결정
=> 20분으로 줄었다. 만쉐.
2) 에러 발생시 디버깅 어려움
=> 20분으로 줄은건 사실이지만 무려 약 470번의 사이클을 돌리다보니 도대체 어디서 어떤 에러가 발생했는지 찾기가 어려웠다.
=> 결국 print를 모든 과정에 찍어 내 터미널 창은 계속 미친듯이 일을 하게 되었다.
=> ;; 그리고 주 2회 연재되는 웹툰은 별도로 중복 체크를 해줘야하는데 너무 나중에 관련 에러를 발견해서 응급 처치만 하고 일단 DB로 넘겼다.
3) DB 변동에 따라 계속 변경되는 크롤링 코드
=> 급격한 인원 감소로 우리 기존 서비스 목표와는 많이 달라졌다.
=> 그런데 우리는 괜찮겠거니 하며 ERD에 별도 반영을 안해줬는데
=> 이러다보니 내가 크롤링 코드를 짜는데 뭐도 있어야하네 뭐도 있어야 하네 하며 ERD도 바뀌고 코드도 바뀌는 상황이 발생했다.
=> 매우 비효율 적이라고 생각했다.
=> 결국, 내 코드에 따라 테이블을 거의 갈아엎는 수준의 공사가 들어갔다.(물론 아직 큰 일 까지는 아니였지만 비효율 비효율....)
4) Mysql에 한번에 데이터 넣기
=> 생각해보니 코드로 데이터들을 뽑으면 뭐해
=> 이걸 Mysql에 한번에 넣어야 했다. (일일히 470줄의 sql 코드를 넣을 수는 없으니 ....)
=> 생각보다 쉽게 해결책을 찾았지만 순간 겁을 먹었었다.
+)추가된 어려움 (21.11.10)
5) 배열을 MySql로 넣기
(1) MariaDB는 "'"를 인식하지 못한다...
크롤링 하면서 스토리에서 태성이가 말했다'와하하 대박' 이런식으로 되어있는 경우가 있어 syntax 에러 발생..
(2) 장르에서 [] 하나의 리스트가 추가되어 뽑히는 문제 발생
[0]으로 어거지로 한번 더 까줬다.
https://m.blog.naver.com/stu5073/221801367202
GoldShipCastle(금배성)님의 블로그를 참조 했습니다.
=> 나중에 알고보니 성안이도 이 블로그 코드를 참조했더라...
import requests
from bs4 import BeautifulSoup
#네이버 웹툰 페이지 파싱을 통해 총 정식 연재 작품 수 구하기
URL='https://comic.naver.com/webtoon/weekday.nhn'
html=requests.get(URL).text
soup=BeautifulSoup(html,'html.parser')
title=soup.find_all('a',{'class': 'title'})
title_list=[] ; title_num=[]
print(title)
#일주일 2회 이상 연재 작품은 한 번만 데이터 수집
for x in range(len(title)):
t=title[x].text
if(t in title_list):
continue
else:
title_list.append(t)
=> 여기서 일주일 2회 힌트를 안줬으면 왜 에러가 뜨는지 또 하루종일 고민 했을 꺼다
=> 시간이 없어 일단 응급처치 하고 프로젝트 진행함
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep
import time
import re
URL='https://comic.naver.com/webtoon/weekday.nhn'
driver=webdriver.Chrome('chromedriver.exe')
driver.get(URL)
time.sleep(1)
artist_list=[] ; genre_list=[] ; score_list=[]
star_parti=[] ; img_list=[] ; link_list=[] ; story_list=[]
for i in range(len(title_list)):
time.sleep(1)
#전체 웹툰 목록 중 월요일 첫 번째 웹툰으로 페이지 이동
page=driver.find_elements_by_class_name('title')
page[i].click()
time.sleep(0.5)
#이동한 페이지 주소 읽고, 파싱하기
html = driver.page_source
soup = BeautifulSoup(html,'html.parser')
#작품 썸네일 이미지
img=soup.select('#content > div.comicinfo > div.thumb > a > img')[0]['src']
if img in img_list:
pass
img_list.append(img)
print(img_list)
#작가님 닉네임 수집
artist = soup.find_all('h2')
artist = artist[1].find('span',{'class':'wrt_nm'}).text[8:]
artist_list.append(artist)
print(artist_list)
#작품 링크
link=soup.select('#content > div.comicinfo > div.thumb > a')[0]['href']
link_list.append('https://comic.naver.com'+link)
print(link_list)
#줄거리 링크
story=soup.select('#content > div.comicinfo > div.detail > p:nth-child(2)')
story_list.append(story)
print(story_list)
#작품 장르 수집
genre=soup.find('span',{'class':'genre'}).text
genre_list.append([genre])
print(genre_list)
#최신 별점 평균 점수 수집 (최대 10화 분량)
score = soup.find_all('strong')
# print(score)
# print(score)
scorelist=[] ; ii=9
while score[ii].text[0].isnumeric()==True:
scorelist.append(float(score[ii].text))
ii +=1
if len(scorelist) == 3:
break
score_list.append(sum(scorelist)/len(scorelist))
print(score_list)
time.sleep(0.5)
#각 회차를 돌며 댓글 별점 참여자 수집
###########################################################################################
# length=driver.find_elements_by_class_name('title')
# for j in range(1, len(length)):
# #해당 페이지의 회차 모두 가져오기
# titlenum=driver.find_elements_by_class_name('title')
# time.sleep(0.5)
# webnum=[y.text for y in titlenum]
# enterToon = driver.find_elements_by_partial_link_text(webnum[j])
# time.sleep(0.5)
# enterToon[0].click()
# pre = []
# html = driver.page_source
# soup = BeautifulSoup(html,'html.parser')
# # 별점
# star_p=soup.select('#topTotalStarPoint > span.pointTotalPerson > em')[0].text
# print(star_p)
# pre.append(int(star_p))
# #페이지 뒤로 가기, 다시 만화 목록으로
# driver.back()
# time.sleep(1)
# titlenum.clear()
# enterToon.clear()
# star_parti.append(sum(pre) / len(pre))
# print(star_parti)
#############################################################################################
print(title_list[i],"end")
driver.back()
time.sleep(0.5)
page.clear()
time.sleep(0.5)
webtoon_data = []
=> sleep은 너무 막 이렇게 셀레니움 갈기다 보면 페이지가 뜨기도 전에 눌러서 에러가 발생하는걸 방지
print("작가 리스트 길이 :", len(artist_list))
print("장르 리스트 길이 :", len(genre_list))
print("평점 리스트 길이 :", len(score_list))
# print("별점참여자 리스트 길이 :", len(star_parti))
print("이미지 리스트 길이 :", len(img_list))
print("링크 리스트 길이 :", len(link_list))
print("스토리 리스트 길이 :", len(story_list))
=> 결과값 확인
이하 jh-0323님의 블로그를 참고했습니다.
CREATE TABLE webtoon (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
platform_id int DEFAULT NULL,
title varchar(50) DEFAULT NULL,
link char(255) DEFAULT NULL,
img_link char(255) DEFAULT NULL,
genre char(50) DEFAULT NULL,
story char(255) DEFAULT NULL,
artist char(50) DEFAULT NULL,
score float(10) DEFAULT NULL) ;
=> 기록해두자 기본 테이블 만드는 매크로 sql 문
data = []
# print(len(title_list))
for i in range(len(link_list)):
# print(title_list[i])
# print(link_list[i])
# print(img_list[i])
# print(genre_list[i][0])
# print(story_list[i])
# print(artist_list[i])
# print(score_list[i])
data.append([1, title_list[i], link_list[i], img_list[i], genre_list[i][0], '줄거리', artist_list[i], score_list[i]])
print(data)
=> Mysql로 보내기 위해 데이터들을 배열로 만들기
=> 줄거리는 ' 문제로 그냥 일단 떼우기 ....
data = [[1, '이태성', 'www', 'www', '액션', '멋쟁이', '천재'], [1, '김한길', 'www', 'www', '액션', '멋쟁이', '천재']]
import pymysql
connect = pymysql.connect(host='localhost', user='아이디', password='비밀번호', db='디비명', charset='utf8mb4')
cursor = connect.cursor()
for r in data:
platform_id = int(r[0])
title = str(r[1])
link = str(r[2])
img_link = str(r[3])
genre = str(r[4])
story = str(r[5])
artist = str(r[6])
sql = """insert into test
(platform_id, title, link, img_link, genre, story, artist)
values (%d, '%s', '%s', '%s', '%s', '%s', '%s')
""" % (platform_id, title, link, img_link, genre, story, artist)
cursor.execute(sql)
connect.commit()
connect.close()
=> test 배열을 만들고 코드에 적용하기 (아직 벡엔드에서 db 테이블 변경하는거 기다리는 중 ....)
import pymysql
connect = pymysql.connect(host='54.166.95.144', user='ssafy', password='ssafyssafy(password)', db='webtoon', charset='utf8mb4')
cursor = connect.cursor()
for r in data:
platform_id = int(r[0])
title = str(r[1])
link = str(r[2])
img_link = str(r[3])
genre = str(r[4])
story = str(r[5])
artist = str(r[6])
score = float(r[7])
sql = """insert into webtoon
(platform_id, title, link, image_link, genre, story, artist, score)
values (%s, '%s', '%s', '%s', '%s', '%s', '%s', '%s')
""" % (platform_id, title, link, img_link, genre, '줄거리', artist, score)
cursor.execute(sql)
connect.commit()
connect.close()
(실제 반영된 코드)
=> 여기서 에러가 떠서 코치님 도움을 요청했는데
=> 디비명을 workbench 키면 나오는 네모 상자의 제목으로 생각하고 계속 했다. 그래서 db를 계속 못 찾았음
=> 그 작은 네모는 들어가서 나오는 db 명으로 해야한다
=> 그리고 형식들 int, str 들은 꼼꼼히 맞춰주어야하고
=> %d 는 int, %s 는 str => 가 아니다 ;;;
=> 이렇게 하니 소수점이 중요한 score가 정수로 mysql에 들어갔다. 그래서 찾아보니 이런 기능을 하는 친구가 아니였다!
https://jaeyung1001.tistory.com/48
=> 그냥 %s로 하니까 소수점들도 잘 들어감 ..
=> 앗;; 이렇게 정리하는 도중에도 오타를 발견했다 ;;
취업한 성안이 ㅠㅠ