[Quant] Dart API를 이용해서 상장기업의 재무제표 크롤링


import requests
from bs4 import BeautifulSoup as bs
from urllib.request import urlopen
import re
import pandas as pd
from io import BytesIO
from zipfile import ZipFile

재무제표 및 가치지표

1. API 키 받기

  • Open Dart를 사용해서 재무제표와 가치지표를 가져오고자 함
  • API Key가 있어야 하므로 해당 사이트에서 가입절차를 완료한 후 API 키를 받아야함
  • https://opendart.fss.or.kr/mng/apiUsageStatusView.do

img

dart_api = "Your API Key"

2. 고유번호 받기

  • 각 기업에 해당하는 데이터 받기
  • https://opendart.fss.or.kr/guide/detail.do?apiGrpCd=DS001&apiId=2019018 사이트에 가면 개발정보가 있음
  • Zipfile 안에 있는 “CORPCODE.xml”이라는 xml 파일을 읽어내는 프로세스
get_url = "https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=" + dart_api # Data 요청 URL

resp = urlopen(get_url) # url
zipfile = ZipFile(BytesIO(resp.read())) # Zipfile 읽기
# code_data = zipfile.open("CORPCODE.xml").read().decode("utf-8","replace") # Zipfile 열면서 utf-8로 디코딩
code_data = zipfile.open("CORPCODE.xml").read()
soup = bs(code_data,"xml") # beautifulsoup를 통해 xml 읽기
  • corp_code는 dart에서 사용하게 될 회사코드이며
  • corp_name은 회사명임
# soup.find_all(["corp_code","corp_name"])
corp_codes = []
for i in soup.find_all("corp_code"):
    corp_codes.append(i.get_text())
corp_codes[:5]
['00434003', '00434456', '00430964', '00432403', '00388953']
corp_names = []
for i in soup.find_all("corp_name"):
    corp_names.append(i.get_text())
corp_names[:5]
['다코', '일산약품', '굿앤엘에스', '한라판지', '크레디피아제이십오차유동화전문회사']
stock_codes = []
for i in soup.find_all("stock_code"):
    stock_codes.append(i.get_text())
stock_codes[:5]
[' ', ' ', ' ', ' ', ' ']
  • 종목코드(티커)가 없는 기업도 많음
  • 우리가 관심있는 기업은 티커가 있는 기업이므로 아래 데이터프레임에서 필터링을 해줌
corp_df = pd.DataFrame({"code" : corp_codes, "name": corp_names, "ticker": stock_codes})
corp_df = corp_df[corp_df["ticker"] != ' '].reset_index().drop("index", axis=1) # ticker가 없는 종목은 drop
corp_df
codenameticker
000260985한빛네트036720
100264529엔플렉스040130
200358545동서정보기술055000
300231567애드모바일032600
400247939씨모스037600
............
343500442455코스나인082660
343600567222우림피티에스101170
343700361594DMS068790
343800141626오리엔트바이오002630
343900985686큐브엔터182360

3440 rows × 3 columns

  • 그래도 있지도 않은 기업들이 껴있음
  • 해당 기업에서 지금 존재하는 기업들만 솎아내야 함
# KRX에 상장되어있는 기업항목 가져오기
market_list = ["STK", "KSQ"]
ticker = []
name = []

for mk in market_list:
    otp_url = "http://data.krx.co.kr/comm/fileDn/GenerateOTP/generate.cmd"
    data = {
        "locale": "ko_KR",
        "mktId": mk,
        "trdDd": "20220607", # 가장 최근 영업일 기준으로 조회 필요
        "money": "1",
        "csvxls_isNo": "false",
        "name": "fileDown",
        "url": "dbms/MDC/STAT/standard/MDCSTAT03901"
    }

    response = requests.post(otp_url, data=data)
    otp = bs(response.text, "html.parser")# OTP 생성

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

    data = bs(down_res.content.decode("euc-kr","replace"), "html.parser") # 인코딩 깨지지 않게
    str_data = data.text.split("\n")

    temp_ticker = [x.replace('"','') for x in [x.split('","')[0] for x in str_data[1:]]]
    temp_name = [x.replace('"','') for x in [x.split('","')[1] for x in str_data[1:]]]
    ticker.extend(temp_ticker)
    name.extend(temp_name)

all_data = list(zip(ticker, name))
ticker_df = pd.DataFrame(all_data, columns = ['ticker','company'])
ticker_df.head(5)
tickercompany
0095570AJ네트웍스
1006840AK홀딩스
2027410BGF
3282330BGF리테일
4138930BNK금융지주
intersected_df = pd.merge(corp_df, ticker_df, how='inner') # Inner join
intersected_df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2374 entries, 0 to 2373
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   code     2374 non-null   object
 1   name     2374 non-null   object
 2   ticker   2374 non-null   object
 3   company  2374 non-null   object
dtypes: object(4)
memory usage: 92.7+ KB

3. 상장기업 재무정보 받기

  • xml 데이터 형식으로 받는 것으로 설정
  • 삼성전자 데이터를 테스트로 해서 연결재무제표 전체를 가져와보기
# corp_code = "00126380"
corp_code = "00985686"
year="2020"
get_url=f"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.xml?crtfc_key={dart_api}&corp_code={corp_code}&bsns_year={year}&reprt_code=11011&fs_div=CFS"

resp = urlopen(get_url) # url 오픈
df = pd.read_xml(resp) # xml 읽기
df2 = df[["sj_div","sj_nm","account_nm", "thstrm_amount"]] # 재무재표구분, 재무제표명, 계정명, 당기금액
df2["year"] = year

4. 년도 별, 회사 별 정보 크롤링

  • all_df 데이터프레임에 데이터가 쌓임
  • 일부 재무제표자료가 없는 경우가 있으몰, try except문을 통해 오류를 잡고 다음 code로 넘어가도록 수행
import time

codes = intersected_df["code"].values.tolist()

all_df = pd.DataFrame()
for code in codes:
    try:
        for year in range(2015, 2022): # 2015 ~ 2021년
            get_url=f"https://opendart.fss.or.kr/api/fnlttSinglAcntAll.xml?crtfc_key={dart_api}&corp_code={code}&bsns_year={year}&reprt_code=11011&fs_div=CFS"

            resp = urlopen(get_url)
    #         soup = bs(resp, "xml")
    #         if soup.text == "013조회된 데이타가 없습니다.":
    #             continue
    #         else:
            df = pd.read_xml(resp)
            df = df[["sj_div","sj_nm","account_nm", "thstrm_amount"]] # 재무재표구분, 재무제표명, 계정명, 당기금액
            df["year"] = year # 해당년도
            df["ticker"] = intersected_df[intersected_df["code"] == '00956028']["ticker"][0] # 티커
            df["company"] = intersected_df[intersected_df["code"] == '00956028']["name"][0] # 회사명
            
            df = df[["ticker","company","year","sj_div","sj_nm","account_nm","thstrm_amount"]] # 순서 재조합
            all_df = pd.concat([all_df, df])
        
    except Exception as e:
        print(f"Error : at {code}, {year}", str(e))
        continue
    
    time.sleep(1)

all_df.reset_index().drop("index", axis=1, inplace=True) # index Resetting



© 2021.04. by Jessie

Powered by jessie