네이버 파이낸스 - 재무제표 크롤링
네이버 파이낸스 크롤링¶
파이썬으로 데이터 크롤링 할 때 requests 와 BeautifulSoup 를 많이 사용한다. 표(table)로 정리된 데이터의 경우 pandas의 read_html()을 활용하면 간편하게 크롤링 할 수 있는 경우가 많다. 이 글에서는 네이버 파이낸스의 페이지 구조를 파악하고, 재무제표 정보를 pandas DataFrame으로 읽어 처리하는 방법을 살펴본다. 종목별 예제로는 삼성전자(005930)을 사용한다.
페이지 구성¶
네이버 파이낸스에서 005930(삼성전자) 종목의 재무제표 정보 페이지의 구성을 단계별로 살펴보자. (이 구성은 크롬 혹은 파이어폭스의 개발자 도구로 살펴볼 수 있다)
1) 종목정보¶
아래 링크에서 종목정보를 볼 수 있다.
- http://finance.naver.com/item/coinfo.nhn?code=005930
- 페이지 아래 부분 "Financial Summary" 영역에 재무제표가 있는데 iframe으로 구성되어 있다.
2) Financial Summary 영역¶
iframe 의 src 링크는 아래와 같이 구성되어 있다.
- http://companyinfo.stock.naver.com/v1/company/c1010001.aspx?cmp_cd=005930&target=finsum_more
- 실제 내용은 JavaScript가 HTML 문서를 가져와 붙이도록 되어 있다
3) 재무제표¶
JavaScript가 요청하는 URL은 아래와 같다.
네이버 파이낸스에서 재무제표 데이터를 크롤링 하기 위해 알아야 할 정보는 아래 URL이 전부다.
http://companyinfo.stock.naver.com/v1/company/ajax/cF1001.aspx?cmp_cd=105560&fin_typ=0&freq_typ=Y
URL을 구성하는 파라미터의 의미는 다음과 같다.
인자 | 의미 | 값 |
---|---|---|
cmp_cd | 종목코드 | 005930 (종목코드) |
fin_typ | 재무제표 타입 | 0: 주재무제표, 1: GAAP개별, 2: GAAP연결, 3: IFRS별도, 4:IFRS연결 |
freq_typ | 기간 | Y:년, Q:분기 |
pandas.read_html 활용한 크롤링¶
pandas.read_html(url)은 HTML 페이지에 포함된 TABLE들을 DataFrame 의 리스트로 반환한다. 우리가 사용하는 재무제표 URL 페이지는 1개의 TABLE만을 가지고 있기 때문에 첫번째 [0] 요소가 바로 재무제표 정보를 담은 DataFrame이 된다.
pd.set_option('display.float_format', '{:,.1f}'.format)
DataFrame 을 화면에 표시할 때 보기 좋도록 하기 위한 것이다. 천 단위에 ',' 표시와 소수점 이하 1자리를 표시하도록 했다. 쓰지 않더라고 아무런 지장은 없다.
import pandas as pd
url_tmpl = 'http://companyinfo.stock.naver.com/v1/company/ajax/cF1001.aspx?cmp_cd=%s&fin_typ=%s&freq_typ=%s'
url = url_tmpl % ('005930', '4', 'Y') # 삼성전자, 4(IFRS 연결), Y:년 단위
dfs = pd.read_html(url)
df = dfs[0]
df = df.set_index('주요재무정보')
df.head()
df.head(10) # 10개 항목만 표시(실제 32개 항목)
단, 5줄의 코드로 삼성전자 재무제표를 크롤링 했다! (의외로 이렇게 크롤링 할 수 있는 정보들이 꽤 많다)
문제점과 해결방안¶
표시된 DataFrame을 자세히 살펴보면 아래와 같은 문제점들이 있다. (대부분 네이버 파이낸스 페이지의 HTML TABLE 표현의 문제다)
문제점¶
- '연간'이른 컬럼명이 추가되었고, 컬럼 이름이 한 컬럼씩 오른쪽으로 밀렸다.
- 마지막 컬럼의 값이 NaN 값 (컬럼 이름이 밀려서 발생)
- 날짜에 "(IFRS연결)"와 같이 불필요한 문자열 포함하고 있다.
- 시계열 데이터로 처리하려면, '주요재무정보'가 컬럼이 되고 날짜가 행(row)가 되는 것이 편리하다.
각 문제에 대한 해결방안¶
- 컬럼명 '연간' 삭제
- 컬럼 문자열에서 날짜(년, 월)만 추출 (정규식 사용)
- 마지막 컬럼 삭제
- 컬럼과 로우를 전환 (df.T) 한다 (transpose 우리말로 전치행렬 이라고 한다)
1. 컬럼명 '연간' 삭제¶
# 리스트로 전환
cols = list(df.columns)
cols
cols.remove('연간')
cols
2. 컬럼 문자열에서 날짜(년, 월) 추출¶
컬럼은 날짜 데이터이다. 그러나 불필요한 문자('\n', '\t', '(IFRS연결)' 등)이 포함되어 있다. 추후 Datetime으로 활용하기 위해 날짜에 해당하는 문자열만 추출해 보자.
'\n\t\t\t\t\t\t\t\t\t2011/12\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(IFRS연결)\n\t\t\t\t\t\t\t\t'
위 문자열에서 '2011/12' 문자열만 추출하기 위해 아래와 같은 코드를 사용할 수 도 있겠지만,
date_str = r['date'].replace('\t', '').replace('\n', '')
date_str = date_str.replace('(E)', '')
date_str = date_str.replace('/', '-')
date_str = date_str.replace(')', '')
정규식(regular expression)을 사용하는 것이 훨씬 깔끔하고 적응력이 좋아진다. 날짜 추출에 필요한 정규식을 만들어 보자.
import re
from datetime import datetime
s = '\n\t\t\t\t\t\t\t\t\t2011/12\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(IFRS연결)\n\t\t\t\t\t\t\t\t'
date_str = ''
r = re.search("\d{4}/\d{2}", s)
if r:
date_str = r.group()
date_str
날짜를 추출하는 내용을 함수로 만들어 컬럼 이름에 적용한다.
import re
from datetime import datetime
def get_date_str(s):
date_str = ''
r = re.search("\d{4}/\d{2}", s)
if r:
date_str = r.group()
date_str = date_str.replace('/', '-')
return date_str
이 함수를 사용하여 아래와 같이 모든 컬럼에서 날짜에 해당하는 문자열을 추출할 수 있다.
cols = [get_date_str(x) for x in cols]
cols
3. 마지막 컬럼 제외¶
df = df.ix[:, :-1]
df.head()
# 컬럼이름 변경
df.columns = cols
df.head()
4. 컬럼과 로우 전환¶
DataFrame.T (transpose, 전치행열)를 사용하면 행, 열이 전환된 DataFrame을 구할 수 있다. 시간을 인덱스로하여 시계열 데이터로 만드는 것이 데이터 분석과 시각화에 유리하다.
dft = df.T
# 첫번째 컬럼 이름을 data로 변경
dft.rename(columns={'주요재무정보':'date'}, inplace=True)
# 인덱스를 날짜형식(datetime)으로 변환
dft.index = pd.to_datetime(dft.index)
# 필요한 컬럼만 출력해 본다
dft[['매출액', '당기순이익', '부채총계', 'PER(배)' , 'PBR(배)', '현금배당수익률']]
# 컬럼 확인
dft.columns
# 인덱스 확인
dft.index
정리: 함수로 만들기¶
재사용하기 위해 앞선 내용을 정리하여 함수로 만든다.
import re
from datetime import datetime
import pandas as pd
import requests
from bs4 import BeautifulSoup
'''
get_date_str(s) - 문자열 s 에서 "YYYY/MM" 문자열 추출
'''
def get_date_str(s):
date_str = ''
r = re.search("\d{4}/\d{2}", s)
if r:
date_str = r.group()
date_str = date_str.replace('/', '-')
return date_str
'''
* code: 종목코드
* fin_type = '0': 재무제표 종류 (0: 주재무제표, 1: GAAP개별, 2: GAAP연결, 3: IFRS별도, 4:IFRS연결)
* freq_type = 'Y': 기간 (Y:년, Q:분기)
'''
def get_finstate_naver(code, fin_type='0', freq_type='Y'):
url_tmpl = 'http://companyinfo.stock.naver.com/v1/company/ajax/cF1001.aspx?' \
'cmp_cd=%s&fin_typ=%s&freq_typ=%s'
url = url_tmpl % (code, fin_type, freq_type)
#print(url)
dfs = pd.read_html(url, encoding="utf-8")
df = dfs[0]
if df.ix[0,0].find('해당 데이터가 존재하지 않습니다') >= 0:
return None
df.rename(columns={'주요재무정보':'date'}, inplace=True)
df.set_index('date', inplace=True)
cols = list(df.columns)
if '연간' in cols: cols.remove('연간')
if '분기' in cols: cols.remove('분기')
cols = [get_date_str(x) for x in cols]
df = df.ix[:, :-1]
df.columns = cols
dft = df.T
dft.index = pd.to_datetime(dft.index)
# remove if index is NaT
dft = dft[pd.notnull(dft.index)]
return dft
# 삼성전자 (년간, IFRS연결)
df = get_finstate_naver('005930')
df[['매출액','영업이익', '당기순이익', '영업활동현금흐름', '순이익률']]
활용 예¶
#df = get_finstate_naver('035720') # 셀트리온 068270
df = get_finstate_naver('035720') # 카카오 035720
#df = get_finstate_naver('035720') # CJ E&M 130960
#df = get_finstate_naver('035720') # 메디톡스 086900
df[['ROE(%)', 'ROA(%)', '부채비율', '자본유보율', 'EPS(원)', 'PER(배)', 'BPS(원)', 'PBR(배)']]
%matplotlib inline
import matplotlib.pyplot as plt
df[['ROE(%)', 'ROA(%)']].plot()
csv로 저장¶
DataFrmae (재무제표 데이터)를 .csv로 저장하고자 한다면,
df.to_csv('035720.csv')
# 확인
! head 035720.csv
요약¶
- 파이썬 데이터 크롤링, requests를 주로 사용하지만 TABLE에 pandas.read_html()를 사용하여 간결하게 처리할 수 있다.
- 정규식을 사용하여 문자열을 추출하는 것이 유연성과 적응성이 뛰어나다. (replace 같은 문자열 처리함수를 사용하는 것 보다.
- pandas.DataFrame의 컬럼, 인덱스 조작을 통해 DataFrame을 적절하게 가공할 수 있다.
- DataFrame으로 만들어지면 파일(csv, excel, RDB)등으로 변환하기 좋고, 시각화 하기도 용이하다.
댓글
Comments powered by Disqus