[저평가 분석] 전자공시시스템 다트 오픈 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_code | corp_name | stock_code | modify_date | |
---|---|---|---|---|
98316 | 01748888 | 엠케이트레이딩 | None | 20230502 |
102744 | 01777941 | 하이클래스디벨롭 | None | 20230801 |
2348 | 00343987 | 오떼마찌엘티디 | None | 20170630 |
50088 | 01506149 | 남악개발대부 | None | 20201014 |
95865 | 01759510 | 에스엘씨엔씨 | None | 20230510 |
결과를 확인해보면 고유번호 API를 통해 십만여개 기업에 대한 고유번호를 얻었음을 알 수 있다.
그런데 ‘stock_code’ 열을 보면 None 값인 경우가 많다. 이러한 경우 해당 기업이 상장되지 않았음을 의미한다. 데이터프레임의 info를 보면 None인 경우도 null 값으로 처리되고 있다. None도 null 값으로 간주되므로 데이터프레임의 dropna 메서드로 비상장기업을 제거할 수 있다.
df_code = (
df_code
.dropna()
.reset_index(drop=True)
)
df_code
corp_code | corp_name | stock_code | modify_date | |
---|---|---|---|---|
0 | 00260985 | 한빛네트 | 036720 | 20170630 |
1 | 00264529 | 엔플렉스 | 040130 | 20170630 |
2 | 00358545 | 동서정보기술 | 055000 | 20170630 |
3 | 00231567 | 애드모바일 | 032600 | 20170630 |
4 | 00247939 | 씨모스 | 037600 | 20170630 |
... | ... | ... | ... | ... |
3692 | 00126414 | 삼성제약 | 001360 | 20231005 |
3693 | 00116426 | 코센 | 009730 | 20231205 |
3694 | 00107987 | 남해화학 | 025860 | 20231205 |
3695 | 01068658 | 디딤이앤에프 | 217620 | 20231206 |
3696 | 00222213 | 피케이엘 | 039870 | 20231206 |
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_no | reprt_code | bsns_year | corp_code | sj_div | sj_nm | account_id | account_nm | account_detail | thstrm_nm | thstrm_amount | frmtrm_nm | frmtrm_amount | bfefrmtrm_nm | bfefrmtrm_amount | ord | currency | thstrm_add_amount | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20160527000376 | 11011 | 2015 | 00126414 | BS | 재무상태표 | ifrs_NoncurrentAssets | 비유동자산 | - | 제 62 기 | 64720596190 | 제 61 기 | 31496125317 | 제 60 기 | 34852182924 | 1 | KRW | NaN |
1 | 20160527000376 | 11011 | 2015 | 00126414 | BS | 재무상태표 | ifrs_PropertyPlantAndEquipment | 유형자산 | - | 제 62 기 | 42434108210 | 제 61 기 | 30515895159 | 제 60 기 | 32839273423 | 2 | KRW | NaN |
2 | 20160527000376 | 11011 | 2015 | 00126414 | BS | 재무상태표 | ifrs_IntangibleAssetsOtherThanGoodwill | 영업권 이외의 무형자산 | - | 제 62 기 | 6348561908 | 제 61 기 | 256406221 | 제 60 기 | 363747986 | 3 | KRW | NaN |
3 | 20160527000376 | 11011 | 2015 | 00126414 | BS | 재무상태표 | ifrs_InvestmentProperty | 투자부동산 | - | 제 62 기 | 6589966350 | 제 61 기 | 제 60 기 | 4 | KRW | NaN | ||
4 | 20160527000376 | 11011 | 2015 | 00126414 | BS | 재무상태표 | dart_GoodwillGross | 영업권 | - | 제 62 기 | 2712305285 | 제 61 기 | 제 60 기 | 5 | KRW | NaN | ||
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
158 | 20160527000376 | 11011 | 2015 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member] | 제 62 기 | 46236610988 | 제 61 기 | 23294889189 | 제 60 기 | 18741261619 | 15 | KRW | NaN |
159 | 20160527000376 | 11011 | 2015 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 연결재무제표 [member] | 제 62 기 | 46236614499 | 제 61 기 | 23294889189 | 제 60 기 | 18741261619 | 15 | KRW | NaN |
160 | 20160527000376 | 11011 | 2015 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|소수주주지분 | 제 62 기 | 3511 | 제 61 기 | 제 60 기 | 15 | KRW | NaN | ||
161 | 20160527000376 | 11011 | 2015 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|연결자본잉여금 | 제 62 기 | 41304925854 | 제 61 기 | 21429147754 | 제 60 기 | 20765899034 | 15 | KRW | NaN |
162 | 20160527000376 | 11011 | 2015 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|자본금 [... | 제 62 기 | 13791812000 | 제 61 기 | 11430685000 | 제 60 기 | 5819523500 | 15 | KRW | NaN |
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_no | reprt_code | bsns_year | corp_code | sj_div | sj_nm | account_id | account_nm | account_detail | thstrm_nm | thstrm_amount | frmtrm_nm | frmtrm_amount | bfefrmtrm_nm | bfefrmtrm_amount | ord | currency | thstrm_add_amount | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20190401003716 | 11011 | 2018 | 00126414 | BS | 재무상태표 | ifrs_CurrentAssets | 유동자산 | - | 제 65 기 | 79724616717 | 제 64 기 | 60978260818 | 제 63 기 | 47711452766 | 1 | KRW | NaN |
1 | 20190401003716 | 11011 | 2018 | 00126414 | BS | 재무상태표 | ifrs_CashAndCashEquivalents | 현금및현금성자산 | - | 제 65 기 | 3151357753 | 제 64 기 | 2931620456 | 제 63 기 | 5390180360 | 2 | KRW | NaN |
2 | 20190401003716 | 11011 | 2018 | 00126414 | BS | 재무상태표 | dart_ShortTermDepositsNotClassifiedAsCashEquiv... | 단기금융자산 | - | 제 65 기 | 1200000000 | 제 64 기 | 1200000000 | 제 63 기 | 1200000000 | 3 | KRW | NaN |
3 | 20190401003716 | 11011 | 2018 | 00126414 | BS | 재무상태표 | dart_ShortTermTradeReceivable | 매출채권 | - | 제 65 기 | 16699562155 | 제 64 기 | 15432793585 | 제 63 기 | 15540472145 | 4 | KRW | NaN |
4 | 20190401003716 | 11011 | 2018 | 00126414 | BS | 재무상태표 | dart_CurrentFinancialAssetHeldForTrading | 단기매매금융자산 | - | 제 65 기 | 3816549555 | 제 64 기 | 4230007815 | 제 63 기 | 4052931600 | 5 | KRW | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
172 | 20190401003716 | 11011 | 2018 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 지분 [member] | 제 65 기 | 131662977152 | 제 64 기 | 93868490973 | 제 63 기 | 82377763900 | 17 | KRW | NaN |
173 | 20190401003716 | 11011 | 2018 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|비지배지분 [member] | 제 65 기 | 제 64 기 | 제 63 기 | 1013 | 17 | KRW | NaN | ||
174 | 20190401003716 | 11011 | 2018 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 연결재무제표 [member] | 제 65 기 | 131662977152 | 제 64 기 | 93868490973 | 제 63 기 | 82377764913 | 17 | KRW | NaN |
175 | 20190401003716 | 11011 | 2018 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 지분 [member]|이익잉여금... | 제 65 기 | -3379583894 | 제 64 기 | -49654622640 | 제 63 기 | -43434867861 | 17 | KRW | NaN |
176 | 20190401003716 | 11011 | 2018 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 지분 [member]|기타포괄손... | 제 65 기 | 24610931430 | 제 64 기 | 24646132247 | 제 63 기 | 12765714568 | 17 | KRW | NaN |
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_no | reprt_code | bsns_year | corp_code | sj_div | sj_nm | account_id | account_nm | account_detail | thstrm_nm | ... | frmtrm_nm | frmtrm_amount | bfefrmtrm_nm | bfefrmtrm_amount | ord | currency | thstrm_add_amount | frmtrm_q_nm | frmtrm_q_amount | frmtrm_add_amount | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 20161129000446 | 11014 | 2016 | 00126414 | BS | 재무상태표 | ifrs_NoncurrentAssets | 비유동자산 | - | 제 63 기 3분기말 | ... | 제 62 기말 | 64720596190 | 제 61 기말 | 31496125317 | 1 | KRW | NaN | NaN | NaN | NaN |
1 | 20161129000446 | 11014 | 2016 | 00126414 | BS | 재무상태표 | ifrs_PropertyPlantAndEquipment | 유형자산 | - | 제 63 기 3분기말 | ... | 제 62 기말 | 42434108210 | 제 61 기말 | 30515895159 | 16 | KRW | NaN | NaN | NaN | NaN |
2 | 20161129000446 | 11014 | 2016 | 00126414 | BS | 재무상태표 | ifrs_IntangibleAssetsOtherThanGoodwill | 영업권 이외의 무형자산 | - | 제 63 기 3분기말 | ... | 제 62 기말 | 6348561908 | 제 61 기말 | 256406221 | 56 | KRW | NaN | NaN | NaN | NaN |
3 | 20161129000446 | 11014 | 2016 | 00126414 | BS | 재무상태표 | dart_GoodwillGross | 영업권 | - | 제 63 기 3분기말 | ... | 제 62 기말 | 2712305285 | 제 61 기말 | 75 | KRW | NaN | NaN | NaN | NaN | |
4 | 20161129000446 | 11014 | 2016 | 00126414 | BS | 재무상태표 | ifrs_OtherNoncurrentFinancialAssets | 기타비유동금융자산 | - | 제 63 기 3분기말 | ... | 제 62 기말 | 6635654437 | 제 61 기말 | 723823937 | 85 | KRW | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
174 | 20161129000446 | 11014 | 2016 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member] | 제 63 기 3분기 | ... | 제 62 기 | 46236610988 | 제 61 기 | 23294889189 | 24 | KRW | NaN | 제 62 기 3분기 | 36795377012 | NaN |
175 | 20161129000446 | 11014 | 2016 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 연결재무제표 [member] | 제 63 기 3분기 | ... | 제 62 기 | 46236614499 | 제 61 기 | 23294889189 | 24 | KRW | NaN | 제 62 기 3분기 | 36795381071 | NaN |
176 | 20161129000446 | 11014 | 2016 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|비지배지분 [member] | 제 63 기 3분기 | ... | 제 62 기 | 3511 | 제 61 기 | 24 | KRW | NaN | 제 62 기 3분기 | 4059 | NaN | |
177 | 20161129000446 | 11014 | 2016 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|미처분이익... | 제 63 기 3분기 | ... | 제 62 기 | -21702923038 | 제 61 기 | -23748181059 | 24 | KRW | NaN | 제 62 기 3분기 | -22832410583 | NaN |
178 | 20161129000446 | 11014 | 2016 | 00126414 | SCE | 자본변동표 | ifrs_Equity | 기말자본 | 자본 [member]|지배기업의 소유주에게 귀속되는 자본 [member]|자본금 [... | 제 63 기 3분기 | ... | 제 62 기 | 13791812000 | 제 61 기 | 11430685000 | 24 | KRW | NaN | 제 62 기 3분기 | 13223954500 | NaN |
179 rows × 21 columns
마무리
위에서 다룬 것과 유사한 방식으로 오픈 다트 API에서 제공하는 다양한 기업 정보를 불러와서 사용할 수 있다. 근 2주 동안 저평가 분석기를 만들기 위해 플러터를 공부하면서 ‘앱 프론트앤드 부분은 어떻게 하겠는데 원하는 기능을 구현하기 위해 필요한 데이터를 쉽게 구할 수 있을까’ 하는 걱정이 있었다. 오늘 오픈 다트 API를 알아보며 대부분 필요한 정보를 다트를 통해 구할 수 있을 것 같아 조금 마음이 놓인다. 이제는 구체화한 기능을 구현하기 위해 꼭 필요한 데이터를 구분하고, 데이터베이스를 어떻게 설계할지 공부하고, 파이어베이스나 수파베이스 활용법을 알아보며 백엔드 부분을 준비해야겠다.
참고자료
소스 | 링크 | 일자 |
---|---|---|
Youtube | [파이썬 퀀트] 18강 - DART API를 이용해 공시정보 및 재무제표 수집하기 | 230307 |