본문 바로가기
지덕이의 데이터 분석/데이터 분석 스킬

[파이썬Python-API] 금융감독원 오픈 API로 적금상품 스크래핑

by 지표덕후 2022. 3. 17.
반응형

금융상품통합비교공시 사이트에서는 시중 금융상품(정기예금, 적금, 연금저축, 주택담보대출, 전세자금대출, 개인신용대출)에 대한 풍성한 정보를 제공하고 있습니다. 또한 금융감독원에서는 이런 정보를 API로 말아서 제공하고 있는데요. 금번 포스팅은 금감원에서 제공하고 있는 금융상품별 API를 활용해 데이터 스크래핑하는 방법을 정리해보겠습니다.

코딩하시는 분 모두가 그렇겠지만 저 역시 최소한의 코드 변경만으로 다양한 스키마의 데이터를 긁어올 수 있는 robust한 코드를 추구합니다. 이 포스트에 정리한 코드도 조금만 변경하면 공공데이터포털의 오픈 API와 전자공시시스템(DART)의 API까지 손쉽게 수집할 수 있습니다. 제가 실제로 그러고 있어요.


1단계: 데이터 스키마 확인


▼ 테스트로 API를 호출해 데이터의 구조(스키마)를 파악합니다.

# 코드 실행에 필요한 모듈 import
import pprint
import requests
from bs4 import BeautifulSoup
import pandas as pd
from lxml import html
from urllib.request import Request, urlopen
from urllib.parse import urlencode, quote_plus, unquote

# API 요청결과를 확인하기 위해 테스트로 호출하기 위한 코드

url = "http://finlife.fss.or.kr/finlifeapi/savingProductsSearch.xml?auth={}&topFinGrpNo=020000&pageNo=1".format(KEY)
KEY =  #발급 받은 인증키

response = requests.get(url).content.decode('euc-kr')
pprint.pprint(response)

# Output
('<?xml version="1.0" encoding="euc-kr"?> \n'
 '<result>  \n'
 '<err_cd>000</err_cd>  \n'
 '<err_msg>정상</err_msg>  \n'
 '<total_count>74</total_count>  \n'
 '<max_page_no>1</max_page_no>  \n'
 '<now_page_no>1</now_page_no>  \n'
 '\t<products>  \n'
 '\t\t<product>  \n'
 '\t\t\t<baseinfo>  \n'
 '<dcls_month>202202</dcls_month> \n'
 '<fin_co_no>0010001</fin_co_no> \n'
 '<kor_co_nm>우리은행</kor_co_nm> \n'
 '<fin_prdt_cd>WR0001A</fin_prdt_cd> \n'
 '<fin_prdt_nm>스무살 우리 적금(정액·자유)</fin_prdt_nm> \n'
 '<join_way>영업점,인터넷,스마트폰</join_way> \n'
 '<mtrt_int>만기 후\n'
 '- 1개월이내 : 만기시점약정이율×50%\n'
 '- 1개월초과 6개월이내: 만기시점약정이율×30%\n'
 '- 6개월초과 : 만기시점약정이율×20%\n'
 '\n'
 '※ 만기시점 약정이율 : 일반정기적금 금리</mtrt_int> \n'
 '<spcl_cnd>- 신규 시 아래항목 충족 시 최고 연0.5%p\n'
 '1. 우리카드 월 10만원 이상 사용 - 연 0.3%p\n'
 '2. 인터넷,스마트뱅킹 신규 -  연 0.2%p\n'
 '\n'
 '- 자동이체 우대 최대 0.6%P(정액적립)\n'
 '1. 가입기간 1/2이상 자동이체 : 0.3%p\n'
 '2. 가입기간 전체 자동이체 : 0.6%p</spcl_cnd> \n'
 '<join_deny>3</join_deny> \n'
 '<join_member>만 18세 이상 ~ 만30세 이하 실명의 개인(도전형·절약형 각 1인1계좌)</join_member> \n'
 '<etc_note>영업점/스마트·인터넷뱅킹 가능</etc_note> \n'
 '<max_limit>200000</max_limit> \n'
 '<dcls_strt_day>20220221</dcls_strt_day> \n'
 '<dcls_end_day></dcls_end_day> \n'
 '<fin_co_subm_day>202202211152</fin_co_subm_day> \n'
 '\t\t\t</baseinfo>  \n'
 '\t\t\t<options>  \n'
 '\t\t\t\t<option>  \n'
 '<intr_rate_type>S</intr_rate_type> \n'
 '<intr_rate_type_nm>단리</intr_rate_type_nm> \n'
 '<rsrv_type>F</rsrv_type> \n'
 '<rsrv_type_nm>자유적립식</rsrv_type_nm> \n'
 '<save_trm>12</save_trm> \n'
 '<intr_rate>2.1</intr_rate> \n'
 '<intr_rate2>2.6</intr_rate2> \n'
 '\t\t\t\t</option>  \n'
 '\t\t\t\t<option>  \n'
 '<intr_rate_type>S</intr_rate_type> \n'
 '<intr_rate_type_nm>단리</intr_rate_type_nm> \n'
 '<rsrv_type>F</rsrv_type> \n'
 '<rsrv_type_nm>자유적립식</rsrv_type_nm> \n'
 '<save_trm>24</save_trm> \n'
 '<intr_rate>2.2</intr_rate> \n'
 '<intr_rate2>2.7</intr_rate2> \n'
 '\t\t\t\t</option>  \n'
 '\t\t\t\t<option>  \n'
 '<intr_rate_type>S</intr_rate_type> \n'
 '<intr_rate_type_nm>단리</intr_rate_type_nm> \n'
 '<rsrv_type>F</rsrv_type> \n'
 '<rsrv_type_nm>자유적립식</rsrv_type_nm> \n'
 '<save_trm>36</save_trm> \n'
 '<intr_rate>2.3</intr_rate> \n'
 '<intr_rate2>2.8</intr_rate2> \n'
 '\t\t\t\t</option>  \n'
 '\t\t\t\t<option>  \n'
 '<intr_rate_type>S</intr_rate_type> \n'
 '<intr_rate_type_nm>단리</intr_rate_type_nm> \n'
 '<rsrv_type>S</rsrv_type> \n'
 '<rsrv_type_nm>정액적립식</rsrv_type_nm> \n'
 '<save_trm>12</save_trm> \n'
 '<intr_rate>2.1</intr_rate> \n'
 '<intr_rate2>3.2</intr_rate2> \n'
 '\t\t\t\t</option>  \n'
 '\t\t\t\t<option>  \n'
 '<intr_rate_type>S</intr_rate_type> \n'
 '<intr_rate_type_nm>단리</intr_rate_type_nm> \n'
 '<rsrv_type>S</rsrv_type> \n'
 '<rsrv_type_nm>정액적립식</rsrv_type_nm> \n'
 '<save_trm>24</save_trm> \n'
 '<intr_rate>2.2</intr_rate> \n'
 '<intr_rate2>3.3</intr_rate2> \n'
 '\t\t\t\t</option>  \n'
 '\t\t\t\t<option>  \n'
 '<intr_rate_type>S</intr_rate_type> \n'
 '<intr_rate_type_nm>단리</intr_rate_type_nm> \n'
 '<rsrv_type>S</rsrv_type> \n'
 '<rsrv_type_nm>정액적립식</rsrv_type_nm> \n'
 '<save_trm>36</save_trm> \n'
 '<intr_rate>2.3</intr_rate> \n'
 '<intr_rate2>3.4</intr_rate2> \n'
 '\t\t\t\t</option>  \n'
 '\t\t\t</options>  \n'
 '\t\t</product>  \n'
 '\t\t<product>  \n'
 '\t\t\t<baseinfo>  \n'
 '<dcls_month>202202</dcls_month> \n'
 '<fin_co_no>0010001</fin_co_no> \n'

▲ 요청결과를 일별한 결과, <products> 태그 하위에 복수의 <product> 태그가 내포(nested)되어 있고, 이 <product> 태그는 개별 적금상품들의 정보를 지니고 있네요. 이 개별 적금상품의 정보들 중 나에게 필요한 정보의 태그만 선별하면 됩니다.

 

 ▼ 개별상품 정보가 내포되어 있는 <product> 태그를 모두 추출해 list로 만들어봅니다. 이 리스트 타입의 데이터를 가지고 지지고 볶고 하는 것이 API 활용 스크래핑의 기본입니다.

# 개별 상품정보를 뜻하는 <proudct> 태그를 달고 있는 데이터 모두 추출: findAll 함수
xml_obj = BeautifulSoup(response, 'html.parser') # html이나 xml 파싱할 때 html.parser(lxml-xml 사용해도 무방)
rows = xml_obj.findAll("product") # <product>에 nested된 데이터 모두 추출
rows # 결과물 확인

 

 

2단계: API를 호출해 데이터를 리스트로 변환하는 함수 정의

이 단계부터의 코드를 합치면 최종 결과물을 생성할 수 있습니다. 


 ▼ 개별 상품 정보가 내포되어 있는 태그를 모두 추출해 list로 만들어봅니다. 

def get_product(KEY, FINGROUP, PAGE):
    # 파이썬에서 인터넷을 연결하기 위해 urllib 패키지 사용. urlopen함수는 지정한 url과 소켓 통신을 할 수 있도록 자동 연결해줌
    import requests
    from bs4 import BeautifulSoup
    from lxml import html
    from urllib.request import Request, urlopen
    from urllib.parse import urlencode, quote_plus, unquote

    url = "http://finlife.fss.or.kr/finlifeapi/savingProductsSearch.xml?auth={}&topFinGrpNo={}&pageNo={}".format(KEY, FINGROUP, PAGE)
    response = requests.get(url).content.decode('euc-kr')
    
    # html을 파싱할 때는 html.parser를,
    # xml을 파싱할 때는 lxml-xml을 사용
    xml_obj = BeautifulSoup(response, 'html.parser')
    rows = xml_obj.findAll("product")
    return rows

 

 

3단계: 금융기관별 적금 정보 수집


 ▼ 2단계에서 정의한 함수를 활용해 금융기관별 적금상품의 상품명, 가입방법, 이자율, 한도, 우대금리 등의 정보를 수집합니다. 이 단계에서는 for-loop 구문을 잘 이해하셔야 합니다. 또 try-except 구문도 사용했는데요, 왜냐하면 상품 스펙 중에 값이 비어있는 경우에는 error가 발생하는데 이런 경우에 에러를 무시하고 공백("")을 넣도록 설계했습니다.  

# API 호출에 필요한 파라미터(필수)
# 금융기관별 코드 list: 데이터 명세 참고
fin_grp_list = [
    '020000' # 은행
    , '030200' # 여신전문
    , '030300' # 저축은행
    , '050000' # 보험회사
    , '060000' # 금융투자
]

# API 호출에 필요한 파라미터(필수)
KEY =  # 발급 받은 인증키
PAGE = 1 # 조회하고자 하는 페이지 번호(1page로 충분한 듯)

# 수집할 상품 스펙의 태그명 list: 데이터 명세 참고
item_list = [
'dcls_month' # 공시제출월
, 'kor_co_nm' # 금융회사명
, 'fin_prdt_nm' # 금융상품명
, 'join_way' # 가입방법
, 'mtrt_int' # 만기 후 이자율
, 'spcl_cnd' # 우대조건
, 'join_deny' # 가입제한 1:제한X, 2:서민전용, 3:일부제한
, 'join_member' # 가입대상
, 'max_limit' # 최고한도
, 'intr_rate_type_nm' # 저축 금리 유형명
, 'rsrv_type_nm' # 적립유형명
, 'save_tm' # 저축 기간
, 'intr_rate' # 저축금
, 'intr_rate2' # 최고 우대금리
]

# 스크래핑한 데이터를 담을 빈 list 정의
bank_savings_list = list()

# 금융기관별로 상품 정보를 호출한 후 의도한 스펙을 스크래핑하는 for-loop 구문
for grp in fin_grp_list:
    products = get_product(KEY, grp, PAGE)
    
    for p in range(0, len(products)):
        savings_product_list = list()
        for i in item_list:
            try:
                savings_info = products[p].find(i).text # 특정 스펙을 수집하는 중에 어떤 종류든 error 발생시
            except:
                savings_info = "" # 해당 값은 ""로 대체
                
            savings_product_list.append(savings_info)
            
        bank_savings_list.append(savings_product_list)
bank_savings_list

 

▼ 위의 코드를 수행하고 나면 데이터가 리스트 형태로 저장이 되어 있을 텐데, 이걸 데이터프레임 형태로 변환하는 코드 스니펫입니다. 

# import pandas
import pandas as pd
from pandas import DataFrame
from datetime import datetime

# 위 과정의 결과물은 list이기 때문에 이것을 dataframe으로 변형
# DF로 변형하면서 컬럼명을 국문으로 지정
bank_savings_df = DataFrame(bank_savings_list, columns=[
'공시제출월'
, '금융회사명' 
, '금융상품명' 
, '가입방법' 
, '만기후이자율' 
, '우대조건'
, '가입제한' # 1:제한X, 2:서민전용, 3:일부제한
, '가입대상'  
, '최고한도'  
, '저축금리유형명'  
, '적립유형명' 
, '저축기간' 
, '저축금리'  
, '최고우대금리' 
])

bank_savings_df.head()

 

▼ 마지막은 데이터프레임을 엑셀로 저장하는 코드입니다. 

# 엑셀 파일로 저장
date = datetime.today().strftime('%Y-%m-%d')
bank_savings_df.to_excel(date+'_savings interest_3.xlsx', index=False)

 

고수 형님들 더 효율적이고 간결한 코드 수정 방안이 있다면 마구마구 댓글 주세요!

반응형

댓글0