본문 바로가기

파이썬 & 머신러닝과 딥러닝

뉴스기사 & 리뷰 (텍스트 데이터) 주가 예측

뉴스기사 & 리뷰 (텍스트 데이터) 주가 예측

 

→ 뉴스 기사 수집

Selenium과 BeautifulSoup을 사용하여 네이버 뉴스에서 특정 키워드와 관련된 뉴스 기사를 수집

주식 키워드와 관련된 최신 뉴스 기사 제목 100개를 스크롤을 반복하면서 수집해, 리스트에 저장

# 필요한 라이브러리 임포트
import undetected_chromedriver as uc  # 크롬 브라우저를 자동화하면서 탐지 방지 기능을 가진 드라이버
from bs4 import BeautifulSoup  # HTML을 파싱해 텍스트 추출
import time  # 일정 시간 지연을 위해 사용

# 크롬 드라이버를 사용하여 브라우저 시작
driver = uc.Chrome()
# 뉴스 검색 페이지로 이동
driver.get("https://search.naver.com/search.naver?sm=tab_hty.top&where=news&ssc=tab.news.all&query=iM%EB%B1%85%ED%81%AC")

# 뉴스 제목을 저장할 빈 리스트 생성
box1 = []

# 뉴스 제목을 반복적으로 수집할 루프 시작
while True:
    # 페이지의 HTML을 BeautifulSoup으로 파싱
    html = BeautifulSoup(driver.page_source, 'html.parser')
    # 뉴스 기사 목록에서 각 기사 아이템을 찾기
    news = html.find("ul", class_="list_news _infinite_list").find_all("li", class_="bx")

    # 각 뉴스 기사에서 제목 추출
    for i in news:
        a = i.find("a", class_="news_tit")['title'].strip()  # 뉴스 제목 텍스트 추출 및 양끝 공백 제거
        if a not in box1:  # 제목이 이미 리스트에 없으면 추가
            box1.append(a)  # 중복 방지를 위해 제목 저장
            if len(box1) == 100:  # 제목이 100개가 되면 수집 종료
                break

    # 수집된 뉴스 제목이 100개면 루프 종료
    if len(box1) == 100:
        break

    # 페이지 스크롤을 최하단으로 이동 (다음 뉴스 불러오기 위해 사용)
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(3)  # 다음 페이지 로딩 대기

    # 현재 수집된 뉴스 기사 개수를 출력
    print(f"{len(box1)}개 뉴스기사 수집")

 

 

→ 수집 후 크롬창에 신한은행 검색해서 box2 코드 진행

box2 = []
while True:
    html = BeautifulSoup(driver.page_source)
    news = html.find("ul", class_ = "list_news _infinite_list").find_all("li", class_ = "bx")
    for i in news:
        a = i.find("a", class_ = "news_tit")['title'].strip()
        if a not in box2:
            box2.append(a)
            if len(box2) == 100:
                break
    if len(box2) == 100:
        break
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 자바 스크립트 명령어 사용할 수 잇는 것, 0에서 한번에 페이지 끝까지 내리겠다
    time.sleep(3)
    print(f"{len(box2)}개 뉴스기사 수집")

 

→ box1과 box2 리스트에 저장된 뉴스 제목 데이터를 저장

import pandas as pd

df1 = pd.DataFrame(box1, columns = ['내용'])
df1['은행'] = 'IM뱅크'

df2 = pd.DataFrame(box2, columns = ['내용'])
df2['은행'] = '신한은행'

df = pd.concat([df1, df2])
df.to_csv('news.csv', index = False)
df

 

 

 

  konlpy 라이브러리의 Okt 형태소 분석기를 사용하여, 데이터에서 명사만을 추출한 후 새로운 열에 저장

from konlpy.tag import Okt  # 한글 텍스트 처리를 위한 konlpy 라이브러리에서 Okt 형태소 분석기 임포트

okt = Okt()  # Okt 형태소 분석기를 초기화하여 okt 변수에 할당

# make_nouns 함수 정의: 주어진 문자열 x에서 명사만 추출하여 공백으로 구분한 문자열로 반환
def make_nouns(x):
    return ' '.join(okt.nouns(x))

# news.csv 파일을 읽어 데이터프레임으로 저장
df = pd.read_csv('news.csv')

# '내용' 열의 각 행에 make_nouns 함수를 적용하여 'Nouns' 열에 결과 저장
df['Nouns'] = df['내용'].apply(make_nouns)

# 결과 데이터프레임 df 출력
df

 

  • make_nouns(x) 함수: 입력된 텍스트 x에서 okt.nouns(x)를 통해 명사 리스트를 추출한 후, join 함수를 사용하여 명사들을 공백으로 연결된 단일 문자열로 변환해 반환
  • df['내용'].apply(make_nouns): 내용 열의 각 텍스트에 make_nouns 함수를 적용하여 명사들로만 이루어진 텍스트를 새로 만들어 "Nouns" 열에 추가

 

 

 

→ 뉴스 기사에서 명사만을 추출한 데이터를 단어 빈도 기반의 벡터 형태로 변환

from sklearn.feature_extraction.text import CountVectorizer  # CountVectorizer 임포트

cv = CountVectorizer()  # CountVectorizer 객체 생성 (텍스트를 단어 빈도수로 변환)

# "Nouns" 열에 있는 명사 텍스트 데이터를 CountVectorizer를 통해 벡터화하고 행렬 형태로 변환
cv_matrix = cv.fit_transform(df['Nouns'])

# 생성된 희소 행렬을 배열 형태로 변환하여 확인하기 쉽게 변경
cv_matrix = cv_matrix.toarray()

# 벡터화된 결과 출력
cv_matrix

 

  • CountVectorizer 객체 생성: cv는 단어 빈도를 세어 벡터화하는 데 사용하는 객체. 기본 설정으로 모든 단어를 개별 요소로 만들고, 해당 단어의 빈도를 세어 각 문장을 벡터화.
  • cv.fit_transform(df['Nouns']): df['Nouns']에 들어있는 명사만으로 구성된 텍스트 데이터를 fit_transform을 사용해 벡터화하고, 결과를 희소 행렬 형태로 반환.
  • cv_matrix.toarray(): 생성된 희소 행렬을 배열로 변환하여 전체 빈도수 데이터를 확인하기 쉽게 .
  • cv.get_feature_names_out(): CountVectorizer가 생성한 모든 단어의 목록을 반환. 각 단어는 cv_matrix의 각 열에 해당하므로, get_feature_names_out()을 통해 어떤 단어가 어떤 열에 위치하는지 알 수 있다.

 

 

→ 뉴스 기사 텍스트 기반 은행 분류기 구축 및 성능 평가

뉴스 기사 텍스트 데이터를 사용하여 IM뱅크와 신한은행을 분류하는 모델을 구축합니다. 주어진 텍스트를 벡터화하고 랜덤 포레스트 모델을 사용하여 학습 및 예측을 수행한 후, 모델의 성능을 평가합니다. 마지막으로 특정 텍스트에 대해 예측을 수행

# 타겟 변수를 생성: IM뱅크는 1, 신한은행은 0
df['Target'] = [1] * 100 + [0] * 100  

# train_test_split과 RandomForestClassifier 모듈 임포트
from sklearn.model_selection import train_test_split  # 데이터 분할을 위한 함수
from sklearn.ensemble import RandomForestClassifier  # 랜덤 포레스트 분류기

# 독립 변수(X)와 종속 변수(Y) 설정
X = cv_matrix  # TF-IDF 벡터화된 데이터
Y = df['Target'].values  # 타겟 변수를 numpy 배열로 변환

# 데이터셋을 학습 세트와 테스트 세트로 분할
train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.2, random_state=42)  
# test_size=0.2: 전체 데이터의 20%를 테스트 세트로 사용하고, random_state=42: 결과 재현을 위한 시드 설정

# 랜덤 포레스트 모델 초기화 및 학습
model = RandomForestClassifier()  # 랜덤 포레스트 분류기 객체 생성
model.fit(train_x, train_y)  # 학습 데이터로 모델 학습
# 성능 평가를 위한 classification_report 모듈 임포트
from sklearn.metrics import classification_report  # 성능 평가를 위한 함수

# 테스트 데이터에 대해 예측 수행
pred = model.predict(test_x)  # 테스트 세트에 대한 예측값 생성

# 예측 결과를 평가하고 출력
report = classification_report(test_y, pred)  # 실제 값과 예측 값을 비교하여 성능 평가
print(report)  # 성능 평가 결과 출력

 

# 중요 특성 시각화를 위한 라이브러리 임포트
import numpy as np  # 수치 계산을 위한 라이브러리
import matplotlib.pyplot as plt  # 데이터 시각화를 위한 라이브러리
import koreanize_matplotlib  # 한글 폰트 지원

# 모델의 특성 중요도 기반으로 상위 20개 특성 인덱스 선택
idx = np.argsort(model.feature_importances_)[::-1][:20]  

# 상위 20개 특성의 중요도를 막대그래프로 시각화
plt.bar(x = cv.get_feature_names_out()[idx],  # 특성 이름
        height = model.feature_importances_[idx])  # 특성 중요도
plt.xticks(rotation = 90)  # x축 레이블을 90도 회전
plt.show()  # 그래프 표시

 

# 예측할 텍스트
text = 'IM뱅크가 원래 대구은행이래'  # 분류할 텍스트

# 텍스트에서 명사 추출 후 벡터화
a = ' '.join(okt.nouns(text))  # okt를 사용해 텍스트에서 명사만 추출
a = cv.transform([a]).toarray()  # 추출한 명사를 TF-IDF 벡터로 변환

# 모델을 사용하여 예측 수행
model.predict(a)  # 벡터화된 텍스트에 대한 예측 결과


오즈비, TF-IDF // 각각 상대적으로 많이 나오는 단어 찾기

# Counter 클래스를 임포트하여 명사 빈도를 계산하기 위한 준비
from collections import Counter

# IM뱅크 명사를 저장할 리스트
box1 = []  # IM뱅크의 명사를 저장할 리스트
# 신한은행 명사를 저장할 리스트
box2 = []  # 신한은행의 명사를 저장할 리스트

# IM뱅크 관련 명사를 box1에 저장
for i in df[df['은행'] == "IM뱅크"]['Nouns'].str.split():  # IM뱅크 관련 뉴스의 Nouns 열에서 명사를 분리
    box1 += i  # box1 리스트에 분리된 명사를 추가

# 신한은행 관련 명사를 box2에 저장
for i in df[df['은행'] == "신한은행"]['Nouns'].str.split():  # 신한은행 관련 뉴스의 Nouns 열에서 명사를 분리
    box2 += i  # box2 리스트에 분리된 명사를 추가

# IM뱅크의 명사 빈도를 계산
counter1 = Counter(box1)  # IM뱅크의 명사 빈도를 계산하여 counter1에 저장
# 신한은행의 명사 빈도를 계산
counter2 = Counter(box2)  # 신한은행의 명사 빈도를 계산하여 counter2에 저장

# IM뱅크의 빈도 데이터를 데이터프레임으로 변환
df1 = pd.DataFrame({"Word": counter1.keys(), "freq": counter1.values()})  # 단어와 빈도를 데이터프레임으로 생성
df1['Bank'] = 'IM'  # 해당 데이터프레임에 'Bank' 열을 추가하고 값으로 'IM'을 설정

# 신한은행의 빈도 데이터를 데이터프레임으로 변환
df2 = pd.DataFrame({"Word": counter2.keys(), "freq": counter2.values()})  # 단어와 빈도를 데이터프레임으로 생성
df2['Bank'] = 'Shin'  # 해당 데이터프레임에 'Bank' 열을 추가하고 값으로 'Shin'을 설정

df1

# 결과를 저장할 데이터프레임을 생성하여 피벗 테이블 형태로 변환
result = pd.concat([df1, df2])  # df1과 df2 데이터프레임을 합쳐서 result에 저장
result = result.pivot_table(index='Word', columns='Bank', values='freq').fillna(0)  # 피벗 테이블 생성

# 각 은행의 비율을 계산 (Laplace smoothing을 위해 +1을 추가)
result['IM Ratio'] = (result['IM'] + 1) / sum(result['IM'] + 1)  # IM뱅크의 비율 계산
result['Shin Ratio'] = (result['Shin'] + 1) / sum(result['Shin'] + 1)  # 신한은행의 비율 계산

# 오즈 비율 계산: IM뱅크의 비율 / 신한은행의 비율
result['Odds Ratio'] = result['IM Ratio'] / result['Shin Ratio']  # 오즈 비율 계산

# 오즈 비율을 기준으로 정렬
result.sort_values("Odds Ratio")  # 오즈 비율에 따라 결과를 정렬

 

 


네이버 금융 페이지에서 리뷰 데이터 크롤링

# undetected_chromedriver를 사용하여 Chrome 웹 드라이버를 초기화
driver = uc.Chrome()
# 네이버 금융에서 특정 종목의 게시판을 첫 페이지로 가져옴
driver.get('https://finance.naver.com/item/board.naver?code=055550&page=1')
# 경고 메시지를 무시하도록 설정
import warnings
warnings.filterwarnings('ignore')

# 현재 페이지의 HTML 소스를 BeautifulSoup 객체로 변환
html = BeautifulSoup(driver.page_source)

# HTML에서 테이블을 추출하는 함수 정의
def make_table(html):
    # 'type2' 클래스의 테이블을 찾기.
    table = html.find('table', class_='type2')
    # 찾은 테이블을 pandas DataFrame으로 변환하고 '날짜'와 '제목' 열만 선택한 후, 결측값을 제거
    table = pd.read_html(str(table))[0][['날짜', '제목']].dropna().iloc[1:]  # 첫 번째 행은 불필요하므로 생략
    return table
# 데이터를 저장할 리스트 초기화
box = []
# 페이지 번호 1부터 100까지 반복하여 크롤링
for n in range(1, 101):
    try:
        # 페이지 URL을 생성하여 해당 페이지로 이동
        driver.get(f'https://finance.naver.com/item/board.naver?code=055550&page={n}')
        # 페이지가 완전히 로드될 때까지 최대 10초 기다림.
        driver.implicitly_wait(10)  
        # 현재 페이지의 HTML을 다시 가져오기.
        html = BeautifulSoup(driver.page_source)
        # HTML에서 테이블을 생성하여 box 리스트에 추가
        table = make_table(html)
        box.append(table)
    except:
        # 오류가 발생하면 다음 페이지로 계속 진행
        continue
# 수집한 테이블을 하나의 DataFrame으로 결합
review = pd.concat(box)
# '날짜' 열을 datetime 형식으로 변환하고 날짜만 남김.
review['날짜'] = pd.to_datetime(review['날짜']).dt.date
# 최종 DataFrame을 출력
review

 

# FinanceDataReader 라이브러리 임포트
import FinanceDataReader as fdr

# NAVER의 주식 데이터를 가져와서 DataFrame으로 저장
# 'NAVER:055550'는 NAVER의 종목 코드
df = fdr.DataReader('NAVER:055550').reset_index()

# 'Date' 열을 datetime 형식으로 변환
df['Date'] = pd.to_datetime(df['Date'])

# 'Change' 열의 값이 0보다 큰 경우 1, 그렇지 않은 경우 0으로 변환하여 'Target' 열 생성
df['Target'] = np.where(df['Change'] > 0, 1, 0)

# 'Target' 열의 값을 한 칸 위로 이동시킴. (다음 날의 목표 값을 현재 행에 할당)
df['Target'] = df['Target'].shift(-1)

# 결측값이 있는 행을 삭제
df = df.dropna()
df

 

# '날짜' 열을 datetime 형식으로 변환하여 날짜 데이터를 적절히 처리
review['날짜'] = pd.to_datetime(review['날짜'])

# df DataFrame과 review DataFrame을 병합
# 'Date' 열과 '날짜' 열을 기준으로 병합
df2 = df.merge(review, left_on='Date', right_on='날짜')

# 병합 후 불필요한 '날짜' 열을 삭제
del df2['날짜']

# 최종 병합된 DataFrame을 출력
df2

 

# 'Target' 열의 값의 개수를 세어서 각 클래스의 분포를 확인함
df2['Target'].value_counts()

 

# Okt 형태소 분석기를 사용하기 위해 초기화함
from konlpy.tag import Okt
okt = Okt()

# '제목' 열에서 명사 추출하여 'Nouns' 열에 저장함
df2['Nouns'] = df2['제목'].apply(make_nouns)
df2

# CountVectorizer를 사용하여 텍스트 데이터 벡터화 준비함
cv = CountVectorizer()
cv_matrix = cv.fit_transform(df2['Nouns'])  # Nouns 열에서 단어의 출현 빈도를 기반으로 행렬 생성
cv_matrix

 

# 희소 행렬을 넘파이 배열로 변환함
X = cv_matrix.toarray()
Y = df2['Target'].values  # 타겟 변수인 'Target' 열을 배열로 변환함

# 데이터셋을 학습용과 테스트용으로 나누어 줌
train_x, test_x, train_y, test_y = train_test_split(X, Y)

# RandomForestClassifier 모델 생성하고 학습함
model = RandomForestClassifier()
model.fit(train_x, train_y)

# 테스트 데이터로 예측 수행함
pred = model.predict(test_x)

# 예측 결과에 대한 분류 리포트를 생성함
report = classification_report(test_y, pred)
print(report)

# 모델의 피처 중요도를 기준으로 상위 20개의 단어 인덱스를 가져옴
idx = np.argsort(model.feature_importances_)[::-1][:20]

# 중요도가 높은 단어들의 막대 그래프를 그리기 위한 설정함
plt.bar(x=cv.get_feature_names_out()[idx], height=model.feature_importances_[idx])

# x축 레이블을 90도 회전하여 가독성을 높임
plt.xticks(rotation=90)

# 그래프를 화면에 출력함
plt.show()

# CountVectorizer와 TF-IDF 벡터화 방법을 가져옴
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

# TF-IDF 벡터라이저 객체 생성
cv = TfidfVectorizer()

# 'Nouns' 열의 텍스트 데이터를 TF-IDF 방식으로 벡터화하여 행렬을 생성함
cv_matrix = cv.fit_transform(df2['Nouns'])

# 생성된 희소 행렬을 넘파이 배열로 변환함
cv_matrix = cv_matrix.toarray()

# 벡터화된 결과 행렬을 출력함
cv_matrix

# 주석 처리된 부분: 모든 단어의 이름을 가져오는 메서드
# cv.get_feature_names_out()