ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [코딩_05] 종목명으로 KRX에서 주가정보 가져오기
    개발일지 2022. 12. 27. 00:24
    반응형

     

    종목명으로 주가 정보 가져오기

     

    코딩_03, 코딩_04를 거쳐 드디어 코딩_05이다.

    (이전 글이 보고 싶다면 하단의 더 보기를 눌러서 확인하면 된다.)

    더보기

     

    처음부터 목표로 했던 내용이 '종목명으로 KRX에서 주가 정보를 불러오는 것'이었다.

    이번 글을 보면 모두들 VS코드에서 종목명으로 원하는 기간만큼의 주가 정보를 저장할 수 있을 것이다.

     

    그러면 바로 시작해 보자.

     

    0. 전체 코드 보기

     

    전체 코드 내용만 확인하고 싶을 경우 더 보기를 눌러서 확인하면 된다.

    더보기
    import requests
    import openpyxl
    import datetime
    import time
    import pandas as pd
    from pandas import DataFrame, Series
    from io import BytesIO
    from tqdm import tqdm
    from openpyxl.utils.dataframe import dataframe_to_rows
    
    def mk_all_stock_info():
        """
        KRX에서 제공하는 전종목 기본정보 중 '표준코드', '종목코드', '한글 종목약명'을 데이터프레임에 저장
        
        return: 데이터프레임(한글종목약명(index), 표준코드, 종목코드 )
        """
        
        # generate.cmd > Headers > General > Request URL 주소
        gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
    
        # generate.cmd > Payload > Form Data 내용
        gen_otp_data = {
            'locale': 'ko_KR',
            'mktId': 'ALL',
            'share': 1,
            'csvxls_isNo': 'false',
            'name': 'fileDown',
            'url': 'dbms/MDC/STAT/standard/MDCSTAT01901'
        }
    
        # 로봇으로 인식하지 않게 하기 위함
        headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
        otp = requests.post(gen_otp_url, params=gen_otp_data, headers=headers).text
    
        # download.cmd > Headers > General > Request URL 주소
        down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
    
        down_data = {
            'code' : otp
        }
        # requests로 data 전달
        down_stock_info = requests.post(down_url, params=down_data, headers=headers)
        
        stock_info = pd.read_csv(BytesIO(down_stock_info.content), encoding='EUC-KR', index_col='한글 종목약명')
    
        return stock_info
    
    def find_stock_std_code(name, stock_info):
        """
        종목명을 기준으로 표준코드 반환
    
        Args:
            name (string): 표준코드를 찾고자 하는 종목의 종목명 (한글 종목약명)
            stock_info (dataframe): 한글 종목약명(index), 표준코드, 단축코드를 갖는 데이터프레임
    
        Returns:
            string: 찾고자 하는 종목의 표준코드
        """
        
        try:
            stock_std_code = stock_info.loc[name, '표준코드']
            return stock_std_code
        
        except KeyError:
            print('종목명이 잘못되었습니다')
        
        except:
            print('error')
        
    
    def find_stock_code(name, stock_info):
        """
        종목명을 기준으로 단축코드 반환
    
        Args:
            name (string): 표준코드를 찾고자 하는 종목의 종목명 (한글 종목약명)
            stock_info (dataframe): 한글 종목약명(index), 표준코드, 단축코드를 갖는 데이터프레임
    
        Returns:
            string: 찾고자 하는 종목의 단축코드
        """
        
        try:
            stock_code = stock_info.loc[name, '단축코드']
            return stock_code
        
        except KeyError:
            print('종목명이 잘못되었습니다')
        
        except:
            print('error')
        
    def stock_price_data(name, std_code, code, info, start_day, end_day):
        """
        특정 기간동안의 주가 정보를 데이터프레임 형태로 저장
    
        Args:
            name (string): 종목명
            std_code (string): 표준코드
            code (string): 단축코드
            info (list): 원하는 정보
            start_day: 시작날짜 (ex: 20220103)
            end_day : 종료날짜 (ex: 20220928)
    
        Returns:
            dataframe : 일자(index), 종가
        """
        
        gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
        
        gen_otp_data = {
        'locale': 'ko_KR',
        'tboxisuCd_finder_stkisu0_0': code + '/' + name,
        'isuCd': std_code,
        'isuCd2': code,
        'codeNmisuCd_finder_stkisu0_0': name,
        'param1isuCd_finder_stkisu0_0': 'ALL',
        'strtDd': start_day,
        'endDd': end_day,
        'share': '1',
        'money': '1',
        'csvxls_isNo': 'false',
        'name': 'fileDown',
        'url': 'dbms/MDC/STAT/standard/MDCSTAT01701',
        }
    
        headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
        otp = requests.post(gen_otp_url, gen_otp_data, headers=headers).text
    
        down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
        down_stock_price = requests.post(down_url, {'code':otp}, headers=headers)
    
        stock_price = pd.read_csv(BytesIO(down_stock_price.content), usecols=info, encoding='EUC-KR', index_col='일자')
        
        return stock_price
    
    def new_excel_save(save_path, file_name, sheet_name, data):
        """
        경로, 파일명, 시트명을 주면 새로운 엑셀 파일을 만들어 저장
    
        Args:
            save_path (string): 엑셀 저장 경로
            file_name (string): 엑셀 파일명
            sheet_name (string): 엑셀 시트명
            data (dataframe): 엑셀에 저장하고 싶은 데이터프레임
        """
        
        save_fpath = save_path + file_name + '.xlsx'
        
        wb = openpyxl.Workbook()
        wb.save(save_fpath)
        
        # 엑셀 오픈
        wb = openpyxl.load_workbook(save_fpath)
        wb.create_sheet(sheet_name)
        del wb['Sheet']
        ws = wb[sheet_name]
        
        if type(data) == DataFrame:
            for r in dataframe_to_rows(data):
                ws.append(r)
                
            ws['A1'].value = ws['A2'].value
            ws.delete_rows(2)
            
            wb.save(save_fpath)
            
        elif type(data) == Series:
            data = pd.DataFrame(data)
            
            for r in dataframe_to_rows(data):
                ws.append(r)
                
            ws['A1'].value = ws['A2'].value
            ws.delete_rows(2)
                
            wb.save(save_fpath)
            
        else:
            print('Data 타입이 잘못되었습니다.') 
        
        wb.close()
    
    # 검색하고 싶은 정보 입력
    company = ['삼성전자', 'SK하이닉스']
    stock_name = company
    stock_info = ['일자', '종가']
    start_day = '20100103'
    end_day = '20221014'
    save_fpath = '원하는 경로 설정'
    excel_name = 'test'
    excel_sheet_name = '주가'
    
    # 모든 종목의 기본 정보를 불러옴
    all_stock_info = mk_all_stock_info()
    
    # 잘못된 종목명 있는지 확인
    for name in stock_name:
        if (name in all_stock_info.index) == True:
            pass
        else:
            print('잘못된 종목명을 입력하셨습니다.   ' + name)
    
    # 단일종목의 주가를 저장하는 경우
    if len(stock_name) == 1:
        std_code = find_stock_std_code(stock_name, all_stock_info)
        code = find_stock_code(stock_name, all_stock_info)
        temp_data = stock_price_data(stock_name, std_code, code, stock_info, start_day, end_day)
        new_excel_save(save_fpath, excel_name, excel_sheet_name, temp_data)
        
    # 여러종목의 주가를 저장하는 경우
    elif len(stock_name) != 1:
        std_code = find_stock_std_code(stock_name[0], all_stock_info)
        code = find_stock_code(stock_name[0], all_stock_info)
        temp_df = stock_price_data(stock_name[0], std_code, code, stock_info, start_day, end_day)
        temp_df.columns = [stock_name[0]]
        
        for name in stock_name[1:]:
            std_code = find_stock_std_code(name, all_stock_info)
            code = find_stock_code(name, all_stock_info)
            temp_data = stock_price_data(name, std_code, code, stock_info, start_day, end_day)
            temp_data.columns = [name]
            temp_df = pd.concat([temp_df, temp_data], axis=1)
        new_excel_save(save_fpath, excel_name, excel_sheet_name, temp_df)

     

    1. 라이브러리 및 함수 임포트

     

    이번에도 시작은 라이브러리 import 및 이전에 작성해두었던 함수를 가져오는 것이다.

    이것저것 라이브러리가 많은데, 사실 대부분 이전에 사용한 라이브러리들이다.

     

    라이브러리 Import

    import requests
    import openpyxl
    import datetime
    import time
    import pandas as pd
    from pandas import DataFrame, Series
    from io import BytesIO
    from tqdm import tqdm
    from openpyxl.utils.dataframe import dataframe_to_rows

     

    이전에 작성한 함수 가져오기

    def mk_all_stock_info():
        """
        KRX에서 제공하는 전종목 기본정보 중 '표준코드', '종목코드', '한글 종목약명'을 데이터프레임에 저장
        
        return: 데이터프레임(한글종목약명(index), 표준코드, 종목코드 )
        """
        
        # generate.cmd > Headers > General > Request URL 주소
        gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
    
        # generate.cmd > Payload > Form Data 내용
        gen_otp_data = {
            'locale': 'ko_KR',
            'mktId': 'ALL',
            'share': 1,
            'csvxls_isNo': 'false',
            'name': 'fileDown',
            'url': 'dbms/MDC/STAT/standard/MDCSTAT01901'
        }
    
        # 로봇으로 인식하지 않게 하기 위함
        headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
        otp = requests.post(gen_otp_url, params=gen_otp_data, headers=headers).text
    
        # download.cmd > Headers > General > Request URL 주소
        down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
    
        down_data = {
            'code' : otp
        }
        # requests로 data 전달
        down_stock_info = requests.post(down_url, params=down_data, headers=headers)
        
        stock_info = pd.read_csv(BytesIO(down_stock_info.content), encoding='EUC-KR', index_col='한글 종목약명')
    
        return stock_info
    
    def find_stock_std_code(name, stock_info):
        """
        종목명을 기준으로 표준코드 반환
    
        Args:
            name (string): 표준코드를 찾고자 하는 종목의 종목명 (한글 종목약명)
            stock_info (dataframe): 한글 종목약명(index), 표준코드, 단축코드를 갖는 데이터프레임
    
        Returns:
            string: 찾고자 하는 종목의 표준코드
        """
        
        try:
            stock_std_code = stock_info.loc[name, '표준코드']
            return stock_std_code
        
        except KeyError:
            print('종목명이 잘못되었습니다')
        
        except:
            print('error')
        
    
    def find_stock_code(name, stock_info):
        """
        종목명을 기준으로 단축코드 반환
    
        Args:
            name (string): 표준코드를 찾고자 하는 종목의 종목명 (한글 종목약명)
            stock_info (dataframe): 한글 종목약명(index), 표준코드, 단축코드를 갖는 데이터프레임
    
        Returns:
            string: 찾고자 하는 종목의 단축코드
        """
        
        try:
            stock_code = stock_info.loc[name, '단축코드']
            return stock_code
        
        except KeyError:
            print('종목명이 잘못되었습니다')
        
        except:
            print('error')
            
    def new_excel_save(save_path, file_name, sheet_name, data):
        """
        경로, 파일명, 시트명을 주면 새로운 엑셀 파일을 만들어 저장
    
        Args:
            save_path (string): 엑셀 저장 경로
            file_name (string): 엑셀 파일명
            sheet_name (string): 엑셀 시트명
            data (dataframe): 엑셀에 저장하고 싶은 데이터프레임
        """
        
        save_fpath = save_path + file_name + '.xlsx'
        
        wb = openpyxl.Workbook()
        wb.save(save_fpath)
        
        # 엑셀 오픈
        wb = openpyxl.load_workbook(save_fpath)
        wb.create_sheet(sheet_name)
        del wb['Sheet']
        ws = wb[sheet_name]
        
        if type(data) == DataFrame:
            for r in dataframe_to_rows(data):
                ws.append(r)
                
            ws['A1'].value = ws['A2'].value
            ws.delete_rows(2)
            
            wb.save(save_fpath)
            
        elif type(data) == Series:
            data = pd.DataFrame(data)
            
            for r in dataframe_to_rows(data):
                ws.append(r)
                
            ws['A1'].value = ws['A2'].value
            ws.delete_rows(2)
                
            wb.save(save_fpath)
            
        else:
            print('Data 타입이 잘못되었습니다.') 
        
        wb.close()

     

    2. 주가 정보 크롤링 함수 만들기

     

    라이브러리 Import 및 함수 입력이 끝났다면 본격적으로 주가 정보 크롤링 함수를 만들 차례이다.

    더보기
    def stock_price_data(name, std_code, code, info, start_day, end_day):
        """
        특정 기간동안의 주가 정보를 데이터프레임 형태로 저장
    
        Args:
            name (string): 종목명
            std_code (string): 표준코드
            code (string): 단축코드
            info (list): 원하는 정보
            start_day: 시작날짜 (ex: 20220103)
            end_day : 종료날짜 (ex: 20220928)
    
        Returns:
            dataframe : 일자(index), 종가
        """
        
        gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
        
        gen_otp_data = {
        'locale': 'ko_KR',
        'tboxisuCd_finder_stkisu0_0': code + '/' + name,
        'isuCd': std_code,
        'isuCd2': code,
        'codeNmisuCd_finder_stkisu0_0': name,
        'param1isuCd_finder_stkisu0_0': 'ALL',
        'strtDd': start_day,
        'endDd': end_day,
        'share': '1',
        'money': '1',
        'csvxls_isNo': 'false',
        'name': 'fileDown',
        'url': 'dbms/MDC/STAT/standard/MDCSTAT01701',
        }
    
        headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
        otp = requests.post(gen_otp_url, gen_otp_data, headers=headers).text
    
        down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
        down_stock_price = requests.post(down_url, {'code':otp}, headers=headers)
    
        stock_price = pd.read_csv(BytesIO(down_stock_price.content), usecols=info, encoding='EUC-KR', index_col='일자')
        
        return stock_price

     

    1. 함수 선언

    def stock_price_data(name, std_code, code, info, start_day, end_day):
        """
        특정 기간동안의 주가 정보를 데이터프레임 형태로 저장
    
        Args:
            name (string): 종목명
            std_code (string): 표준코드
            code (string): 단축코드
            info (list): 원하는 정보
            start_day: 시작날짜 (ex: 20220103)
            end_day : 종료날짜 (ex: 20220928)
    
        Returns:
            dataframe : 일자(index), 종가
        """

     

    2. KRX정보데이터 시스템 접속 > 종목시세 > 개별종목 시세 추이 페이지로 이동

    KRX정보데이터 접속 및 페이지 이동

     

    KRX 정보데이터 페이지에 접속해 준다.

    KRX 정보데이터: https://data.krx.co.kr/

     

    정보데이터시스템

     

    data.krx.co.kr

     

    메인페이지 좌측을 보면 여러 메뉴가 있는데 '종목시세' > '개별종목 시세 추이' 페이지로 이동한다.

     

     

    3. 원하는 종목명을 입력 후 CSV로 다운

    종목 주가 정보 CSV 다운로드

     

    개별종목 시세 추이 페이지로 이동했다면,

    주가 정보를 확인하고 싶은 종목명과 기간을 입력해 준다.

    이후 빨간색 1번, 2번을 따라서 CSV 파일을 다운로드해 보자.

     

    4. F12를 눌러 generate.cmd 정보 확인

    generate.cmd 정보 확인

     

    크롤링을 위해 필요한 정보들을 수집할 차례이다.

    컴퓨터에게는 친절하게 한 땀 한 땀 설명을 해줘야 우리의 일을 대신해 준다.

    즉, 크롤링할 URL 주소와 방법, 어떤 정보들을 가져와야 하는지를 말해줘야 한다.

    이런 정보들은 generate.cmd에서 확인할 수 있으며, 코드로 구현하면 다음과 같다.

     

    1. generate.cmd > Headers > General을 들어가 Request URL이라는 것을 복사해 변수로 설정한다.

    2. generate.cmd > Payload에 들어가 Form Data도 복사해 변수로 설정한다.

    3. Post 방식을 활용해 requests를 진행한다.

    gen_otp_url = 'http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd'
        
    gen_otp_data = {
        'locale': 'ko_KR',
        'tboxisuCd_finder_stkisu0_0': code + '/' + name,
        'isuCd': std_code,
        'isuCd2': code,
        'codeNmisuCd_finder_stkisu0_0': name,
        'param1isuCd_finder_stkisu0_0': 'ALL',
        'strtDd': start_day,
        'endDd': end_day,
        'share': '1',
        'money': '1',
        'csvxls_isNo': 'false',
        'name': 'fileDown',
        'url': 'dbms/MDC/STAT/standard/MDCSTAT01701',
    }
    
    headers = {'Referer' : 'http://data.krx.co.kr/contents/MDC/MDI/mdiLoader'}
    otp = requests.post(gen_otp_url, gen_otp_data, headers=headers).text

     

    5. F12를 눌러 download.cmd 정보 확인

     

    download.cmd 정보 확인

     

    이번에는 download.cmd에 있는 정보를 불러올 것이다.

    어려울 것 없다. generate.cmd보다 더 쉽다.

     

    1. download.cmd > Headers > General을 들어가 Request URL이라는 것을 복사해 변수로 설정한다.

    2. Post 방식을 활용해 requests를 진행한다.

    down_url = 'http://data.krx.co.kr/comm/fileDn/download_csv/download.cmd'
    down_stock_price = requests.post(down_url, {'code':otp}, headers=headers)

     

    6. 크롤링한 정보를 dataframe으로 저장 후 Return

     

    이제 download.cmd의 정보를 데이터프레임 형태로 저장 후 Return 해준다.

    stock_price = pd.read_csv(BytesIO(down_stock_price.content), usecols=info, encoding='EUC-KR', index_col='일자')
        
    return stock_price

     

    3. 주가 정보 크롤링 함수 실행

     

    이제 크롤링을 위한 거의 모든 준비가 끝났다.

    함수를 만들었으니 직접 실행해보면서 작동이 잘 되는지 확인을 해야 하는데,

    한 가지 고민해볼 일이 있다.

     

    바로 주가정보를 한 종목씩 가져올 것인가, 여러 종목을 한 번에 가져올 것인가에 대해 고민을 해봐야 한다.

    당연히 나는 여러 종목을 한 번에 가져오는 것도 고려하는 게 좋다고 생각을 한다.

    그렇기에 if문을 통해 경우의 수를 구분해서 엑셀로 저장할 데이터프레임을 만들어야 한다.

     

    순서는 다음과 같다.

     

    1. 검색하고 싶은 기본 정보들을 정리한다.

    2. 모든 종목의 기본 정보를 불러온다.

    3. 주가 정보를 불러오는 것이 단일 종목인지 여러 종목인지 경우의 수를 구분해 함수를 호출한다.

    company = ['삼성전자', 'SK하이닉스']
    
    # 검색하고 싶은 정보 입력
    stock_name = company
    stock_info = ['일자', '종가']
    start_day = '20100103'
    end_day = '20221014'
    save_fpath = '사용자가 원하는 경로'
    excel_name = 'test'
    excel_sheet_name = '주가'
    
    # 모든 종목의 기본 정보를 불러옴
    all_stock_info = mk_all_stock_info()
    
    # 잘못된 종목명 있는지 확인
    for name in stock_name:
        if (name in all_stock_info.index) == True:
            pass
        else:
            print('잘못된 종목명을 입력하셨습니다.   ' + name)
    
    # 단일종목의 주가를 저장하는 경우
    if len(stock_name) == 1:
        std_code = find_stock_std_code(stock_name, all_stock_info)
        code = find_stock_code(stock_name, all_stock_info)
        temp_data = stock_price_data(stock_name, std_code, code, stock_info, start_day, end_day)
        new_excel_save(save_fpath, excel_name, excel_sheet_name, temp_data)
        
    # 여러종목의 주가를 저장하는 경우
    elif len(stock_name) != 1:
        std_code = find_stock_std_code(stock_name[0], all_stock_info)
        code = find_stock_code(stock_name[0], all_stock_info)
        temp_df = stock_price_data(stock_name[0], std_code, code, stock_info, start_day, end_day)
        temp_df.columns = [stock_name[0]]
        
        for name in stock_name[1:]:
            std_code = find_stock_std_code(name, all_stock_info)
            code = find_stock_code(name, all_stock_info)
            temp_data = stock_price_data(name, std_code, code, stock_info, start_day, end_day)
            temp_data.columns = [name]
            temp_df = pd.concat([temp_df, temp_data], axis=1)
        new_excel_save(save_fpath, excel_name, excel_sheet_name, temp_df)

     

    이상으로 종목명으로 KRX에서 주가정보 가져오기를 해보았다.

    어렵고 복잡해 보일 수 있지만 사실 특별히 어려울 일이 없다고 생각한다.

    모두들 따라 할 수 있으니 실제로 사용해보고 실제 투자에 도움이 되었으면 한다.

     

    반응형

    댓글

Designed by Tistory.