지난 포스팅에서 naver finance 에서 제공하는 "Financial Summary" 정보를 크롤링하는 법을 다뤘었다. 그럼 이번에는 크롤링한 데이터에서 우리가 필요한 정보를 추출하고, 만일 원하는 데이터가 없다면 다른 곳에서 추가로 크롤링하는 방법을 다뤄보자. 우선 우리가 필요한 정보를 다시 상기시켜보자. 아래 표에서 추가로 정리해보면 11개로 정리할 수 있다.
PER(배), PBR(배), 시가총액, 매출액, 유동자산, 부채총계, 자본총계, ROA(%), 당기 순이익, 매출총이익, 자산총액
Measures
naver finance 에 matching
naver finance tab
적용 기법
PER
PER(배)
기업현황
NCAV, 3P Combo
PBR
PBR(배)
기업현황
Graham, 3P Combo, PBR + GP/A
PSR
시가총액 / 매출액
기업현황
3P Combo
Market cap
시가총액(억)
기업현황
NCAV
Current assets
유동자산
재무분석 (재무상태표)
NCAV
Total liabilities
부채총계(억)
기업현황
NCAV
Total debt/equity
부채총계 / 자본총계
기업현황
Graham
ROA
ROA(%)
기업현황
Graham
Net income
당기 순이익
기업현황
NCAV
GP/A
매출총이익 / 자산총액
재무분석 (포괄손익계산서)
PBR + GP/A
그렇다면 이제 지난 번에 크롤링한 결과를 먼저 다시 살펴보자. 여기서 우리가 필요한 정보 중 무엇을 얻을 수 있을까?
크롤링 결과 (Financial Summary)
여기서는 11 개 중 8 개의 정보를 찾을 수 있다. 여기서 파생시킬 수 있는 변수는 "Total debt/equity" 가 있다.
PER(배), PBR(배), 매출액, ROA(%), 부채총계, 자본총계, 당기순이익, 자산총계
> 파생변수:
Total debt/equity = 부채총계 / 자본총계
그 외에 우리가 추가로 필요한 정보는 시가총액, 유동자산, 매출총이익이 남았고, 시가총액을 구하면 PSR 도 산출할 수 있다. 우선 시가총액을 구하는 코드를 먼저 보자. 이 코드는 아래 화면에서 보이는 시가총액 데이터를 긁어오는 코드다.
시가총액 데이터
def get_marketcap(code):
url = 'https://navercomp.wisereport.co.kr/v2/company/c1010001.aspx?cmp_cd={}'.format(code)
raw_data = pd.read_html(url, encoding='utf-8')
data = raw_data[1]
data.columns = ['col', 'value']
marketcap = data[data['col'] == '시가총액']['value'].values[0]
marketcap = marketcap.replace(',', '')
# 단위 변환
float_marketcap = float(marketcap[:-2]) * unit_dict[marketcap[-2:]]
return float_marketcap
여기서 보면 시가총액이 "658,114억원" 이라는 string 형태로 반환되는데, 우리는 지난 번 미국편에서 다루었듯이 "억원" 이라는 단위를 숫자로 바꿔줄 수 있다. 시가총액까지 불러왔으니, 이제 남은 건 유동자산과 매출총이익 데이터. 우선 유동자산 데이터는 재무상태표에서 긁어올 수 있는데, 지난 번에 기업현황을 긁어왔던 것처럼 "encparam" 을 구하고 접근할 수 있는 URL 을 찾아 데이터를 수집하면 된다. 동일하게 "개발자도구 - Network" 를 활용해서 재무상태표를 눌렀을 때의 response 를 살펴보자.
재무상태표 URL
조금 더 내려보면 referer 정보를 확인할 수 있는데, 간단하게 말하면 http 프로토콜의 header 로 현재 페이지의 이전 페이지가 뭐였는지를 나타낸다고 볼 수 있다. Referer URL 도 필요하니 이렇게 확인할 수 있다는 점을 알아두자.
이렇게 찾은 URL 들을 바탕으로 재무상태표 데이터를 불러오는 코드는 다음과 같다.
def get_balancesheet(code):
bs_encparam = get_encparam_bs(code)
#연간
url = 'https://navercomp.wisereport.co.kr/v2/company/cF3002.aspx?cmp_cd={}&frq=0&rpt=1&finGubun=MAIN&frqTyp=0&cn=&encparam={}'.format(code, bs_encparam)
#분기
#url = 'https://navercomp.wisereport.co.kr/v2/company/cF3002.aspx?cmp_cd={}&frq=1&rpt=1&finGubun=MAIN&frqTyp=1&cn=&encparam={}'.format(code, bs_encparam)
referer_url = 'https://navercomp.wisereport.co.kr/v2/company/c1030001.aspx?cmp_cd={}&cn='.format(code)
response_data = requests.get(url, verify=False, headers={'referer': referer_url})
html = response_data.text
jsonObject = json.loads(html)
이렇게 얻은 jsonObject 는 아래와 같이 생겼는데, 여기서 우리가 필요한 데이터는 "DATA", "YYMM" 이다.
jsonObject 구조
그 중 "YYMM" 은 column 정보라고 보면 되고, 실제 데이터는 "DATA" 에 포함되어 있다. "DATA" 의 구조는 dictionary 의 list 로 구성되어 있다. 우리는 각 dictionary 에 접근해서 우리가 원하는 데이터를 긁어오면 된다.
jsonObject 의 DATA 구조
"DATA" 내에서 데이터 항목 이름은 "ACC_NM" 이고, 수치형 데이터는 "DATA1", "DATA2", ... 이런 식으로 구성되어 있다. 이걸 모아서 dataframe 화 한 후, 유동자산 데이터를 뽑아내면 된다. 마지막으로 포괄손익계산서에서 매출총이익 데이터를 긁어와보자. 재무상태표와 동일한 방식으로 데이터를 불러오면 되는데, 자세히 살펴보면 URL 이 비슷하다는 걸 알 수 있다.
포괄손익계산서 URL
재무상태표 URL:
https://navercomp.wisereport.co.kr/v2/company/cF3002.aspx?cmp_cd=000660&frq=0 &rpt=1 &finGubun=MAIN&frqTyp=0 &cn=&encparam=a0IyMG82R0sxT05OWlZSTlpoZXlhdz09
포괄손익계산서 URL:
https://navercomp.wisereport.co.kr/v2/company/cF3002.aspx?cmp_cd=000660&frq=0 &rpt=0 &finGubun=MAIN&frqTyp=0 &cn=&encparam=a0IyMG82R0sxT05OWlZSTlpoZXlhdz09
그렇다면 우리는 재무상태표에서 데이터를 긁어오기 위해 정의한 함수를 그대로 쓸 수 있다는 말이 된다. 아래 코드는 위의 내용들을 모두 종합해서 코드를 취합한 전체코드이다. 중간에 편의를 위해 정의한 함수는 쉬우니 한 번 공부해보도록...!! (어려우면 언제든 물어보시면 알려드림)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: better_coco
"""
import requests
import re
import os
import pandas as pd
import json
base_path = os.getcwd()
kospi_df = pd.read_csv(os.path.join(base_path, '검색 결과.csv'))
kospi_list = kospi_df['기호'].values
unit_dict = {'억원': 100000000}
#%% 기업현황
def get_basic_key_values(code):
url = 'https://navercomp.wisereport.co.kr/v2/company/c1010001.aspx?cmp_cd={}'.format(code)
html = requests.get(url).text
re_enc = re.compile("encparam: '(.*)'", re.IGNORECASE)
re_id = re.compile("id: '([a-zA-Z0-9]*)' ?", re.IGNORECASE)
encparam = re_enc.search(html).group(1)
encid = re_id.search(html).group(1)
return encparam, encid
def get_dataframe(code):
encparam, encid = get_basic_key_values(code)
url = 'http://companyinfo.stock.naver.com/v1/company/ajax/cF1001.aspx?cmp_cd={}&fin_type=0&freq_type=A&encparam={}&id={}'.format(code, encparam, encid)
headers = {'Referer': 'HACK'}
html = requests.get(url, headers=headers).text
dfs = pd.read_html(html)
df = dfs[1]
return df
#%% 시총 데이터
def get_marketcap(code):
url = 'https://navercomp.wisereport.co.kr/v2/company/c1010001.aspx?cmp_cd={}'.format(code)
raw_data = pd.read_html(url, encoding='utf-8')
data = raw_data[1]
data.columns = ['col', 'value']
marketcap = data[data['col'] == '시가총액']['value'].values[0]
marketcap = marketcap.replace(',', '')
float_marketcap = float(marketcap[:-2]) * unit_dict[marketcap[-2:]]
return float_marketcap
#%%
# 재무상태표 (balance sheet): option = 1
# 포괄손익계산서 (income statement): option = 0
def get_encparam(code):
url = 'https://navercomp.wisereport.co.kr/v2/company/c1010001.aspx?cmp_cd={}'.format(code)
html = requests.get(url).text
regex = re.compile("encparam.*")
mo = regex.search(html)
if mo != None:
encparam = mo.group()[10:].replace("\'", "")
return encparam
def get_balancesheet(code, option):
encparam = get_encparam(code)
url = 'https://navercomp.wisereport.co.kr/v2/company/cF3002.aspx?cmp_cd={}&frq=0&rpt={}&finGubun=MAIN&frqTyp=0&cn=&encparam={}'.format(code, option, encparam)
referer_url = 'https://navercomp.wisereport.co.kr/v2/company/c1030001.aspx?cmp_cd={}&cn='.format(code)
response_data = requests.get(url, verify=False, headers={'referer': referer_url})
html = response_data.text
jsonObject = json.loads(html)
cols = list(map(lambda x: x.replace('<br />', ' '), jsonObject.get('YYMM')))
cols = list(filter(lambda x: '전년대비' not in x, cols))
cols = list(filter(lambda x: '(E)' not in x, cols))
data_list = jsonObject.get('DATA')
tmp_df = []
for ele in data_list:
tmp = []
tmp.append(ele['ACC_NM'])
for idx in range(len(cols)):
tmp.append(ele['DATA{}'.format(idx + 1)])
tmp_df.append(tmp)
cols.insert(0, '항목')
df = pd.DataFrame(tmp_df, columns=cols)
return df
#%% 데이터 취합 함수
def main_code(code):
print(code)
#시가총액
marketcap = get_marketcap(code)
#기본정보
df = get_dataframe(code)
df.columns = df.columns.get_level_values(1)
def get_values(col, current=True):
time_info = df.columns[1:]
current_time = ''
for time in time_info:
if '(E)' not in time:
current_time = time
else:
break
if current == True:
return df[df['주요재무정보'] == col][current_time]
else:
return df[df['주요재무정보'] == col]
per = get_values('PER(배)').values[0]
pbr = get_values('PBR(배)').values[0]
roa = get_values('ROA(%)').values[0]
net_income = get_values('당기순이익').values[0]
total_sales = get_values('매출액').values[0]
total_liabilities = get_values('부채총계').values[0]
total_equity = get_values('자본총계').values[0]
total_assets = get_values('자산총계').values[0]
psr = marketcap / total_sales
debt_ratio = total_liabilities / total_equity
# balance sheet for 유동자산
balance_sheet = get_balancesheet(code, 1)
current_asset = balance_sheet[balance_sheet['항목'] == '유동자산'][balance_sheet.columns[-1]].values[0]
# income statement for 매출총이익
income_statement = get_balancesheet(code, 0)
gross_profit = income_statement[income_statement['항목'] == '매출총이익'][income_statement.columns[-1]].values[0]
gpa = gross_profit / total_assets
return [per, pbr, psr, marketcap, current_asset, total_liabilities, debt_ratio, roa, net_income, gpa]
#%% 전체 함수 call
tmp = []
for code in kospi_list:
try:
tmp.append(main_code(code))
except:
pass
all_df = pd.DataFrame(tmp, columns=['PER', 'PBR', 'PSR', 'MarketCap', 'CurrentAssets', 'TotalLiabilities', 'DebtRatio', 'ROA', 'NetIncome', 'GP/A'])
오늘도 즐거운 코딩 공부 되시길😆 (class 형태로 짜고 싶긴 한데 지금은 기능 구현에 좀 더 집중할게요.)