네이버 주식 서비스로부터 종목 시가, 종가, 고가, 저가, 거래량, 전일대비에 대한 시계열 데이터 얻기

주식 종목에 대한 하루 단위의 시가, 종가, 거래량 등을 웹을 통해 얻을 수 있는데요. 이러한 데이터를 파이썬을 이용해, 네이버의 주식 서비스로부터 얻는 코드에 대해 설명합니다.

코드는 다음과 같습니다. 원하는 종목에 대해 원하는 페이지만큼.. (이 부분은 주식 서비스마다 가능 여부 및 방식이 달라짐) 정보를 얻어옵니다.

import requests
from bs4 import BeautifulSoup

def print_stock_price(code, page_num):
    result = [[], [], [], [], [], [], [], [], []]

    for n in range(page_num):
        url = 'https://finance.naver.com/item/sise_day.nhn?code='+code+'&page='+str(n+1)
        print(url)

        r = requests.get(url)

        if not r.ok: 
            print('Not more data !')
            break

        html = r.content
        soup = BeautifulSoup(html, 'html.parser')
        tr = soup.select('table > tr')

        for i in range(1, len(tr)-1):
            td = tr[i].select('td')
            if td[0].text.strip():
                result[0].append(td[0].text.strip()) # 날짜
                result[1].append(td[1].text.strip()) # 종가
                
                img = td[2].select('img')
                if len(img) != 0: 
                    if 'src' in img[0].attrs:
                        src = img[0]['src']
                        if 'up' in src: result[2].append('상승')
                        else: result[2].append('하락')
                else: result[2].append('보합')

                result[3].append(td[2].text.strip()) # 전일대비
                result[4].append(td[3].text.strip()) # 시장가
                result[5].append(td[4].text.strip()) # 최고가
                result[6].append(td[5].text.strip()) # 최저가
                result[7].append(td[6].text.strip()) # 거래량

    for i in range(len(result[0])):
        #     날짜          종가           상승/하락/보합+a           시장가         최고가        최저가        거래량
        print(result[0][i], result[1][i], result[2][i]+result[3][i], result[4][i], result[5][i], result[6][i], result[7][i])

print_stock_price(code='005930', page_num=1)

코드를 보면, print_stock_price 함수의 url 변수에 저장된 주소에 대한 결과 DOM을 해석하고 있는 것을 알 수 있습니다. 즉, DOM에 대한 구조를 먼저 파악해야 한다는 것이 핵심인데요. 위의 코드가 정상적으로 작동할 당시의 실제 DOM의 한가지 예는 다음과 같습니다.

코드와 추출하고자 하는 DOM 요소가 명확하게 1:1로 매칭되고 있는 것을 확인할 수 있습니다.

최적 가중치값을 얻기 위한 경사하강법을 Python 코드로 이해하기

이 글은 텐서플로나 파이토치와 같은 같은 딥러닝 라이브러리의 기반을 이해하거나, 직접 개발하고자 할때 참고할 만한 글로 작성한 코드는 이해를 위해 나이브하게 작성했다 . 아래는 이 글에서 사용할 모델로 단순화를 위해 은닉층은 없고 입력층과 출력층만이 존재한다.

입력값 x와 해당 입력값에 대한 라벨값 t는 다음과 같다.

x = np.array([20.1, 32.2])
t = np.array([0, 0, 1])

데이터가 단일 항목인데, 실제는 그 개수가 상당이 많을 것이다. 가중치 W는 입력값의 특성개수가 2개이고 츨력값의 분류수가 3개이므로 2×3 행렬이며, 초기값은 아래처럼 난수로 잡는다.

W = np.random.randn(2, 3)

모델에 대한 입력 데이터가 정해졌으므로, 초기에 난수로 정한 가중치값을 보정하기 위한 방법인 경사하강법에 대한 함수인 gradient를 다음처럼 사용할 수 있다.

dW = gradient(x, t, W)
print(dW)

gradient 함수는 인자로 입력값과 라벨값 그리고 보정할 가중치값이 저장된 행렬을 받으며, 가중치값들을 보정할 경사도(미분값)를 가중치 행렬과 동일한 크기로 반환한다. 먼저 손실함수 loss는 다음과 같다.

def predict(input, weight):
    return np.dot(input, weight)

def softmax(input):
    input = input - np.max(input)
    return np.exp(input) / np.sum(np.exp(input))

def cee(activated, label):
    label = label.argmax(axis=0)
    result = -np.sum(np.log(activated[label] + 1e-7))

    return result    

def loss(input, label, weight):
    output = predict(input, weight)
    activated = softmax(output)
    loss = cee(activated, label)

    return loss

손실함수는 교차 엔트로피 오차(Cross Entropy Error, CEE) 함수를 사용했으며, 출력층에 대한 활성화 함수는 Softmax를 사용한 것을 알 수 있다. 이제 gradient 함수는 아래와 같다.

def gradient(input, label, weight):
    h = 1e-4
    grad = np.zeros_like(weight)
    
    it = np.nditer(weight, flags=['multi_index'])
    while not it.finished:
        idx = it.multi_index
        v = weight[idx]

        weight[idx] = v + h
        fxh1 = loss(input, label, weight)
        
        weight[idx] = v - h 
        fxh2 = loss(input, label, weight)
        
        grad[idx] = (fxh1 - fxh2) / (2*h)
        
        weight[idx] = v

        it.iternext()   
        
    return grad

가중치에 대한 W의 각 요소별로 편미분을 기울기 방향을 구하고 있다. 이 기울기 방향으로 일정한 길이만큼 가중치값을 이동해 주는 것을 반복하면 최소의 손실값을 갖는 가중치들의 모음을 얻을 수 있게 된다. 기울기 방향을 구하기 위한 방법으로 편미분에 대한 수치해석 기법을 활용했으나, 실제 텐서플로우나 파이토치 등과 같은 머신러닝 라이브러리에서는 역전파기법을 활용한다.