[저평가 분석] 전자공시시스템 다트 오픈 API, OPEN Dart 사용법

오픈 다트 API를 사용하기 위해 먼저 API 인증키를 발급받아야 한다. 오픈 다트 사이트에 접속하고 인증키 신청을 누른 뒤, 몇 가지 약관에 동의하고 회원가입과 인증 절차를 거치면 바로 발급받을 수 있다. 발급받은 인증키는 인증키 신청/관리 텝의 오픈API 이용현황에서 확인할 수 있으며 keyring 라이브러리를 이용하면 다음과 같이 API 인증키를 필요할 때 편하게 꺼내 쓸 수 있다.

import keyring

keyring.set_password("system", "username", "password")
keyring.get_password("system", "username")

개발가이드 텝을 보면 오픈 다트 API를 통해 사용할 수 있는 API와 각 API를 통해 얻을 수 있는 정보가 무엇인지 확인할 수 있다.

개발가이드

개발가이드


고유번호 불러오기

고유번호 API 개발가이드를 살펴보자. 오픈 다트 API에서는 각 기업을 식별할 때 기업 고유번호를 사용한다. 기업 고유번호는 고유번호 API로 확인할 수 있으므로 다른 API를 사용하기 위해 먼저 알아둘 필요가 있다.

고유번호 개발가이드

고유번호 개발가이드


기본 정보를 보면 메서드, 요청URL, 인코딩 방식, 출력포멧이 무엇인지 확인할 수 있다. 요청URL에 GET 메서드를 활용해서 UTF-8로 인코딩되어 있는 기업 고유번호를 zip 파일로 받을 수 있다는 의미이다.

요청 인자의 요청키를 보면 ‘crtfc_key’이고 설명을 보면 발급받은 인증키라고 한다.

이러한 설명을 참고하여 고유번호 API를 통해 기업 고유번호를 받아보자.

import keyring
from typing import Optional
import pprint
import requests
from io import BytesIO
import zipfile
import xmltodict
import json
import pandas as pd

DART_API_KEY = keyring.get_password("open_dart_api", "gimmaru")

def get_corp_code(DART_API_KEY: str) -> pd.DataFrame:
    corp_code_url = f"https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key={DART_API_KEY}"
    corp_code_byte = requests.get(corp_code_url)
    assert corp_code_byte.status_code == 200, f"{corp_code_byte.status_code} HTTP Error\n\n See the following sites: https://httpstatusdogs.com/"

    corp_code_zip = BytesIO(corp_code_byte.content)
    
    if zipfile.is_zipfile(corp_code_zip):
        corp_code_unzip = zipfile.ZipFile(corp_code_zip)
        corp_code_xml = corp_code_unzip.read('CORPCODE.xml').decode('utf-8')
        corp_code_dict = xmltodict.parse(corp_code_xml)
        return pd.DataFrame(corp_code_dict['result']['list'])
    else:
        status_code = xmltodict.parse(corp_code_byte.content)['result']['status']
        assert status_code == '000', f'{status_code} Error\n\nCheck Message Explanation: https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001&apiId=2019018'

요청URL의 뒷 부분에 ‘?’를 붙이고 요청키와 발급받은 다트 API 키를 넘겨주었다. URL에서 ?는 GET 방식으로 서버에 데이터를 요청할 때 사용된다. ? 뒤에서부터 요청할 데이터가 작성된다는 의미이다.

출력포멧이 ‘Zip File(binary)’이므로 바이트 타입인 zip으로 압축된 파일을 반환된다. 바이트 타입의 raw_corp_code.content 파일을 zipfile로 처리할 수 있도록 BytesIO를 통해 zip 파일로 변환해주고

zip 파일의 압축을 해제하고 나온 ‘CORPCODE.xml’ 파일이 utf-8로 인코딩되었으므로 그에 맞춰 디코딩해줬다.

그 후 xmltodict을 통해 xml 파일을 파이썬 딕셔너리로 변환해주고 최종적으로 데이터프레임 형태로 반환하였다.

반환된 데이터프레임의 ‘corp_code’ 열의 값이 고유번호를 의미한다.

df_code = get_corp_code(DART_API_KEY)
df_code.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 104025 entries, 0 to 104024
Data columns (total 4 columns):
 #   Column       Non-Null Count   Dtype 
---  ------       --------------   ----- 
 0   corp_code    104025 non-null  object
 1   corp_name    104025 non-null  object
 2   stock_code   3697 non-null    object
 3   modify_date  104025 non-null  object
dtypes: object(4)
memory usage: 3.2+ MB
df_code.sample(5)
corp_codecorp_namestock_codemodify_date
9831601748888엠케이트레이딩None20230502
10274401777941하이클래스디벨롭None20230801
234800343987오떼마찌엘티디None20170630
5008801506149남악개발대부None20201014
9586501759510에스엘씨엔씨None20230510

결과를 확인해보면 고유번호 API를 통해 십만여개 기업에 대한 고유번호를 얻었음을 알 수 있다.

그런데 ‘stock_code’ 열을 보면 None 값인 경우가 많다. 이러한 경우 해당 기업이 상장되지 않았음을 의미한다. 데이터프레임의 info를 보면 None인 경우도 null 값으로 처리되고 있다. None도 null 값으로 간주되므로 데이터프레임의 dropna 메서드로 비상장기업을 제거할 수 있다.

df_code = (
    df_code
    .dropna()
    .reset_index(drop=True)
)

df_code
corp_codecorp_namestock_codemodify_date
000260985한빛네트03672020170630
100264529엔플렉스04013020170630
200358545동서정보기술05500020170630
300231567애드모바일03260020170630
400247939씨모스03760020170630
...............
369200126414삼성제약00136020231005
369300116426코센00973020231205
369400107987남해화학02586020231205
369501068658디딤이앤에프21762020231206
369600222213피케이엘03987020231206

3697 rows × 4 columns

상장기업 재무정보 불러오기

이렇게 가져온 기업 고유번호로 상장기업 재무정보를 가져올 수 있다. 개발가이드의 상장기업 재무정보를 통해 확인할 수 있는 오픈 다트의 API는 다음과 같다.

개발가이드 상장기업 재무정보 목록

개발가이드 상장기업 재무정보 목록


그 중 단일회사 전체 재무제표를 불러와 보자.

단일회사 전체 재무제표 개발가이드

단일회사 전체 재무제표 개발가이드


단일회사 전체 재무제표 개발가이드의 기본 정보를 보면 요청URL이 2개다. 각각 출력포멧이 ‘json’, ‘xml’이라는 점이 차이가 있다.

요청인자의 요청키를 보면 ‘API 인증키’, ‘고유번호’, ‘사업연도’, ‘보고서 코드’, ‘개별/연결구분’이 있다. 해당 값들을 바꿔 전달하면서 원하는 기업과 시기에 맞는 재무정보를 불러올 수 있으며, 2015년 이후 정보에 한해서 제공하고 있다는 점은 유의해야 한다.

예시로 확인해볼 기업은 삼성제약이다. 삼성제약의 고유번호는 다음과 같이 확인할 수 있다.

print( df_code.loc[df_code['corp_name'] == '삼성제약', 'corp_code'].values[0] )
00126414
def get_financial_statements(
    DART_API_KEY: str, 
    CORP_CODE: str, 
    BSNS_YEAR: str, 
    REPRT_CODE: str, 
    FS_DIV: str
) -> pd.DataFrame:

    fs_url = f"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?crtfc_key={DART_API_KEY}&corp_code={CORP_CODE}&bsns_year={BSNS_YEAR}&reprt_code={REPRT_CODE}&fs_div={FS_DIV}"
    fs_raw = requests.get(fs_url)
    assert fs_raw.status_code == 200, f"{fs_raw.status_code} HTTP Error\n\n See the following sites: https://httpstatusdogs.com/"

    fs_dict = fs_raw.json()
    assert fs_dict['status'] == '000', f"{fs_dict['status']} Error\n\n {fs_dict['message']}"

    return pd.DataFrame(fs_dict['list'])

df_fs = get_financial_statements(
    DART_API_KEY=DART_API_KEY,
    CORP_CODE = df_code.loc[df_code['corp_name'] == '삼성제약', 'corp_code'].values[0],
    BSNS_YEAR = '2015',
    REPRT_CODE = '11011',
    FS_DIV = 'CFS',
)
df_fs
rcept_noreprt_codebsns_yearcorp_codesj_divsj_nmaccount_idaccount_nmaccount_detailthstrm_nmthstrm_amountfrmtrm_nmfrmtrm_amountbfefrmtrm_nmbfefrmtrm_amountordcurrencythstrm_add_amount
02016052700037611011201500126414BS재무상태표ifrs_NoncurrentAssets비유동자산-제 62 기64720596190제 61 기31496125317제 60 기348521829241KRWNaN
12016052700037611011201500126414BS재무상태표ifrs_PropertyPlantAndEquipment유형자산-제 62 기42434108210제 61 기30515895159제 60 기328392734232KRWNaN
22016052700037611011201500126414BS재무상태표ifrs_IntangibleAssetsOtherThanGoodwill영업권 이외의 무형자산-제 62 기6348561908제 61 기256406221제 60 기3637479863KRWNaN
32016052700037611011201500126414BS재무상태표ifrs_InvestmentProperty투자부동산-제 62 기6589966350제 61 기제 60 기4KRWNaN
42016052700037611011201500126414BS재무상태표dart_GoodwillGross영업권-제 62 기2712305285제 61 기제 60 기5KRWNaN
.........................................................
1582016052700037611011201500126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]제 62 기46236610988제 61 기23294889189제 60 기1874126161915KRWNaN
1592016052700037611011201500126414SCE자본변동표ifrs_Equity기말자본연결재무제표 [member]제 62 기46236614499제 61 기23294889189제 60 기1874126161915KRWNaN
1602016052700037611011201500126414SCE자본변동표ifrs_Equity기말자본자본 [member]|소수주주지분제 62 기3511제 61 기제 60 기15KRWNaN
1612016052700037611011201500126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|연결자본잉여금제 62 기41304925854제 61 기21429147754제 60 기2076589903415KRWNaN
1622016052700037611011201500126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|자본금 [...제 62 기13791812000제 61 기11430685000제 60 기581952350015KRWNaN

163 rows × 18 columns

오픈 다트 API를 통해 삼성제약의 2015년 연간 연결재무제표를 쉽게 불러왔다. 불러온 데이터프레임을 각자의 필요에 맞춰 가공하여 사용하면 유용할 것이다.

2015년 기준으로 데이터를 불러왔을 때, 2015년 이전 2개년도 정보도 함께 확인 가능하다. 2024년 2월 13일 기준으로 2013년 재무제표까지 오픈 다트 API를 통해 얻을 수 있다. 3개년도 정보를 한번에 확인 가능하므로 전체 연도 정보를 불러오고자 할땐 3년 단위 정보를 불러오면 될 것 같다. 현재 2015년 기준으로 60기, 61기, 62기 재무제표를 불러왔으므로 2018년을 기준으로 불러온다면 63기, 64기, 65기 재무제표를 불러올 수 있을 것이다.

df_fs_2018 = get_financial_statements(
    DART_API_KEY=DART_API_KEY,
    CORP_CODE = df_code.loc[df_code['corp_name'] == '삼성제약', 'corp_code'].values[0],
    BSNS_YEAR = '2018',
    REPRT_CODE = '11011',
    FS_DIV = 'CFS',
)

df_fs_2018
rcept_noreprt_codebsns_yearcorp_codesj_divsj_nmaccount_idaccount_nmaccount_detailthstrm_nmthstrm_amountfrmtrm_nmfrmtrm_amountbfefrmtrm_nmbfefrmtrm_amountordcurrencythstrm_add_amount
02019040100371611011201800126414BS재무상태표ifrs_CurrentAssets유동자산-제 65 기79724616717제 64 기60978260818제 63 기477114527661KRWNaN
12019040100371611011201800126414BS재무상태표ifrs_CashAndCashEquivalents현금및현금성자산-제 65 기3151357753제 64 기2931620456제 63 기53901803602KRWNaN
22019040100371611011201800126414BS재무상태표dart_ShortTermDepositsNotClassifiedAsCashEquiv...단기금융자산-제 65 기1200000000제 64 기1200000000제 63 기12000000003KRWNaN
32019040100371611011201800126414BS재무상태표dart_ShortTermTradeReceivable매출채권-제 65 기16699562155제 64 기15432793585제 63 기155404721454KRWNaN
42019040100371611011201800126414BS재무상태표dart_CurrentFinancialAssetHeldForTrading단기매매금융자산-제 65 기3816549555제 64 기4230007815제 63 기40529316005KRWNaN
.........................................................
1722019040100371611011201800126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 지분 [member]제 65 기131662977152제 64 기93868490973제 63 기8237776390017KRWNaN
1732019040100371611011201800126414SCE자본변동표ifrs_Equity기말자본자본 [member]|비지배지분 [member]제 65 기제 64 기제 63 기101317KRWNaN
1742019040100371611011201800126414SCE자본변동표ifrs_Equity기말자본연결재무제표 [member]제 65 기131662977152제 64 기93868490973제 63 기8237776491317KRWNaN
1752019040100371611011201800126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 지분 [member]|이익잉여금...제 65 기-3379583894제 64 기-49654622640제 63 기-4343486786117KRWNaN
1762019040100371611011201800126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 지분 [member]|기타포괄손...제 65 기24610931430제 64 기24646132247제 63 기1276571456817KRWNaN

177 rows × 18 columns

다음과 같이 ‘REPRT_CODE’ 인자를 수정해 3분기 데이터를 불러올 수 있다. 1분기, 반기 데이터도 해당 요청 인자를 수정하여 확인할 수 있다.

df_fs_3q = get_financial_statements(
    DART_API_KEY=DART_API_KEY,
    CORP_CODE = df_code.loc[df_code['corp_name'] == '삼성제약', 'corp_code'].values[0],
    BSNS_YEAR = '2016',
    REPRT_CODE = '11014',
    FS_DIV = 'CFS',
)

df_fs_3q
rcept_noreprt_codebsns_yearcorp_codesj_divsj_nmaccount_idaccount_nmaccount_detailthstrm_nm...frmtrm_nmfrmtrm_amountbfefrmtrm_nmbfefrmtrm_amountordcurrencythstrm_add_amountfrmtrm_q_nmfrmtrm_q_amountfrmtrm_add_amount
02016112900044611014201600126414BS재무상태표ifrs_NoncurrentAssets비유동자산-제 63 기 3분기말...제 62 기말64720596190제 61 기말314961253171KRWNaNNaNNaNNaN
12016112900044611014201600126414BS재무상태표ifrs_PropertyPlantAndEquipment유형자산-제 63 기 3분기말...제 62 기말42434108210제 61 기말3051589515916KRWNaNNaNNaNNaN
22016112900044611014201600126414BS재무상태표ifrs_IntangibleAssetsOtherThanGoodwill영업권 이외의 무형자산-제 63 기 3분기말...제 62 기말6348561908제 61 기말25640622156KRWNaNNaNNaNNaN
32016112900044611014201600126414BS재무상태표dart_GoodwillGross영업권-제 63 기 3분기말...제 62 기말2712305285제 61 기말75KRWNaNNaNNaNNaN
42016112900044611014201600126414BS재무상태표ifrs_OtherNoncurrentFinancialAssets기타비유동금융자산-제 63 기 3분기말...제 62 기말6635654437제 61 기말72382393785KRWNaNNaNNaNNaN
..................................................................
1742016112900044611014201600126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]제 63 기 3분기...제 62 기46236610988제 61 기2329488918924KRWNaN제 62 기 3분기36795377012NaN
1752016112900044611014201600126414SCE자본변동표ifrs_Equity기말자본연결재무제표 [member]제 63 기 3분기...제 62 기46236614499제 61 기2329488918924KRWNaN제 62 기 3분기36795381071NaN
1762016112900044611014201600126414SCE자본변동표ifrs_Equity기말자본자본 [member]|비지배지분 [member]제 63 기 3분기...제 62 기3511제 61 기24KRWNaN제 62 기 3분기4059NaN
1772016112900044611014201600126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|미처분이익...제 63 기 3분기...제 62 기-21702923038제 61 기-2374818105924KRWNaN제 62 기 3분기-22832410583NaN
1782016112900044611014201600126414SCE자본변동표ifrs_Equity기말자본자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|자본금 [...제 63 기 3분기...제 62 기13791812000제 61 기1143068500024KRWNaN제 62 기 3분기13223954500NaN

179 rows × 21 columns

마무리

위에서 다룬 것과 유사한 방식으로 오픈 다트 API에서 제공하는 다양한 기업 정보를 불러와서 사용할 수 있다. 근 2주 동안 저평가 분석기를 만들기 위해 플러터를 공부하면서 ‘앱 프론트앤드 부분은 어떻게 하겠는데 원하는 기능을 구현하기 위해 필요한 데이터를 쉽게 구할 수 있을까’ 하는 걱정이 있었다. 오늘 오픈 다트 API를 알아보며 대부분 필요한 정보를 다트를 통해 구할 수 있을 것 같아 조금 마음이 놓인다. 이제는 구체화한 기능을 구현하기 위해 꼭 필요한 데이터를 구분하고, 데이터베이스를 어떻게 설계할지 공부하고, 파이어베이스나 수파베이스 활용법을 알아보며 백엔드 부분을 준비해야겠다.

참고자료

소스링크일자
Youtube[파이썬 퀀트] 18강 - DART API를 이용해 공시정보 및 재무제표 수집하기230307





© 2023. by gimmaru

Powered by aiden