[NewsSentiment] News Sentiment Analysis Part1: URL 수집


개요

News Sentiment Analysis를 위한 뉴스 url 링크를 수집합니다.

이전 sentiment analysis는 Reddit의 개인 투자자를 대상으로 하였습니다. 상당히 한정된 대상이고, 그들의 관심 종목이 주로 고성장주인지라 시장 전체의 분위기를 파악하는 것과는 방향이 맞지 않았습니다. 그래서, 미국 뉴스 기사들 중 경제나 주식 시장과 관련이 있을 것으로 보이는 (금융, 경제, 정치, 비즈니스, 일반 뉴스, 세계 뉴스) 뉴스 기사들을 가져와 sentiment analysis를 진행합니다.

News Sentiment Index (NSI)는 뉴스 기사들이 긍정적인가 부정적인가를 계산한 후 여러 가지 기법을 적용해 산출한 것이므로, S&P 500 지수를 선행하거나 추종한다고 할 수는 없습니다. 그때그때의 시황을 참고하여 고려할 하나의 지표일 뿐입니다. News Sentiment Index (NSI)의 활용은 개인마다 다른 방법을 생각해 낼 것입니다. 우선 News Sentiment 자체를 주가 예측에 직접 활용할 수 없는 이유는 다음과 같습니다.

  • 모든 뉴스가 주가에 영향을 미치는 것은 아닙니다.
  • 일반적으로 완전히 새로운 뉴스일 경우 sentiment가 부정적이면 새로운 악재나 불확실성처럼 작용하여 주가에 부정적이고, sentiment가 긍정적이면 호재로 작용하여 주가에 긍정적입니다.
  • 이미 시장에 반영이 다 된 뉴스일 경우 sentiment가 부정적인데 주가는 올라 무관하게 움직이는 경우도 많습니다.
  • 시장에 반영이 다 되지는 않았지만 완전히 새로운 뉴스도 아닐 경우에는, 시장이 어떻게 해석하고 받아들이냐에 달려 있습니다.

결국 Sentiment 그 자체로 지수 수익률을 예측하는 것은 어렵고, 이 news sentiment analysis의 목적은 다음과 같습니다.

  • 시간이 없어 읽지 못했거나 관심사의 한계로 읽지 못한 기사들을 반영해줍니다. 2008년 금융 위기 때를 예로 들면, 서브프라임 모기지에 관한 부정적 기사들은 금융 위기가 터지기 1, 2년 전부터 구석에 기사가 나오기 시작했는데 헤드라인만 보거나 대충 보는 사람들은 그것을 알기 힘들었습니다. 컴퓨터가 저 대신 읽고 판단을 도와줄 것입니다.
  • Sentiment 변화가 있을 경우, 그것을 long/short, buy/sell 신호로 가져갈 수는 없지만, 시장의 심리가 변하는 시점이므로 스스로의 뷰를 검토하는 시점으로 삼을 수 있습니다.

필요한 라이브러리를 가져옵니다.

from newscatcher import Newscatcher, describe_url, urls
import requests
from bs4 import BeautifulSoup
import pandas as pd
from datetime import datetime
import numpy as np
import math
import time
import calendar
import json
import sqlite3

nytimes.com과 같이 뉴스 기사의 기초 url이 되는 것들을 가져옵니다.

base_urls = []

with open('base_urls.txt', 'r') as f:
    lines = f.readlines()
    for i in range(len(lines)):
        base_urls.append(lines[i].strip())

2021년 4월 11일 기준으로 사용 가능한 소스는 nytimes, wsj, cnn 등 1514개입니다. 이들 중 데이터를 구해올 수 있는지, 어느 정도 규칙적으로 많은 기사를 내고 있는지, 공신력이 있는 편인지를 감안하여 잘 알려진 신문(뉴스)으로 제한합니다. LexisNexis 같은 데이터 제공자는 자체 정제를 해서 기사 text를 주고 있으나, 비싸고 개인이라서 접근하기 어렵습니다. 데이터 수집을 크롤링 방식으로 하기 때문에 소스 제한을 두어야 다운받고 작업할 시간이 확보됩니다.

base_url을 newscatcher 라이브러리를 사용해서 접속 후 topic별로 기사를 가져옵니다. 각 base_url, topic에 대하여 title, publish_date, link 등 필요한 내용을 뽑아 데이터프레임으로 만듭니다.

# [source, topic, title, publish_date, link, text] --> 이렇게 해서 link를 뉴스 기사 url로 보고 newspaper3k 패키지로 내용 가져올 것
# link까지가 newscatcher, text는 newspaper3k로 가져옴

news_base_data = []
number = 0

for base_url in base_urls:
    
    number += 1
    print(number)
    
    # base_url의 topic들
    topics = describe_url(base_url)['topics']
    
    # topic들 각각에 대하여
    try:
        for topic in topics:
            nc = Newscatcher(base_url, topic = topic)
            results = nc.get_news()
            articles = results['articles']

            # base_url - topic pair에 맞는 기사 각각에 대하여
            for article in articles:
                title = article['title']
                pub_date = article['published']
                link = article['link']

                temp = []

                temp.append(base_url)
                temp.append(topic)
                temp.append(title)
                temp.append(pub_date)
                temp.append(link)

                news_base_data.append(temp)

    except:
        print(base_url, topic, title)
df_news_base_data = pd.DataFrame(news_base_data, columns=['source','topic','title','publish_date','link'])
df_news_base_data = df_news_base_data.drop_duplicates()
df_news_base_data = df_news_base_data.reset_index(drop=True)

시장에 영향을 줄 수 있을 것으로 보이는 news, business, finance, politics, economics, world로 topic을 한정합니다. Sports, Entertainment, Food, Travel, Tech topic이 더 있는데 해당 topic에서 대부분의 기사들은 시장과 무관하여 제외했습니다.

# topic은 news, business, finance, politics, economics, world (시장에 영향을 줄 수 있다고 생각되는 topic)
is_news = df_news_base_data['topic']=='news'
is_business = df_news_base_data['topic']=='business'
is_finance = df_news_base_data['topic']=='finance'
is_politics = df_news_base_data['topic']=='politics'
is_economics = df_news_base_data['topic']=='economics'
is_world = df_news_base_data['topic']=='world'

df_news_base_data = df_news_base_data[is_news|is_business|is_finance|is_politics|is_economics|is_world].reset_index(drop=True)

이전까지 작업했던 url db와 지금 새로 받아온 url db를 비교해서 새로 추가된 부분만 url db에 추가하도록 합니다.

# df_news_base_data하고 news_urls 테이블에 있는 것의 차집합을 구한 뒤 그 차집합을 news_urls 테이블에 업데이트함  
con = sqlite3.connect('news_urls.db')  
df_news_url_db = pd.read_sql('select * from news_urls', con=con)  
con.close()  
# 원래 있던 db와 비교해야 하니 원래 db를 가져옵니다.  
df_news_url_db = df_news_url_db.loc[:, ['source','topic','title','publish_date','link']]  
# 새로 받아온 것에서 원래 db에 있는 것들을 날림  
df_new = pd.merge(df_news_base_data, df_news_url_db, how='outer', indicator='Exist')  
df_new = df_new.loc[df_new['Exist']=='left_only']  
df_new = df_new.loc[:, ['source','topic','title','publish_date','link']]  
df_new = df_new.reset_index(drop=True)  
con = sqlite3.connect('news_urls.db')  
df_new.to_sql('news_urls', con, if_exists='append', index_label='id')  
con.close()  





© 2021.03. by JacobJinwonLee

Powered by theorydb