def __init__(self): self.db = DB() self.krt = KrTokenizer() self.dtmvector = CountVectorizer() self.tfidf_transformer = TfidfTransformer() self.tfidf = TfidfVectorizer() self.category_list = {100: '맛집', 101: '음식 상품 리뷰', 102: '레시피'}
def __init__(self, web_path): """ :param web_path: (str) 크롬 브라우저 경로 """ self.driver = webdriver.Chrome(web_path) self.db = DB() # 사람처럼 보이기 위한 옵션들 self.options = webdriver.ChromeOptions() self.options.add_argument("disable-gpu") self.options.add_argument("lang=ko_KR") # 검색 키워드 리스트 self.keyword_ls = [# '강남역', #'홍대', '건대', # '혜화', # '광화문', '이태원', # '명동', '종로', '신촌', '압구정', '동대문', '신림', # di-re '영등포', '신사', '노원', '잠실' ] # 검색 키워드 당 맛집 수집 개수 self.n_restaurant = 200 # DB primary key self.number = 38390
def __init__(self, web_path): self.driver = webdriver.Chrome(web_path) self.db = DB() # 100 영화, 문화 # 101 육아, 결혼 # 102 상품리뷰 # 103 여행 # 104 맛집, 음식 # 105 IT, 인터넷 # 106 사회, 정치 # 107 비즈니스, 경제 # 108 패션, 미용 self.category_list = { '100': 6, '101': 15, '102': 21, '103': 27, '104': 29, '105': 30, '106': 31, '107': 33, '108': 18 }
def __init__(self): self.db = DB() self.bc = Blog_Crawler('c://chromedriver.exe') self.c1 = BCC_NB() self.c2 = BCC_NB_02() self.pc = Posting_Classification() self.sa = Sentiment_Analysis()
def __init__(self): self.db = DB() self.krt = KrTokenizer() self.dtmvector = CountVectorizer() self.tfidf_transformer = TfidfTransformer() self.tfidf = TfidfVectorizer() self.category_list = { 100: '영화, 문화', 101: '육아, 결혼', 102: '상품리뷰', 103: '여행', 104: '맛집, 음식', 105: 'IT, 인터넷', 106: '사회, 정치', 107: '비즈니스, 경제', 108: '패션, 미용' }
import json import math from nltk import FreqDist from collections import OrderedDict from tokenizer import KrTokenizer from db_connect import DB krt = KrTokenizer() dl = DB() # standard DTM 생성 함수 def standard_dtm_build(): # 데이터 로드 load_size = 1000 sql = 'SELECT posting_contents FROM blog.blog_restaurant order by rand() LIMIT ' + str( load_size) + ';' temp = dl.data_load(sql) # str 형식으로 변환 source = [] for _temp in temp: _temp = str(_temp) _temp = _temp.replace('(', '') _temp = _temp.replace(')', '') source.append(_temp) # 토크나이징 token_data = krt.extract_morphs_for_all_document(source, False)
class Restaurant_Review_Crawler: def __init__(self, web_path): """ :param web_path: (str) 크롬 브라우저 경로 """ self.driver = webdriver.Chrome(web_path) self.db = DB() # 사람처럼 보이기 위한 옵션들 self.options = webdriver.ChromeOptions() self.options.add_argument("disable-gpu") self.options.add_argument("lang=ko_KR") # 검색 키워드 리스트 self.keyword_ls = [# '강남역', #'홍대', '건대', # '혜화', # '광화문', '이태원', # '명동', '종로', '신촌', '압구정', '동대문', '신림', # di-re '영등포', '신사', '노원', '잠실' ] # 검색 키워드 당 맛집 수집 개수 self.n_restaurant = 200 # DB primary key self.number = 38390 # 다이닝 코드 리뷰 수집 함수 def get_dining_code_review(self): # 키워드 별 순환 for keyword in self.keyword_ls: # 키워드 검색 페이지 접근 page_link = 'https://www.diningcode.com/isearch.php?query=' + str(keyword) self.driver.get(page_link) # 맛집 링크 리스트 불러오기 self.driver.find_element_by_xpath('//*[@id="btn_normal_list"]/a/span').click() n_click = 0 for i in range(self.n_restaurant//10 - 1): sleep(5) try: self.driver.find_element_by_xpath('//*[@id="div_list_more"]/a/span').click() # n_restaurant 보다 맛집이 더 적을 경우 불러올 수 있을만큼 불러오기... except: break n_click = i # 맛집 링크 리스트 추출 rs_link_ls = [] for i in range(1, self.n_restaurant + n_click + 1): try: x_path = '// *[ @ id = "div_list"] / li[' + str(i) + '] / a / span[2]' rs_link_ls.append(self.driver.find_element_by_xpath(x_path)) # 중간 광고 pass except: pass sleep(3) # 맛집 소개 순환하며 데이터 추출 for rs_link in rs_link_ls: # 링크로 접근 rs_link.click() sleep(randint(5, 9)) self.driver.switch_to.window(window_name=self.driver.window_handles[-1]) # 맛집 기본 정보 (이름, 평점) 수집 rs_name = self.driver.find_element_by_xpath('//*[@id="div_profile"]/div[1]/div[2]/p').text rs_score = self.driver.find_element_by_xpath('//*[@id="div_profile"]/div[1]/div[4]/p/strong').text # 리뷰 리스트 불러오기 while True: try: sleep(1) self.driver.find_element_by_xpath('//*[@id="div_more_review"]/a/span').click() # 더 이상 없을 때까지... except: break # 리뷰들의 고유 id 추출 try: info_page = BeautifulSoup(self.driver.page_source, 'html.parser') # 로그인 요구 팝업 창 처리 except Exception as e: print(e) sleep(30) self.driver.close() self.driver.switch_to.window(window_name=self.driver.window_handles[0]) continue review_blocks1 = info_page.findAll('div', {'class': 'latter-graph'}) review_blocks2 = info_page.findAll('div', {'class': 'latter-graph short'}) review_blocks = review_blocks1 + review_blocks2 review_id_ls = [review_block.attrs['id'] for review_block in review_blocks if 'id' in review_block.attrs] review_id_ls = set(review_id_ls) # print(rs_name, rs_score, 'reviews count : ', len(review_id_ls)) # 리뷰 데이터 수집 for review_id in review_id_ls: # 리뷰 남긴 사람 ID 수집 및 정제 reviewer = self.driver.find_element_by_xpath('//*[@id="' + review_id + '"]/p[1]/span[1]/strong').text # 리뷰 평점 수집 및 정제 review_score = self.driver.find_element_by_xpath('//*[@id="' + review_id + '"]/p[1]/span[3]/i[1]/i') review_score = str(review_score.get_attribute('style')) review_score = review_score.replace('width: ', '') review_score = review_score.replace('%;', '') review_score = int(review_score)//20 # 리뷰 날짜 수집 및 정제 review_date = self.driver.find_element_by_xpath('//*[@id="' + review_id + '"]/p[1]/span[3]/i[2]').text review_date = str(review_date) try: try: # 올해 이전에 작성된 리뷰 temp_date = datetime.strptime(review_date, '%Y년 %m월 %d일') except: # 올해 작성된 리뷰 처리 temp_date = '2019년 ' + str(review_date) temp_date = datetime.strptime(temp_date, '%Y년 %m월 %d일') review_date = datetime.strftime(temp_date, '%Y-%m-%d') except: try: # 며칠 전에 작성된 리뷰 temp_date = review_date.replace('일 전', '') temp_date = int(temp_date) temp_date = datetime.now() + timedelta(days=(temp_date * (-1))) review_date = datetime.strftime(temp_date, '%Y-%m-%d') except: # 몇분전, 몇시간전 작성된 리뷰 (그냥 버려...) continue # 리뷰 내용 수집 및 정제 try: review_contents = self.driver.find_element_by_xpath('//*[@id="' + review_id + '"]/p[3]').text review_contents = str(review_contents).replace("'", "") except: review_contents = '' # 데이터 저장 self.db.data_save_restaurant(self.number, rs_name, reviewer, review_score, review_date, review_contents, 'dining_code') self.number = self.number + 1 # 수집 한 페이지 닫기 self.driver.close() self.driver.switch_to.window(window_name=self.driver.window_handles[0]) # 망고 플레이트 리뷰 수집 함수 def get_mango_plate_review(self): # 키워드 별 순환 for keyword in self.keyword_ls: review_id_ls = [] # 페이지 별 순환하며 맛집 소개 페이지 링크 추출 for page_num in range(1, self.n_restaurant//20 + 1): page_link = 'https://www.mangoplate.com/search/' + str(keyword) + '?keyword=' + str(keyword) +\ '&page=' + str(page_num) self.driver.get(page_link) sleep(2) try: info_page = BeautifulSoup(self.driver.page_source, 'html.parser') sleep(2) info_boxes = info_page.findAll('div', {'class': 'info'}) # 한 페이지 당 20개의 맛집 소개 for i in range(20): temp = str(info_boxes[i]).split('a href="') temp = temp[1].split('">') review_id_ls.append(temp[0]) # n_restaurant 보다 맛집이 더 적을 경우 불러올 수 있을만큼 불러오기... except: break # 맛집 소개 페이지 순환 for review_id in review_id_ls: # 맛집 소개 페이지 접근 review_link = 'https://www.mangoplate.com' + str(review_id) self.driver.get(review_link) sleep(3) try: # 맛집 기본 정보 (이름, 평점) 수집 rs_name = self.driver.find_element_by_xpath( '/html/body/main/article/div[1]/div[1]/div/section[1]/header/div[1]/span/h1').text rs_score = self.driver.find_element_by_xpath( '/html/body/main/article/div[1]/div[1]/div/section[1]/header/div[1]/span/strong').text # 맛집 리뷰 개수 추출 review_count = self.driver.find_element_by_xpath( '/html/body/main/article/div[1]/div[1]/div/section[3]/header/ul/li[1]/button/span').text review_count = int(review_count) except: sleep(10) continue # 리뷰 리스트 불러오기 (원인을 알수없는 불러오기 문제로 인해 15번으로 제한) for _ in range(15): try: sleep(3) more_list_button = self.driver.find_element_by_xpath( '/html/body/main/article/div[1]/div[1]/div/section[3]/div[2]') more_list_button.click() # 더 이상 없을 때까지... except: break # 리뷰 데이터 수집 for i in range(review_count): try: # 각 수집 데이터들의 x_path reviewer_xPath = '/html/body/main/article/div[1]/div[1]/div/section[3]/ul/li['\ + str(i + 1) + ']/a/div[1]/span' review_score_xPath = '/html/body/main/article/div[1]/div[1]/div/section[3]/ul/li[' \ + str(i + 1) + ']/a/div[3]/span' review_date_xPath = '/html/body/main/article/div[1]/div[1]/div/section[3]/ul/li[' \ + str(i + 1) + ']/a/div[2]/div/span' review_contents_xPath = '/html/body/main/article/div[1]/div[1]/div/section[3]/ul/li[' \ + str(i + 1) + ']/a/div[2]/div/p' # 리뷰 남긴 사람 ID 수집 및 정제 reviewer = self.driver.find_element_by_xpath(reviewer_xPath).text # 리뷰 평점 수집 및 정제 (맛있다 : 5점, 괜찮다 : 3점, 별로다 : 1점) review_score = self.driver.find_element_by_xpath(review_score_xPath).text if review_score == '맛있다': review_score = 5 elif review_score == '별로': review_score = 1 else: review_score = 3 # 리뷰 날짜 수집 및 정제 review_date = self.driver.find_element_by_xpath(review_date_xPath).text # 리뷰 내용 수집 및 정제 review_contents = self.driver.find_element_by_xpath(review_contents_xPath).text review_contents = str(review_contents).replace("'", "") # 데이터 저장 self.db.data_save_restaurant(self.number, rs_name, reviewer, review_score, review_date, review_contents, 'mango_plate') self.number = self.number + 1 # 수집 에러 데이터는 그냥 버리기... except: pass
class Blog_Crawler: def __init__(self, web_path): self.driver = webdriver.Chrome(web_path) self.db = DB() # 100 영화, 문화 # 101 육아, 결혼 # 102 상품리뷰 # 103 여행 # 104 맛집, 음식 # 105 IT, 인터넷 # 106 사회, 정치 # 107 비즈니스, 경제 # 108 패션, 미용 self.category_list = { '100': 6, '101': 15, '102': 21, '103': 27, '104': 29, '105': 30, '106': 31, '107': 33, '108': 18 } def change_month(self, month): month_word = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] if month in month_word: return month_word.index(month) + 1 # 포스팅의 크롤링 가능한 실제 링크를 반환 하는 함수 (네이버 블로그는 크롤링 가능 링크와 읽기용 링크 따로 존재) def real_link_build(self, blogger, posting_index): """ :param blogger: (str) 블로거의 아이디 :param posting_index: (str) 해당 포스팅의 고유 번호 :return: (str) 크롤링이 가능한 포스팅 실제 주소 """ posting_real_link = 'http://blog.naver.com/PostView.nhn?blogId=' + str(blogger) + \ '&logNo=' + str(posting_index) + \ '&from=search&redirect=Log&widgetTypeCall=true&directAccess=false' return posting_real_link # 카테고리 버전의 블로그 크롤링 함수 def crawler_ver_category(self, category_number, search_number): """ :param category_number: (int) 수집할 카테고리 고유 번호 :param search_number: (int) 수집할 포스팅의 갯수 :return: None """ count = 0 page_number = 1 posting_body_list = [] while True: page_link = 'https://section.blog.naver.com/ThemePost.nhn?directoryNo=' \ + str(self.category_list[str(category_number)]) + \ '&activeDirectorySeq=4¤tPage=' + str(page_number) self.driver.get(page_link) sleep(3) posting_page = BeautifulSoup(self.driver.page_source, 'html.parser') temp_list = posting_page.findAll('a', {'class': 'desc_inner'}) url_list = [] for temp in temp_list: temp = str(temp) temp = temp.split('" href="') temp = temp[1] temp = temp.split('" ng') url_list.append(temp[0]) for url in url_list: url = str(url) temp = url.split('.com/') temp = temp[1] temp = temp.split('/') blogger = temp[0] posting_index = temp[1] posting_real_link = self.real_link_build(blogger, posting_index) posting_body = self.body_crawler(posting_real_link) posting_body_list.append(posting_body) count = count + 1 self.db.data_save_blog(url, posting_body, category_number) if count >= search_number: return page_number = page_number + 1 def get_posting_info(self, url): """ :param url: (str) 포스팅 링크 (real_link) :return: (str) 포스팅 본문 """ blog_link = url blog_link = blog_link.replace("http", "https") blog_xml_link = blog_link.replace("https://", "https://rss.") blog_xml_link = blog_xml_link + str(".xml") # blog_url 에서 blogger 추출 blogger = blog_link.split('.com/') blogger = blogger[1] try: # RSS 2.0 으로 접근 self.driver.get(blog_xml_link) soup = BeautifulSoup(self.driver.page_source, 'html.parser') # RSS 2.0 에서 포스팅들의 정보 수집 rss_board = soup.findAll('span', {'class': 'text'}) rss_board2 = str(rss_board).split('</span>') except Exception as e: # RSS 2.0 접근 실패 print(e) print("RSS 2.0 error : ", blog_link) return temp_list = [] temp2_list = [] # 포스팅 링크 수집 (blog.naver 형식) for line in rss_board2: if blog_link in line: line = line.split('"text">') line = line[1] if line != blog_link: temp_list.append(line) for i in range(0, len(temp_list), 2): if temp_list[i][0:4] == "http": temp2_list.append(temp_list[i]) # 포스팅 링크 수집 (blog.me 형식) if len(temp2_list) is 0: blog_link = 'http://' + str(blogger) + '.blog.me' for line in rss_board2: if blog_link in line: line = line.split('"text">') line = line[1] if line != blog_link: temp_list.append(line) for i in range(0, len(temp_list), 2): if temp_list[i][0:4] == "http": temp2_list.append(temp_list[i]) posting_real_number_ls = [] for posting_real_number in temp2_list: posting_real_number = posting_real_number.split(blog_link + str('/')) posting_real_number_ls.append(posting_real_number[1]) posting_date_ls = [] # 포스팅 날짜 수집 for line in rss_board2: if "+0900" in line: line = line.split('"text">') line = line[1] posting_date_ls.append(line) posting_date_ls.pop(0) # 포스팅 날짜 변환 (ex. Fri, 2018 Jun 07 -> 2018-01-07) for i in range(0, len(posting_date_ls)): posting_date_ls[i] = posting_date_ls[i].split(", ") posting_date_ls[i] = posting_date_ls[i][1] posting_date_ls[i] = posting_date_ls[i].split(" +") posting_date_ls[i] = posting_date_ls[i][0] posting_date_ls[i].strip() posting_date_ls[i] = str(posting_date_ls[i][7:12]) + str(posting_date_ls[i][3:7]) + str(posting_date_ls[i][0:3]) posting_date_ls[i] = datetime.date(int(posting_date_ls[i][0:4]), int(self.change_month(posting_date_ls[i][5:8])), int(posting_date_ls[i][9:11])) return posting_real_number_ls, posting_date_ls # 포스팅의 본문 수집 함수 def get_posting_body(self, url): """ :param url: (str) 포스팅 실제 링크 (real_link) :return: (str) 포스팅 본문 """ posting_full_contents = '' try: # 포스팅 본문으로 접근 self.driver.get(url) posting_page = BeautifulSoup(self.driver.page_source, 'html.parser') try: # 본문 "se-main-container" HTML_TAG 사용 temp = posting_page.find('div', {'class': 'se-main-container'}).text temp = str(temp).replace('\n', '') posting_full_contents = temp.strip() posting_full_contents = posting_full_contents.replace("'", "") posting_full_contents = posting_full_contents.replace("\\", "") except: try: # 본문 "postViewArea" HTML_TAG 사용 temp = posting_page.find('div', {'id': 'postViewArea'}).text temp = str(temp).replace('\n', '') posting_full_contents = temp.strip() posting_full_contents = posting_full_contents.replace("'", "") posting_full_contents = posting_full_contents.replace("\\", "") except: try: # 본문 "se_component_wrap sect_dsc __se_component_area" HTML_TAG 사용 temp = posting_page.find('div', {'class': 'se_component_wrap sect_dsc __se_component_area'}).text temp = str(temp).replace('\n', '') posting_full_contents = temp.strip() posting_full_contents = posting_full_contents.replace("'", "") posting_full_contents = posting_full_contents.replace("\\", "") except: # 분문이 다른 HTML_TAG 사용 print("posting error (maybe new html_tag) url : ", url) pass except Exception as e: print(e) print("posting error (maybe deleted posts) url : ", url) pass return posting_full_contents def get_posting_feature(self, url): """ :param url: (str) 포스팅 실제 링크 (real_link) :return: (str) 포스팅 본문 """ # 포스팅 본문으로 접근 self.driver.get(url) posting_page = BeautifulSoup(self.driver.page_source, 'html.parser') # 포스팅 길이 수집 posting_length = len(self.get_posting_body(url)) image_count = 0 video_count = 0 map_count = 0 # 포스팅 이미지 수 수집 try: image = posting_page.findAll("div", {"class": "se-component se-image se-l-default"}) image_count = len(image) if image_count == 0: image = posting_page.findAll("div", {"class": "se_component se_image default"}) image_count = len(image) if image_count == 0: image = posting_page.findAll("img", {"class": "_photoImage"}) image_count = len(image) except Exception as e: print(e) # 포스팅 동영상의 수 수집 try: video = posting_page.findAll("div", {"class": "se-component se-video se-l-default"}) video_count = len(video) except Exception as e: print(e) # 포스팅 지도의 수 수집 try: map = posting_page.findAll("div", {"class": "se-component se-placesMap se-l-default"}) map_count = len(map) except Exception as e: print(e) return posting_length, image_count, video_count, map_count
# -*- coding: utf-8 -*- from rs_sentiment_analysis.sentiment_analysis import Sentiment_Analysis from db_connect import DB if __name__ == '__main__': sa = Sentiment_Analysis() db = DB() sql = "select contents from restaurant.review where not contents = '' order by rand() limit 10;" source = db.data_load(sql) data = [] for _source in source: temp = str(_source).replace('(', '') temp = temp.replace(')', '') temp = temp.replace("'", "") data.append(temp) for i, j in enumerate(data): print(i + 1, ' ) ', j) print('\n**********************************') sa.sentiment_analysis(data)
class BCC_NB_02: def __init__(self): self.db = DB() self.krt = KrTokenizer() self.dtmvector = CountVectorizer() self.tfidf_transformer = TfidfTransformer() self.tfidf = TfidfVectorizer() self.category_list = {100: '맛집', 101: '음식 상품 리뷰', 102: '레시피'} # 토크나이징, 전처리 함수 def tokenize(self, corpus, use_mecab=True): """ :param corpus: (str) 블로그 하나의 문서 :param use_mecab: (bool) mecab 사용 여부, default=True :return: (list) 토크나이징 된 블로그 문서 """ if use_mecab is True: token_doc = self.krt.extract_morphs_for_single_doc(corpus, True) else: token_doc = self.krt.extract_morphs_for_single_doc(corpus, False) return token_doc # 토크나이징 된 문서를 하나의 문서로 변환 (모델 적용을 위해) def token_to_corpus(self, token_doc): """ :param token_doc: (list) 토크나이징으로 리스트화 된 하나의 문서 :return: (str) 토크나이징이 완료된 하나의 문서 """ result = '' for token in token_doc: result = result + ' ' + str(token) return result # 모델 저장 함수 def model_save(self, model): f = open('classifier_nb.pickle', 'wb') pickle.dump(model, f) f.close() # 모델 로드 함수 def model_load(self): f = open('classifier_nb.pickle', 'rb') model = pickle.load(f) f.close() return model # 모델 학습 함수 def model_train(self): # 학습 데이터 로드 sql = 'SELECT body, category_num FROM blog_category_classification.posting2 order by rand()' pre_data = self.db.data_load(sql) # 학습 데이터 토크나이징, 전처리 af_data = [] for _data in pre_data: temp = [] body = self.token_to_corpus(self.tokenize(_data[0], True)) category_num = _data[1] temp.append(body) temp.append(category_num) af_data.append(temp) X_train = [] y_train = [] for _data in af_data: X_train.append(_data[0]) y_train.append(_data[1]) print('train data_set size : ' + str(len(X_train))) # 학습 데이터를 DTM, TF-IDF 행렬로 변환 X_train_dtm = self.dtmvector.fit_transform(X_train) X_train_tfidfv = self.tfidf_transformer.fit_transform(X_train_dtm) X_train_tfidf = self.tfidf.fit(X_train) print(X_train_dtm.shape) # TF-IDF 토큰 저장 temp = [] for key in X_train_tfidf.vocabulary_.keys(): temp.append(key) save_data = OrderedDict() save_data['token'] = temp save_data['doc_count'] = len(af_data) json.dump(save_data, open('./naive_bayes_tf_idf.json', 'w', encoding='utf-8'), ensure_ascii=False, indent='\t') # 모델 학습 mod = MultinomialNB() mod.fit(X_train_tfidfv, y_train) # 모델 저장 self.model_save(mod) # 모델 테스트 함수 (학습 차원 내에서의 범위 한정) def model_test(self): # 모델 로드 mod = self.model_load() # TF-IDF 토큰 로드 with open('./naive_bayes_tf_idf.json', 'r', encoding='UTF-8') as f: load_data = json.load(f) token_ls = load_data['token'] # 테스트 데이터 로드 sql = 'SELECT body, category_num FROM blog_category_classification.posting2 order by rand() limit 100' pre_data = self.db.data_load(sql) # 학습 데이터 토크나이징, 전처리 af_data = [] for _data in pre_data: temp = [] body = self.token_to_corpus(self.tokenize(_data[0], True)) category_num = _data[1] temp.append(body) temp.append(category_num) af_data.append(temp) X_temp = [] y_test = [] for _data in af_data: X_temp.append(_data[0]) y_test.append(_data[1]) X_test = X_temp X_fit_dim = '' for token in token_ls: X_fit_dim = X_fit_dim + ' ' + str(token) X_test.insert(0, X_fit_dim) y_test.insert(0, 0) print('test data_set size : ', len(X_test) - 1) MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True) # 테스트 데이터를 DTM, TF-IDF 행렬로 변환 X_test_dtm = self.dtmvector.fit_transform(X_test) X_test_tfidf = self.tfidf_transformer.fit_transform(X_test_dtm) print(X_test_tfidf.shape) # 테스트 데이터에 대한 예측 predicted = mod.predict(X_test_tfidf) # 예측값과 실제값 비교 print("정확도:", accuracy_score(y_test[1:], predicted[1:])) # 모델 평가 함수 def model_evaluation(self): # 학습 데이터 로드 sql = 'SELECT body, category_num FROM blog_category_classification.posting2 order by rand()' pre_data = self.db.data_load(sql) # 학습 데이터 토크나이징, 전처리 af_data = [] for _data in pre_data: temp = [] body = self.tokenize(_data[0], True) category_num = _data[1] temp.append(body) temp.append(category_num) af_data.append(temp) X = [] y = [] for _data in af_data: X.append(_data[0]) y.append(_data[1]) # 학습, 테스트 데이터 분할 (test_data=0.2) sym = int(len(af_data) * 0.8) X_train = X[:sym] X_test = X[sym:] y_train = y[:sym] y_test = y[sym:] print('train data_set size : ' + str(len(X_train))) print('test data_set size : ' + str(len(X_test))) # 학습 데이터를 DTM, TF-IDF 행렬로 변환 X_train_dtm = self.dtmvector.fit_transform(X_train) X_train_tfidfv = self.tfidf_transformer.fit_transform(X_train_dtm) print(X_train_dtm.shape) # 모델 학습 mod = MultinomialNB() mod.fit(X_train_tfidfv, y_train) MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True) # 테스트 데이터를 DTM, TF-IDF 행렬로 변환 X_test_dtm = self.dtmvector.transform(X_test) X_test_tfidf = self.tfidf_transformer.transform(X_test_dtm) # 테스트 데이터에 대한 예측 predicted = mod.predict(X_test_tfidf) # 예측값과 실제값 비교 print("정확도:", round(accuracy_score(y_test, predicted), 3)) # 카테고리 분류 함수 def classifier(self, data, use_save_model=False, use_mecab=True): """ :param data: (list or str) 분류 하고자 하는 하나 또는 복수개의 블로그 문서 :return: None """ t_tokenizing_s = time.time() # 토크나이징 시작 시간 # 분류할 데이터 토크나이징 if use_mecab is True: token_doc_ls = [] for _data in data: token_doc = self.tokenize(_data, use_mecab=True) token_doc_ls.append(token_doc) else: token_doc_ls = [] for _data in data: token_doc = self.tokenize(_data, use_mecab=False) token_doc_ls.append(token_doc) t_tokenizing = time.time() - t_tokenizing_s # 토크나이징 끝 시간 # 세이브 모델 사용 if use_save_model is True: # 모델 로드 mod = self.model_load() # TF-IDF 토큰 로드 with open('./naive_bayes_tf_idf.json', 'r', encoding='UTF-8') as f: load_data = json.load(f) token_ls = load_data['token'] t_padding_s = time.time() # 패딩 작업 시작 시간 # 분류 할 데이터와 TF-IDF 토큰 결합 (차원 수를 맞추기 위한 패딩 작업) X_predict = [] for token_doc in token_doc_ls: temp = [] for token in token_doc: if token in token_ls: temp.append(token) X_predict.append(self.token_to_corpus(temp)) X_fit_dim = '' for token in token_ls: X_fit_dim = X_fit_dim + ' ' + str(token) X_predict.insert(0, X_fit_dim) t_padding = time.time() - t_padding_s # 패딩 작업 끝 시간 MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True) # 학습 데이터를 DTM, TF-IDF 행렬로 변환 X_predict_dtm = self.dtmvector.fit_transform(X_predict) X_predict_tfidf = self.tfidf_transformer.fit_transform( X_predict_dtm) # print(X_predict_tfidf.shape) predicted = mod.predict(X_predict_tfidf) # 결과 출력 for i, _predicted in enumerate(predicted[1:]): print(i, ') ', _predicted, ': ', self.category_list[int(_predicted)]) # 시간 출력 print('\n') print('tokenizing time : ', round(t_tokenizing, 2)) print('padding time : ', round(t_padding, 2)) # 세이브 모델 미사용 else: size = len(data) # 학습 데이터 로드 sql = 'SELECT body, category_num FROM blog_category_classification.posting2 order by rand()' pre_data = self.db.data_load(sql) # 학습 데이터 토크나이징, 전처리 af_data = [] for _data in pre_data: temp = [] body = self.token_to_corpus(self.tokenize(_data[0], True)) category_num = _data[1] temp.append(body) temp.append(category_num) af_data.append(temp) df_doc_ls = [] for token_doc in token_doc_ls: temp = [] temp.append(self.token_to_corpus(token_doc)) temp.append('') df_doc_ls.append(temp) # 학습 데이터, 분류할 데이터 정리 X = [] y = [] af_data = af_data + df_doc_ls for i, _data in enumerate(af_data): X.append(_data[0]) y.append(_data[1]) sym = int(len(X) - size) X_train = X[:sym] X_predict = X[sym:] y_train = y[:sym] for i in X_predict: print(i) print('train data_set size : ', len(X_train)) print('predict data_set size : ', len(X_predict)) # 학습 데이터를 DTM, TF-IDF 행렬로 변환 X_train_dtm = self.dtmvector.fit_transform(X_train) tfidfv = self.tfidf_transformer.fit_transform(X_train_dtm) print(X_train_dtm.shape) # 모델 학습 mod = MultinomialNB() mod.fit(tfidfv, y_train) MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True) # 분류 할 데이터를 DTM, TF-IDF 행렬로 변환 X_test_dtm = self.dtmvector.transform(X_predict) X_test_tfidf = self.tfidf_transformer.transform(X_test_dtm) # 분류 prediction = mod.predict(X_test_tfidf) # 테스트 데이터에 대한 예측 # 결과 출력 for i, _prediction in enumerate(prediction): print(i, ') ', _prediction, ': ', self.category_list[int(_prediction)]) if len(prediction) == 1: return int(prediction) else: return prediction
import time from db_connect import DB from category_classification import classification_nb start = time.time() # 전체 작업 시작 시간 db = DB() bbc_nb = classification_nb.BCC_NB() t_test_load_s = time.time() # 테스트 데이터 로드 시작 시간 # 테스트 데이터 로드 sql = 'SELECT posting_url, posting_contents From blog order by rand() limit 10' pre_doc_ls = db.data_load(sql) t_test_load = time.time() - t_test_load_s # 테스트 데이터 로드 끝 시간 # 테스트 데이터 전처리 doc_ls = [] for i, doc in enumerate(pre_doc_ls): print(i, ') ', doc[0]) temp = str(doc[1]) temp = temp.replace('(', '') temp = temp.replace(')', '') doc_ls.append(temp) # 테스트 데이터 카테고리 분류 bbc_nb.classifier(doc_ls, use_mecab=True, use_save_model=True) # bbc_nb.model_evaluation() # bbc_nb.model_train()
import pickle import pandas as pd import random from sklearn.model_selection import train_test_split import matplotlib.pylab as plt from konlpy.tag import Okt from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from db_connect import DB from rs_sentiment_analysis.model import LSTM_M okt = Okt() db = DB() # 불용어 리스트 stopwords = [ '의', '가', '이', '은', '들', '는', '좀', '잘', '걍', '과', '도', '를', '으로', '자', '에', '와', '한', '하다' ] def save_pickle(data, file_name='test'): f = open(file_name + '.pickle', 'wb') pickle.dump(data, f) f.close() def load_pickle(file_name='test'):