반응형

전자공시(Open DART) 재무제표 크롤링 : Python

 

앞서 Julia 언어로 전자공시의 기업 고유번호, 단일회사 및 다중회사 주요계정, 단일회사 전체 재무제표를 크롤링하는 코드를 작성했었습니다. 본래 해당 코드로 라즈베리파이에서 상시 돌아가는 프로그램을 만들 생각이었는데, Julia 언어가 라즈베리파이에서 작동을 하긴 하는데, 컴파일되는 시간이 너무 오래 걸립니다. 많은 연산을 하는데 있어서 Julia가 Python보다 빠르지만, 데이터 크롤링 하는데 있어서 속도는 의미가 없어보입니다. Python으로 1분 걸리는 걸 Julia가 10초만에 끝낸다 한들, Julia 코드 실행시 컴파일이 1분이상 걸리면 전혀 의미 없는 코드가 되어버리네요. 그래서 앞서 전자공시 Open DART를 Julia로 크롤링 하던 코드들을 Python으로 다 바꾸었습니다.

 

이 글에서 보이는 Python 코드는 다음 사항들을 크롤링 하는 코드입니다.

 

  • 기업 고유번호
  • 단일회사 주요계정
  • 다중회사 주요계정
  • 단일회사 전체 재무제표

 

앞서 Julia 코드와 매우 유사하나, pandas의 데이터프레임으로 반환하는 코드입니다.

 

크롤링을 위해 사용된 패키지들은 다음과 같습니다.

import requests
import pandas as pd
import io
import zipfile
import xml.etree.ElementTree as et
import json

 

기업 고유번호 크롤링

  def get_corpcode(crtfc_key):
    """
    OpenDART 기업 고유번호 받아오기
    return 값: 주식코드를 가진 업체의 DataFrame
    """
    params = {'crtfc_key':crtfc_key}
    items = ["corp_code","corp_name","stock_code","modify_date"]
    item_names = ["고유번호","회사명","종목코드","수정일"]
    url = "https://opendart.fss.or.kr/api/corpCode.xml"
    res = requests.get(url,params=params)
    zfile = zipfile.ZipFile(io.BytesIO(res.content))
    fin = zfile.open(zfile.namelist()[0])
    root = et.fromstring(fin.read().decode('utf-8'))
    data = []
    for child in root:
      if len(child.find('stock_code').text.strip()) > 1: # 종목코드가 있는 경우
        data.append([])
        for item in items:
          data[-1].append(child.find(item).text)
    df = pd.DataFrame(data, columns=item_names)
    return df

여기서 crtfc_key는 전자공시 OpenDART의 인증키(string) 입니다. 그냥 웹크롤링이라기에는 zip파일을 받아서 압축파일 내에 있는 문서를 열고 utf-8 디코딩을 해야하는 것이 좀 독특할 뿐 입니다.

 

다음에는 단일회사 및 다중회사 주요계정, 단일회사 전체 재무제표를 가져올 때 공통으로 쓰이는 함수입니다.

  def convertFnltt(url, items, item_names, params):
    res = requests.get(url, params)
    json_dict = json.loads(res.text)
    data = []
    if json_dict['status'] == "000":
      for line in json_dict['list']:
        data.append([])
        for itm in items:
          if itm in line.keys():
            data[-1].append(line[itm])
          else: data[-1].append('')
    df = pd.DataFrame(data,columns=item_names)
    return df

여기서 url은 json형태로 요청하는 주소, items는 반환되는 데이터들의 key를 가진 리스트, item_names는 데이터프레임을 만들때 컬럼명 리스트, params는 url 요청시 필수값으로 들어가는 인자들을 가진 딕셔너리입니다. 아래 기술할 3개의 함수는 위의 함수를 이용하여 pandas 데이터프레임으로 반환합니다.

 

 

단일회사 주요계정

  def get_fnlttSinglAcnt(crtfc_key, corp_code, bsns_year, reprt_code):
    items = ["rcept_no", "bsns_year", "stock_code", "reprt_code", "account_nm",
          "fs_div", "fs_nm","sj_div", "sj_nm", "thstrm_nm", "thstrm_dt",
          "thstrm_amount","thstrm_add_amount", "frmtrm_nm", "frmtrm_dt",
          "frmtrm_amount","frmtrm_add_amount", "bfefrmtrm_nm", "bfefrmtrm_dt",
          "bfefrmtrm_amount","ord"]
    item_names = ["접수번호", "사업연도", "종목코드", "보고서코드", "계정명",
                "개별연결구분","개별연결명", "재무제표구분", "재무제표명", "당기명",
                "당기일자","당기금액", "당기누적금액", "전기명", "전기일자",
                "전기금액","전기누적금액", "전전기명", "전전기일자", "전전기금액",
                "계정과목정렬순서"]
    params = {'crtfc_key':crtfc_key, 'corp_code':corp_code, 
              'bsns_year':bsns_year,'reprt_code':reprt_code}
    url = "https://opendart.fss.or.kr/api/fnlttSinglAcnt.json"
    return convertFnltt(url,items,item_names,params)

crtfc_key, corp_code, bsns_year, reprt_code 는 전부 string 문자열이며, 전자공시에 요청할때 필수값입니다.

 

 

다중회사 주요계정

  def get_fnlttMultiAcnt(crtfc_key, corp_code, bsns_year, reprt_code):
    items = ["rcept_no", "bsns_year", "stock_code", "reprt_code", "account_nm",
          "fs_div", "fs_nm","sj_div", "sj_nm", "thstrm_nm", "thstrm_dt",
          "thstrm_amount","thstrm_add_amount", "frmtrm_nm", "frmtrm_dt",
          "frmtrm_amount","frmtrm_add_amount", "bfefrmtrm_nm", "bfefrmtrm_dt",
          "bfefrmtrm_amount","ord"]
    item_names = ["접수번호", "사업연도", "종목코드", "보고서코드", "계정명",
                "개별연결구분","개별연결명", "재무제표구분", "재무제표명", "당기명",
                "당기일자","당기금액", "당기누적금액", "전기명", "전기일자",
                "전기금액","전기누적금액", "전전기명", "전전기일자", "전전기금액",
                "계정과목정렬순서"]
    corps_str = ",".join(corp_code)
    #문자열 Array를 "문자열1,문자열2,..,문자열N"으로 하나의 문자열로 변환
    params = {'crtfc_key':crtfc_key, 'corp_code':corps_str, 
              'bsns_year':bsns_year,'reprt_code':reprt_code}
    url = "https://opendart.fss.or.kr/api/fnlttMultiAcnt.json"
    return convertFnltt(url,items,item_names,params)

다중회사 주요계정은 단일회사 주요계정과 요청 url 외에 같아 보이지만, corp_code를 ["고유번호1","고유번호2",..."고유번호n"] 형식의 리스트로 넘겨줍니다.

 

 

단일회사 전체 재무제표

  def get_fnlttSinglAcntAll(crtfc_key, corp_code, bsns_year, reprt_code, fs_div = "CFS"):
    items = ["rcept_no","reprt_code","bsns_year","corp_code","sj_div","sj_nm",
            "account_id","account_nm","account_detail","thstrm_nm",
            "thstrm_amount","thstrm_add_amount","frmtrm_nm","frmtrm_amount",
            "frmtrm_q_nm","frmtrm_q_amount","frmtrm_add_amount","bfefrmtrm_nm",
            "bfefrmtrm_amount","ord"]
    item_names = ["접수번호","보고서코드","사업연도","고유번호","재무제표구분",
                  "재무제표명","계정ID","계정명","계정상세","당기명","당기금액",
                  "당기누적금액","전기명","전기금액","전기명(분/반기)",
                  "전기금액(분/반기)","전기누적금액","전전기명","전전기금액",
                  "계정과목정렬순서"]
    params = {'crtfc_key':crtfc_key, 'corp_code':corp_code,
              'bsns_year':bsns_year, 'reprt_code':reprt_code,
              'fs_div':fs_div}
    url = "https://opendart.fss.or.kr/api/fnlttSinglAcntAll.json?"
    return convertFnltt(url,items,item_names,params) 

이것 역시 앞선 코드들과 기본적인 형태를 유사합니다. 단지 items, item_names, params, url 내용만 바뀌는 겁니다.

 

위의 코드들은 전자공시 개발가이드에 나와있는 반환값에 대한 키들을 전부 컬럼명으로 쓰는 데이터프레임을 반환하고 있습니다. 개발가이드에는 20개의 반환키가 있지만, 실제 요청값에 따라 반환되는 데이터의 키는 14개밖에 안된다 하더라도 20개의 컬럼을 가지는 데이터프레임으로 만듭니다. 1분기, 사업보고서, 연결 유무 등에 관계없이 동일한 테이블에 데이터를 넣기 위함입니다.

 

 

반응형
  1. 2020.08.14 07:28

    비밀댓글입니다

  2. 2020.08.17 11:13

    비밀댓글입니다

    • Bésixdouze Bésixdouze 2020.08.17 11:15 신고

      기업고유번호 전체를 넣으면 에러났던 기억이 저도 있네요. 저는 안전하게 4,5백개씩 고유번호를 나눠서 호출했었습니다.

  3. sg 2020.09.10 20:19

    안녕하세요. 위의 함수를 구동시키는데 get_status함수가 정의되지 않았다고 뜹니다. 다른 글을 찾아봐도 안나오는데 누락된 내용인가요?

    • Bésixdouze Bésixdouze 2020.09.10 20:27 신고

      죄송합니다. get_status 함수는 제 프로그램에서만 있었던 함수였는데, 제대로 수정이 되질 않았었네요. 해당 부분을 수정하였습니다.
      json_dict['status'] 값이 '000'이 나와야 openDART로부터 정상적으로 데이터를 리턴받은 겁니다.

    • sg 2020.09.10 21:43

      감사합니다!! 너무나 간단하고 유용한 코드 감사드립니다.

+ Recent posts