Merge branch 'naverNews' into 'master'
Naver news Naver News 크롤링 결과 See merge request !2
Showing
2 changed files
with
306 additions
and
0 deletions
naverNews/naverNews.md
0 → 100644
| 1 | +1. Data 받아오기 | ||
| 2 | + 1) selenuim을 이용하여 웹페이지에서 데이터를 검색 | ||
| 3 | + 2) 원하는 URL 입력받는다 | ||
| 4 | + 3) headless하게 구현하기 위해 chrome option 적용하여 driver 생성 | ||
| 5 | + 4) naverNews는 댓글 영역 하단 부 '더보기'를 지속적으로 눌러줘야하므로 | ||
| 6 | + driver의 find_element_by_css_selector함수로 해당 class인 | ||
| 7 | + u_cbox_btn_more을 페이지가 끝날 때까지 돌림 | ||
| 8 | + 5) 위의 과정에서 얻은 페이지 소스를 beautifulSoup을 이용하여, find_all을 통해 {사용자ID, 댓글, 작성시간}의 데이터를 각각 raw하게 뽑음. (naverNews의 제한적인 특징으로 사용자ID 뒤 4자리는 비공개처리됨) | ||
| 9 | + | ||
| 10 | +2. 사용할 DataSet으로 가공 | ||
| 11 | + 1) 리스트 형태로 각각 nicknames(사용자ID), comments(댓글), times(작성시간)을 뽑아냄 | ||
| 12 | + 2) 세 리스트에서 짝을 이루는 쌍을 dictionary형태로 {사용자ID, 댓글, 작성시간} 다음과 같이 저장 | ||
| 13 | + 3) 저장된 dictionary list(info_dic)을 최종 결과 리스트인 naverNewsList에 저장한다. | ||
| 14 | + | ||
| 15 | +3. 함수 구현 | ||
| 16 | + 1) KEYWORD 기반 검색 기능 | ||
| 17 | + 2) 가장 자주 나온 단어 검색 기능 | ||
| 18 | + 3) ID 기반 검색 기능 | ||
| 19 | + 4) 시간 대별 검색 기능 | ||
| 20 | + 등 여러 함수 구현 예정 | ||
| 21 | + | ||
| 22 | +=> 수정사항 | ||
| 23 | + | ||
| 24 | + data를 get하여 정제하는 파일을 모듈로 분리해 내어 list형태로 저장된 데이터셋을 반환하여 | ||
| 25 | + main 에서 사용할 수 있도록 한다. 이 후 main에서 리스트를 받아와 url을 입력받아 데이터를 | ||
| 26 | + 받아오는 방식으로 사용한다. 이 후, keyword기반, id기반, 시간대 기반 검색 함수를 구현하였고 | ||
| 27 | + 시간대별 검색 함수의 기능 보강과 가장 자주 나온 단어 검색 기능을 추가 구현할 예정이다. | ||
| 28 | + | ||
| 29 | +* 4차 수정사항 | ||
| 30 | + | ||
| 31 | + 기존파일의 분리 관리 시, import관련 오류 문제 해결 완료(하나의 파일로 관리) | ||
| 32 | + 사용자 UI의 틀을 구축해놓았고, 곧바로 함수별 추가 세부 구현 예정 | ||
| 33 | + | ||
| 34 | +* 5차 수정사항 | ||
| 35 | + | ||
| 36 | + 1) 네이버 댓글공간엑서 받아온 날짜 정보를 YYYY-MM-DD형식으로 바꿈. ('방금 전, 몇 분 전, 몇 시간 전, 몇 일 전'의 경우를 처리하기 위해 dateTime과 timeDelta 모듈을 활용하여 | ||
| 37 | + 현재 날짜를 기준으로 계산하여 YYYY-MM-DD로 저장될 수 있도록 | ||
| 38 | + 코드 추가) | ||
| 39 | + 2) 시간대별로 (시작시간, 끝시간)을 입력하여 그 시간에 해당하는 기사를 출력해주는 함수 구현 | ||
| 40 | + | ||
| 41 | + 가장 자주 많이 나온 단어 검색과 MATPLOTLIB을 활용한 시각적 표현 구현 예정 | ||
| 42 | + | ||
| 43 | +* 6차 수정사항 | ||
| 44 | + | ||
| 45 | + konlpy를 활용한 명사 추출 및 단어 빈도수가 많으 순대로 사용자가 입력한 limit만큼 출력해주는 함수 구현 완료 | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
naverNews/naverNews_crawling.py
0 → 100644
| 1 | +from selenium import webdriver | ||
| 2 | +from selenium.common import exceptions | ||
| 3 | +from bs4 import BeautifulSoup | ||
| 4 | +from datetime import datetime, timedelta | ||
| 5 | +from konlpy.tag import Twitter | ||
| 6 | +from collections import Counter | ||
| 7 | +import time | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +def getData(url): | ||
| 11 | + ## chrome option걸기 (headless하게 웹 크롤링 수행하기 위해<웹페이지 안보이게 하기>) | ||
| 12 | + options = webdriver.ChromeOptions() | ||
| 13 | + #options.add_argument('headless') | ||
| 14 | + #options.add_argument("disable-gpu") | ||
| 15 | + #_url = "https://entertain.naver.com/ranking/comment/list?oid=144&aid=0000642175" # 크롤링할 URL | ||
| 16 | + _url = url # 크롤링할 URL | ||
| 17 | + webDriver = "C:\\Users\\user\\Desktop\\chromedriver_win32\\chromedriver.exe" # 내 웹드라이버 위치 | ||
| 18 | + driver = webdriver.Chrome(webDriver,chrome_options=options) | ||
| 19 | + #driver = webdriver.Chrome(webDriver) | ||
| 20 | + driver.get(_url) | ||
| 21 | + pageCnt = 0 | ||
| 22 | + driver.implicitly_wait(3) # 페이지가 다 로드 될때까지 기다리게함 | ||
| 23 | + try: | ||
| 24 | + while True: # 댓글 페이지 끝날때까지 돌림 | ||
| 25 | + #driver의 find_element_by_css_selector함수로 '네이버 뉴스'의 댓글 '더보기' 버튼을 찾아서 계속 클릭해준다(끝까지) | ||
| 26 | + driver.find_element_by_css_selector(".u_cbox_btn_more").click() | ||
| 27 | + pageCnt = pageCnt+1 | ||
| 28 | + | ||
| 29 | + except exceptions.ElementNotVisibleException as e: # 페이지가 끝남 | ||
| 30 | + pass | ||
| 31 | + | ||
| 32 | + except Exception as e: # 다른 예외 발생시 확인 | ||
| 33 | + print(e) | ||
| 34 | + | ||
| 35 | + pageSource = driver.page_source # 페이지 소스를 따와서 | ||
| 36 | + result = BeautifulSoup(pageSource, "lxml") # 빠르게 뽑아오기 위해 lxml 사용 | ||
| 37 | + | ||
| 38 | + # nickname, text, time을 raw하게 뽑아온다 | ||
| 39 | + comments_raw = result.find_all("span", {"class" : "u_cbox_contents"}) | ||
| 40 | + nicknames_raw = result.find_all("span", {"class" : "u_cbox_nick"}) | ||
| 41 | + times_raw = result.find_all("span", {"class" : "u_cbox_date"}) | ||
| 42 | + | ||
| 43 | + # nickname, text, time 값 만을 뽑아내어 리스트로 정리한다 | ||
| 44 | + comments = [comment.text for comment in comments_raw] | ||
| 45 | + nicknames = [nickname.text for nickname in nicknames_raw] | ||
| 46 | + times = [time.text for time in times_raw] | ||
| 47 | + | ||
| 48 | + naverNewsList = [] | ||
| 49 | + | ||
| 50 | + for i in range(len(comments)): | ||
| 51 | + info_dic = {'userID' : nicknames[i], 'comment' : comments[i], 'time' : times[i]} | ||
| 52 | + naverNewsList.append(info_dic) | ||
| 53 | + | ||
| 54 | + return naverNewsList | ||
| 55 | + #driver.quit() | ||
| 56 | + | ||
| 57 | +from time import sleep | ||
| 58 | + | ||
| 59 | +def print_cList(c_List) : | ||
| 60 | + for item in c_List : | ||
| 61 | + print(item) | ||
| 62 | + | ||
| 63 | +def search_by_author(c_List,user_ID) : | ||
| 64 | + result_List = [] | ||
| 65 | + for item in c_List : | ||
| 66 | + #print(item['userID']) | ||
| 67 | + if ( user_ID in item['userID']) : | ||
| 68 | + result_List.append(item) | ||
| 69 | + return result_List | ||
| 70 | + | ||
| 71 | +def search_by_keyword(c_List,keyword) : | ||
| 72 | + result_List = [] | ||
| 73 | + for item in c_List : | ||
| 74 | + #print(item['comment']) | ||
| 75 | + if ( keyword in item['comment']) : | ||
| 76 | + result_List.append(item) | ||
| 77 | + return result_List | ||
| 78 | + | ||
| 79 | +def refine_time(c_List): # 시간에서 몇일 전, 몇 분 전, 방금 전 등의 형태를 YYYY.MM.DD로 바꿔준다 | ||
| 80 | + now = datetime.now() | ||
| 81 | + | ||
| 82 | + for item in c_List: | ||
| 83 | + if (item['time'].find('전') != -1): # ~~전이 있으면 | ||
| 84 | + if (item['time'].find('일 전') != -1): # ~일 전이라면 | ||
| 85 | + _day = -(int)(item['time'][0]) # 몇 일전인지에 대한 정수형 변수 | ||
| 86 | + tempTime = now + timedelta(days=_day) | ||
| 87 | + item['time'] = str(tempTime) | ||
| 88 | + item['time'] = item['time'][0:10] | ||
| 89 | + continue | ||
| 90 | + elif (item['time'].find('시간 전') != -1): | ||
| 91 | + _index = item['time'].index('시') | ||
| 92 | + _time = -(int)(item['time'][0:_index]) # 몇 시간 전인지에 대한 정수형 변수 | ||
| 93 | + tempTime = now + timedelta(hours = _time) | ||
| 94 | + item['time'] = str(tempTime) | ||
| 95 | + item['time'] = item['time'][0:10] | ||
| 96 | + continue | ||
| 97 | + elif (item['time'].find('분 전') != -1): | ||
| 98 | + _index = item['time'].index('분') | ||
| 99 | + _minute = -(int)(item['time'][0:_index]) # 몇 분 전인지에 대한 정수형 변수 | ||
| 100 | + tempTime = now + timedelta(minutes = _minute) | ||
| 101 | + item['time'] = str(tempTime) | ||
| 102 | + item['time'] = item['time'][0:10] | ||
| 103 | + continue | ||
| 104 | + elif (item['time'].find('방금 전') != -1): | ||
| 105 | + tempTime = now | ||
| 106 | + item['time'] = str(tempTime) | ||
| 107 | + item['time'] = item['time'][0:10] | ||
| 108 | + continue | ||
| 109 | + else: | ||
| 110 | + item['time'] = item['time'][0:10] | ||
| 111 | + continue | ||
| 112 | + | ||
| 113 | + | ||
| 114 | + | ||
| 115 | + | ||
| 116 | + | ||
| 117 | +def search_by_time(c_List,startTime, endTime) : | ||
| 118 | + result_List = [] | ||
| 119 | + | ||
| 120 | + startYear = int(startTime[0:4]) | ||
| 121 | + | ||
| 122 | + if (int(startTime[5]) == 0): # 한자리의 월일 때 | ||
| 123 | + startMonth = int(startTime[6]) | ||
| 124 | + else: | ||
| 125 | + startMonth = int(startTime[5:7]) | ||
| 126 | + | ||
| 127 | + if (int(startTime[8]) == 0): # 한자리의 일일 때 | ||
| 128 | + startDay = int(startTime[9]) | ||
| 129 | + else: | ||
| 130 | + startDay = int(startTime[8:10]) | ||
| 131 | + | ||
| 132 | + | ||
| 133 | + | ||
| 134 | + endYear = int(endTime[0:4]) | ||
| 135 | + | ||
| 136 | + if (int(endTime[5]) == 0): # 한자리의 월일 때 | ||
| 137 | + endMonth = int(endTime[6]) | ||
| 138 | + else: | ||
| 139 | + endMonth = int(endTime[5:7]) | ||
| 140 | + | ||
| 141 | + if (int(endTime[8]) == 0): # 한자리의 일일 때 | ||
| 142 | + endDay = int(endTime[9]) | ||
| 143 | + else: | ||
| 144 | + endDay = int(endTime[8:10]) | ||
| 145 | + | ||
| 146 | + for item in c_List: | ||
| 147 | + itemYear = int(item['time'][0:4]) | ||
| 148 | + | ||
| 149 | + if (int(item['time'][5]) == 0): # 한자리의 월일 때 | ||
| 150 | + itemMonth = int(item['time'][6]) | ||
| 151 | + else: | ||
| 152 | + itemMonth = int(item['time'][5:7]) | ||
| 153 | + | ||
| 154 | + if (int(item['time'][8]) == 0): # 한자리의 일일 때 | ||
| 155 | + itemDay = int(item['time'][9]) | ||
| 156 | + else: | ||
| 157 | + itemDay = int(item['time'][8:10]) | ||
| 158 | + | ||
| 159 | + if (itemYear >= startYear and itemYear <= endYear): | ||
| 160 | + if (itemMonth >= startMonth and itemMonth <= endMonth): | ||
| 161 | + if(itemDay >= startDay and itemDay <= endDay): | ||
| 162 | + result_List.append(item) | ||
| 163 | + | ||
| 164 | + return result_List | ||
| 165 | + | ||
| 166 | +def printMostShowed(c_List,limit): | ||
| 167 | + temp = "" | ||
| 168 | + result = "" | ||
| 169 | + for item in c_List: | ||
| 170 | + temp = str(item['comment']) + " " | ||
| 171 | + result = result + temp | ||
| 172 | + | ||
| 173 | + sp = Twitter() | ||
| 174 | + | ||
| 175 | + nouns = sp.nouns(result) | ||
| 176 | + | ||
| 177 | + _cnt = Counter(nouns) | ||
| 178 | + | ||
| 179 | + tempList = [] | ||
| 180 | + repCnt = 0 | ||
| 181 | + | ||
| 182 | + for i,j in _cnt.most_common(limit): | ||
| 183 | + print(str(repCnt+1)+'. '+str(i)+" : "+str(j)) | ||
| 184 | + repCnt += 1 | ||
| 185 | + | ||
| 186 | +def printResult(c_List): | ||
| 187 | + for i in range(0,len(c_List)): | ||
| 188 | + print(c_List[i]) | ||
| 189 | + | ||
| 190 | +def main (): | ||
| 191 | + ## 시작화면 | ||
| 192 | + | ||
| 193 | + _star = '*' | ||
| 194 | + print(_star.center(30,'*')) | ||
| 195 | + print('\n') | ||
| 196 | + headString = '< Naver News Crawling >' | ||
| 197 | + print(headString.center(30,'*')) | ||
| 198 | + print('\n') | ||
| 199 | + print(_star.center(30,'*')) | ||
| 200 | + | ||
| 201 | + | ||
| 202 | + # 검색하고자 하는 url을 입력받는다 | ||
| 203 | + _url = input('검색하고자 하는 url을 입력해주세요: ') | ||
| 204 | + print('comment_list를 가져오는 중.....') | ||
| 205 | + cList = getData(_url) | ||
| 206 | + refine_time(cList) | ||
| 207 | + #printMostShowed(cList,10) | ||
| 208 | + print('\n') | ||
| 209 | + print('comment_list를 다 가져왔습니다!') | ||
| 210 | + | ||
| 211 | + while(True): | ||
| 212 | + print('***********************************') | ||
| 213 | + print('1.닉네임 기반 검색') | ||
| 214 | + print('2.키워드 기반 검색') | ||
| 215 | + print('3.작성시간 기반 검색') | ||
| 216 | + print('4.자주 나타난 단어 출력') | ||
| 217 | + menu = input('메뉴를 입력해주세요: ') | ||
| 218 | + | ||
| 219 | + if(menu == str(1)): | ||
| 220 | + print('***********************************') | ||
| 221 | + inputID = input('검색할 닉네임 앞 4자리를 입력해주세요(전 단계로 가시려면 -1을 입력해주세요): ') | ||
| 222 | + if(inputID == str(-1)): | ||
| 223 | + continue | ||
| 224 | + _result = search_by_author(cList,inputID) | ||
| 225 | + printResult(_result) | ||
| 226 | + print(_result) | ||
| 227 | + elif(menu == str(2)): | ||
| 228 | + print('***********************************') | ||
| 229 | + inputKW = input('검색할 키워드를 입력해주세요(전 단계로 가시려면 -1을 입력해주세요): ') | ||
| 230 | + if(inputKW == str(-1)): | ||
| 231 | + continue | ||
| 232 | + _result = search_by_keyword(cList,inputKW) | ||
| 233 | + printResult(_result) | ||
| 234 | + elif(menu == str(3)): | ||
| 235 | + print('***********************************') | ||
| 236 | + print('전 단계로 돌아가시려면 -1을 입력해주세요') | ||
| 237 | + startTime = input('검색할 시간대의 시작일을 입력해주세요(YYYY-MM-DD): ') | ||
| 238 | + endTime = input('검색할 시간대의 마지막 일을 입력해주세요(YYYY-MM-DD): ') | ||
| 239 | + | ||
| 240 | + if(startTime == str(-1) or endTime == str(-1)): | ||
| 241 | + continue | ||
| 242 | + | ||
| 243 | + _result = search_by_time(cList,startTime,endTime) | ||
| 244 | + printResult(_result) | ||
| 245 | + elif(menu == str(4)): | ||
| 246 | + print('***********************************') | ||
| 247 | + inputLimit = input('상위 몇 개 까지 보고 싶은지 입력하세요(1~20): ') | ||
| 248 | + while(True): | ||
| 249 | + if (int(inputLimit) <= 0 or int(inputLimit) > 20): | ||
| 250 | + inputLimit = input('상위 몇 개 까지 보고 싶은지 입력하세요(1~20): ') | ||
| 251 | + else: | ||
| 252 | + break | ||
| 253 | + | ||
| 254 | + printMostShowed(cList,int(inputLimit)) | ||
| 255 | + else: | ||
| 256 | + print('잘못된 입력입니다') | ||
| 257 | + continue | ||
| 258 | + | ||
| 259 | + | ||
| 260 | + | ||
| 261 | +main() |
-
Please register or login to post a comment