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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | First : Crawling import selenium from selenium import webdriver import random from time import sleep import pandas as pd from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys from tqdm import tqdm # Browser 켜기 browser = webdriver.Chrome() browser.get('https://everytime.kr/timetable') # login 함수 생성 def login(id_,password): browser.find_element_by_name('userid').send_keys(str(id_)) browser.find_element_by_name('password').send_keys(str(password)) browser.find_element_by_xpath('//*[@id="container"]/form/p[3]/input').click() # 가끔 팝업뜨는거 닫아주기 browser.find_element_by_xpath('//*[@id="container"]/ul/li[1]').click() # 스크롤 위치 찾기 scr1=browser.find_element_by_xpath('//*[@id="subjects"]/div[2]') # 스크롤 계속 내리기 ( 매우 오래걸림 실행 X) for i in range(1000): browser.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", scr1) sleep(3) # 에타 과목 정보 저장 tables = browser.find_elements_by_tag_name("tr") # 이 과목 평점 저장 stars = browser.find_elements_by_class_name('star') 에브리타임 과목정보 크롤링 # 칼럼 이름이 html에 있길래 뽑은건데, 2번씩 중복해서 나오길래 이렇게 뽑아봄 (사용안했음 ㅎㅎ;;ㅈㅅ;;) cols=[] for i in range(0,24,2): cols.append(table[2].text.split('\n')[i]) # 점수랑 타이틀 가져오는 코드 scores=[] links=[] for table in tqdm (stars) : scores.append(table.get_attribute('title')) links.append(table.get_attribute('href')) # 테이블이, 3번행부터 정보가 있고, 2918행 이후로는 의미없는 행이길래 제외 # 테이블에서 세부정보 뽑는 코드 tables=tables[3:2918] grades=[] # 추천학년 categories=[] # 수업 분류(전공,교양) codes=[] # 과목코드 names=[] # 수업 이름 profs=[] # 교수 이름 weights=[] # 학점(1,2,3) times=[] # 시간 competitiors=[] # 에타 시간표에 담은인원 for element in tqdm(tables): splited=element.text.split(' ') grades.append(splited[0]) categories.append(splited[1]) codes.append(splited[2]) names.append(splited[3]) profs.append(splited[4]) weights.append(splited[5]) try : times.append(splited[8]+splited[9]+splited[10]+splited[11]+splited[12]) except : times.append(None) try : competitiors.append(splited[13]) except : competitiors.append(None) # 뽑은 리스트들을 df로 바꿔줬음 dfs=[names,codes,scores,categories,profs,times,grades,weights,competitiors,links] df=pd.DataFrame() for cols in dfs: df=pd.concat([df,pd.Series(cols)],axis=1) # 칼럼 이름 만들어줌 df.columns=['class_name','class_code','score','category','professor','time', 'recommend_year','weight','competitor','link'] # 아까 위에서 형식 제대로 안지켜진 데이터들 떨굼 ( 보완 방향 (!) ) df.dropna(inplace=True) # 교수이름이 3글자 아닌 수업들 뽑아봄 : 왜냐면 그냥 인덱싱으로 한거라 형식 안지켜진건데 들어갔을 수 있음 error_list=[] for i in df['professor'].unique(): if len(i) !=3 : error_list.append(i) # 직접 보고 이름 2,4글자인 교수님들 이름 빼줌 non_error=['박진','이화','박민','호티롱안','나강','문영','전긍','고댕','김예솔란','정철', '최웅','안준','김강','이건','사공석진','허림','오정','이민','이찬', '김명','허균','김영','이웅','임강','최영'] # 제거 for i in non_error : error_list.remove(i) # 그냥 이름3자 아니거나, non error 아닌 거 다 없애버렸는데 이거 보완해야함 ( 1 ) #df2=df.query('professor not in @error_list') # Save #df2.to_csv('df2.csv',index=False) # Load df2=pd.read_csv('df2.csv') # 링크 추출함, 리뷰 하나도 없는 수업은 java스크립트로 뽑혀오길래 그것도 제외 link_list=list(df2['link'].unique()) link_list.remove('javascript: alert("%EC%95%84%EC%A7%81 %EB%93%B1%EB%A1%9D%EB%90%9C %EA%B0%95%EC%9D%98%ED%8F%89%EC%9D%B4 %EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4.");') 에브리타임 리뷰크롤링 from random import randint from bs4 import BeautifulSoup as bs import requests import lxml.html import re def review_crawl(link_text): ## Selenium browser.get(link_text) sleep(0.5) # 로딩 기다리기 # 리뷰 내용만 추출 reg=re.compile('[0-9]') reviews={} sleep(2) # 로딩 기다리기 tb=browser.find_element_by_class_name('articles') art_table=tb.find_elements_by_tag_name('article') for element in art_table : reviews[element.find_element_by_class_name('text').text]=(float(''.join(reg.findall(element.find_element_by_class_name('on').get_attribute('style'))))*0.05) # 소스 뺴와서 파싱 table=bs(browser.page_source,'xml') heads=table.find('div',class_='side head') # 제목, 교수 내용 추출 class_name=(heads.find('h2').text) prof_name=(heads.find('span').text) # 강의평 테이블 추출 articles=table.find('div',class_='side article') # 강의평의 평균 평점 내용 추출 mean_score=float(articles.find('span',class_='value').text) # 강의평 중 세부 테이블 추출 details=articles.find('div',class_='details') # 강의평 중 세부 내용 추출 labels=details.find_all('label') detail=details.find_all('span') detail_type={} for l,d in zip(labels,detail): detail_type[str(l.text)]=d.text review_t=pd.DataFrame.from_dict(reviews, orient='index').reset_index() review_t.columns=['review','score'] review_t['name']=class_name review_t['prof']=prof_name review_detail=pd.DataFrame.from_dict(detail_type,orient='index').T review_detail['name']=class_name review_detail['prof']=prof_name return review_t,review_detail review=pd.DataFrame() # 강의평 추출 테이블 detail=pd.DataFrame() # 과목 세부정보 추출 테이블 # ↓ 가끔 안 켜고 시작하면 오류뜰 때가 있어서 그때 대비 browser = webdriver.Chrome() browser.get(link_list[0]) login('###','####') # ↑ 가끔 안 켜고 시작하면 오류뜰 때가 있어서 그때 대비 # 크롤링 과정 (오래걸림 실행 ㄴ) for link in tqdm(link_list) : try: r_t,r_d=review_crawl(link) review=pd.concat([review,r_t]) detail=pd.concat([detail,r_d]) sleep(1) except: print(link , '강의평 추출 X ') #Save #review.to_csv('review.csv',index=False) #detail.to_csv('detail.csv',index=False) #Load review=pd.read_csv('review.csv') review=review.reset_index().drop('index',axis=1) 현재 가지고 있는 테이블 테이블 1: 에브리타임 과목정보 테이블 2: 에브리타임 리뷰 테이블 3 :에브리타임 리뷰 요약 review.head() def cleanText(readData): text = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', ' ', readData) # 특문제거 text = re.sub('[^가-힣]',' ',text) # 한글 제외, + 단일 자음모음 제거 return text clean=[] for data in review['review']: clean.append(cleanText(data)) review['review']=pd.Series(clean) Second : Text-Mining Tokenize from konlpy.tag import * kkma = Kkma() https://m.blog.naver.com/PostView.nhnblogId=wideeyed&logNo=221337575742&proxyReferer=https%3A%2F%2Fwww.google.com%2F # 토크나이징하고, 에러난 row ( 대부분 빈 텍스트를 토크나이징 한 것 ) 찾아줌 corpus=[] error=[] i=0 for data in tqdm(review['review']): try: corpus.append(kkma.morphs(data)) except: error.append(i) i += 1 error=[2568,2720,6009,6532,6589,7729,8722,10853] # 에러난 친구들 display(review.loc[error,:]) review=review.drop(error,0) # 빈 행 삭제 # save with open('kkma_token_0909.pickle', 'wb') as f: pickle.dump(corpus, f, pickle.HIGHEST_PROTOCOL) #load import pickle with open('kkma_token_0909.pickle', 'rb') as f: corpus = pickle.load(f) Embedding from gensim.models import Word2Vec, FastText embd_SG= Word2Vec(corpus, size=100, window=2, min_count=10, workers=-1, iter=1000, sg=1) #CBOW보단 Skip - Gram의 성능이 전반적으로 낫다고들 함 #리뷰 여러개니까, 차원 적으면 표현이 안될거라고 생각, 100차원정도 임시로 줘봤음 #리뷰다보니까 별로 안 길어서 윈도우는 작게주고, min_count는 의미없는 단어 나오지 말라고 10정도로 줌 # -> 이거 나중에 보완 ( 2 ) # 리뷰 단위별 벡터구하기 (받은 코드가 옛날버전인듯 word2vec 모델에 그런인자가 없던데..그래서 그냥 짬) def get_embedding_vector(model,corpus): embeding_dict={} embeding_df=pd.DataFrame() for w,v in zip(model.wv.index2word , model.wv.vectors): embeding_dict[w]=v embeding_table=(pd.DataFrame(embeding_dict).T.reset_index()) for words in (corpus): embeding_df=pd.concat([embeding_df, embeding_table.query('@words in index').iloc[:,1:].mean()] ,axis=1 ) return embeding_df %%time #임베딩 벡터 추출 embedding_vect=get_embedding_vector(embd_SG,corpus) # This is added back by InteractiveShellApp.init_path() Wall time: 7min 26s vect=(embedding_vect.T.reset_index()).iloc[:,1:] # 데이터 프레임으로 바꿔주고 vect.columns=['Review_Embeded'+str(x) for x in range(100)] # 칼럼이름도 달아줌 # 리뷰랑 병합(Group화 해서 강의별 계산을 위함) review.index=range(len(review)) df3=pd.concat([review.iloc[:,1:],vect],axis=1) # 여기다 담아놓은다음에 Merge feature=[] for col_name in (['Review_Embeded'+str(x) for x in range(100)]): feature.append(df3.groupby(['name','prof'])[col_name].mean().reset_index()) #Merge의 과정 df=df2.drop(['class_code','time','link','competitor'],axis=1) df.rename({'class_name':'name','professor':'prof'},axis=1,inplace=True) for f in feature : df =pd.merge(df, f, on=['name','prof'],how='left') df.drop_duplicates(inplace=True) df.dropna(inplace=True) # Skip-Gram을 이용한 인베딩 테이블 완성 sim_df=pd.concat([df[['name','prof',]],df.iloc[:,6:]],axis=1) sim_df.index=[x for x in range(892)] # 나중에 사용할 테이블 detail_table=df2[['class_name','professor','category','recommend_year','weight','score']].drop_duplicates() detail_table.rename({'class_name':'name','professor':'prof'},axis=1,inplace=True) """ df3=pd.concat([df3,pd.get_dummies(df3['category'])],axis=1) df3=pd.concat([df3,pd.get_dummies(df3['recommend_year'])],axis=1) df3.drop(['category','recommend_year'],axis=1,inplace=True) df3=pd.concat([df3[['name','prof','score']],df3.iloc[:,3:]],axis=1) df3['weight']=df3['weight'].astype(int) """ Similarlity from numpy import dot from numpy.linalg import norm import numpy as np #코사인 유사도 계산 함수 def cos_sim(A, B): return dot(A, B)/(norm(A)*norm(B)) #다른 모든 행과 유사도 계산 함수 def find_similar(input_name,input_prof,df): input_lecture=df.query('name == @input_name & prof == @input_prof') rest_lecture=df.query('name != @input_name | prof != @input_prof') output=rest_lecture.iloc[:,:2] output['similar']='' for i in range(len(rest_lecture)): output.iloc[i,2]= cos_sim(input_lecture.iloc[:,2:],rest_lecture.iloc[i,2:]) output = pd.merge(output, detail_table, on=['name','prof'],how='left') output = output.sort_values('similar',ascending=False) return output # 내가 들었던 수업중에 교수님이 (사람)좋았던 수업 find_similar('문명과세계화의도전','안현상',sim_df)[:5] # 경제학개론 확인 # 강의는 썩 재미없었지만 교수님이 열정 넘치고 엄청 좋은 분이었음, 유은나 교수님의 경제학 개론도 대체적으로 비슷한 평가 display(review.query('name=="문명과세계화의도전" & prof =="안현상"')[['review']][:5]) display(review.query('name=="경제학개론" & prof =="유은나"')[['review']][:5]) # 외우기만 해서 맘에 안들었던 수업, 영화시리즈 제외하고 한문과 문화 확인 display(find_similar('영화작문','이지현',sim_df)[:5]) # 암기라는 키워드 자주등장, 차이점은 교수님 칭찬이 많음 ㅎ; 평점차인듯 display(review.query('name=="영화작문" & prof =="이지현"')[['review']][:5]) display(review.query('name=="한문과문화" & prof =="이규일"')[['review']][:5]) Pre-Trained Embedding from gensim.models import FastText %%time model = FastText.load_fasttext_format('D:/fasttext/wiki.ko.bin') # Pre-triainde vector를 사용해보고 싶은데, 커뮤니티 리뷰다 보니까 별 이상한 단어랑 줄임말,오타들이 다있음 # -> 그래서 일반적 word2vec모델에선 사용이 안되것 같음 # -> 근데 FastText는 corpus에 없는 단어들도 사용 가능 + Noise에 강한모델 # -> 따라서 FastText를 이용한 Pre_Trained Vector 사용 시도 fatx_vec=[] # 문장별 임베딩 for words in corpus: fatx_vec.append(model[words].mean(axis=0)) C:\Users\kcg99\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: DeprecationWarning: Call to deprecated `__getitem__` (Method will be removed in 4.0.0, use self.wv.__getitem__() instead). This is separate from the ipykernel package so we can avoid doing imports until fatx_emd=pd.DataFrame() # 임베딩한 벡터를 데이터프레임으로 만들어줌 for vecs in (fatx_vec): fatx_emd=pd.concat([fatx_emd,pd.DataFrame(vecs)],axis=1) #벡터 데이터프레임 전처리 fatx_emd=fatx_emd.T fatx_emd.index=range(len(fatx_emd)) fatx_emd.columns=['Review_ft_Embeded'+str(x) for x in range(300)] #그룹바이 하기위해 달아줌 fatx_df=pd.concat([review[['name','prof']],fatx_emd],axis=1) # 강의별 임베딩 평균값 생성 feature=[] for col_name in (['Review_ft_Embeded'+str(x) for x in range(300)]): feature.append(fatx_df.groupby(['name','prof'])[col_name].mean().reset_index()) # 중복값 제거 fatx_df=fatx_df.iloc[:,:2].drop_duplicates() # 평균값과 강의명 병합 for f in feature : fatx_df =pd.merge(fatx_df, f, on=['name','prof'],how='left') # 임베딩 테이블 완성 fatx_df.dropna(inplace=True) display(find_similar('문명과세계화의도전','안현상',fatx_df)[:5]) display(find_similar('문명과세계화의도전','안현상',sim_df)[:5]) display(find_similar('영화작문','이지현',fatx_df)[:5]) display(find_similar('영화작문','이지현',sim_df)[:5]) # 결과가 다름 : 수업 분야가 비슷한것들끼리 조금더 잘 뽑은 느낌 (내 주관 + 내가 알고있는 평가) # 가중치를 줘서 앙상블하는 것도 괜찮지 않을까? 개선방안 1) 강의평은 학생이 주관적으로 쓰는것, 게다가 온라인커뮤니티상이라 대충쓰는 경우가 많으며, 에타는 리뷰가 적어서 그게 잘 걸러지지도 않음 수업계획서를 크롤링해서, 내용이 비슷한것과, 교수님 성향이 비슷한 걸 나눠줄 수 있지 않을까? 2) 머신러닝 모델링을 진행 안했음 이를 이용해 평점에 어떤게 가장 영향을 많이 미치는 지 알 수 있었을텐데 ( 교수 피드백용으로 사용 할 수 있지 않을까?) 3) 디테일 테이블이랑 병합을 할 수 있음 이를 통해 유사도 말고도, 다양한 방법으로 필터링 할 수 있을텐데, 구현해야할 듯 관련 링크: 1) https://ratsgo.github.io/natural%20language%20processing/2017/03/08/word2vec/ 2) https://fasttext.cc/docs/en/pretrained-vectors.html 3) https://byeongkijeong.github.io/fastText/ 4) https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/03/11/embedding/ 1) word2vec의 전반적 사용법 2) fasttext pre-trained-vectors 구한곳 3) 그거 사용법 찾은곳 4) 임베딩의 전반적 정보 얻은곳 """### 전처리 해보려고 했으나 과정이 너무 번거로워서 나중에 Develop error_index=df.query('professor in @error_list').index # error_index.astype(int) error_table=[] for i in error_index: error_table.append(tables[i].text) #### 에러의 유형 : 1) 팀팀class의 제목명이 길어서 오류 ( 팀팀class에 대한 분석은 우리task에 별로 맞지 않는듯 ) -> 제거 2) 강의이름이 영어라 강의 제목에 띄어쓰기가 들어감 3) 외국인 교수라 교수 이름란에 띄어쓰기가 들어감 import re error_team= re.compile('팀팀Class') error_english = re.compile('[a-zA-Z]') #### 팀팀삭제 error_table2=[] for table in error_table : if len(error_team.findall(table)) == 0 : error_table2.append(table) #### 영어제목 수정 error_eng=[] error_table3=[] for table in error_table2: if len(error_english.findall(table)) < 5: error_table3.append(table) else : error_eng.append(table) error_eng[8] reg_kor=re.compile('[가-힣]') eng_title=[] for element in error_eng: valid_chr=''.join(element.split(' ')[3:]) point=(reg_kor.search(valid_chr)).start() if len(valid_chr[:point]) >= 1: eng_title.append(valid_chr[:point]) else eng_title ### 전처리 해보려고 했으나 과정이 너무 번거로워서 나중에 Develop """ | cs |
Designed by sketchbooks.co.kr / sketchbook5 board skin
Sketchbook5, 스케치북5
Sketchbook5, 스케치북5
Sketchbook5, 스케치북5
Sketchbook5, 스케치북5