[Python 재무제표 크롤링 #7] NAVER 금융에서 재무제표 데이터, 파이썬 데이터 프레임으로 추출하기

이전 포스팅과 이어지는 내용입니다.


▶[Python/Python 재무제표 크롤링] - [Python 재무제표 크롤링] NAVER 금융에서 재무제표 HTML 요소 추출하기


| 삼성전자 재무제표 부분 HTML 불러오기


삼성전자 재무제표 부분 HTML 를 불러오는 파이썬 스크립트입니다.

import requests
import numpy as np
import pandas as pd
from bs4 import BeautifulSoup


URL = "https://finance.naver.com/item/main.nhn?code=005930"

samsung_electronic = requests.get(URL)
html = samsung_electronic.text

soup = BeautifulSoup(html, 'html.parser')

finance_html = soup.select('div.section.cop_analysis div.sub_section')[0]


| 삼성전자 재무제표 RAW HTML 코드에서 데이터 추출하기


삼성전자 재무제표 RAW HTML 코드인 finance_html에서 재무제표의 각 연간 및 분기 날짜, 주요재무제표 정보인 매출액, 영업이익 ... 배당성향 등의 데이터를 추출해보도록 하겠습니다.


먼저 재무제표의 날짜 정보를 추출해 보도록 하겠습니다.



위 빨간색 안의 날짜정보들은 다음과 같이 thead 태그 및의 th 태그에 데이터가 위치해 있습니다.

<th class="" scope="col">
2015.12
</th>
<th class="" scope="col">
2016.12
</th>
<th class="" scope="col">
2017.12
</th>
<th class="t_line cell_strong" scope="col">
2018.12<em>(E)</em>
</th>
<th class="" scope="col">
2017.09
</th>
<th class="" scope="col">
2017.12
...


BeautifulSoup CSS Selector 방식으로 해당 날짜 데이터들을 파이썬의 리스트 형식으로 모두 추출할 수 있습니다. 

th_data = [item.get_text().strip() for item in finance_html.select('thead th')]
annual_date = th_data[3:7]
quarter_date = th_data[7:13]
['2015.12', '2016.12', '2017.12', '2018.12(E)']
['2017.09', '2017.12', '2018.03', '2018.06', '2018.09', '2018.12(E)']


주요재무정보의 각 명칭들을 추출해 보겠습니다.



매출액, 영업이익, 당기순이익 같은 재무정보 지표는 th 태그의 h_th2 클래스에 위치해 있습니다.

<th class="h_th2 th_cop_anal8" scope="row"><strong>매출액</strong></th>
...
<th class="h_th2 th_cop_anal11" scope="row"><strong>영업이익률</strong></th>
...

추출하는 방법은 다음과 같습니다.

finance_index = [item.get_text().strip() for item in finance_html.select('th.h_th2')][3:]
['매출액', '영업이익', '당기순이익', '영업이익률', '순이익률', 'ROE(지배주주)', '부채비율', '당좌비율', '유보율', 'EPS(원)', 'BPS(원)', '주당배당금(원)', '시가배당률(%)', '배당성향(%)']


마찬가지 방법으로 재무제표의 데이터들을 추출합니다. 



이 데이터들은 td 태그에 위치해있습니다. 

< td class ="" >


2,006,535

</td>
<td class ="" >


2,018,667

< td >

...

추출방법은 다음과 같습니다.

finance_data = [item.get_text().strip() for item in finance_html.select('td')]
['2,006,535', '2,018,667', '2,395,754', '2,508,293', '620,489', '659,784', '605,637', '584,827', '654,600', '663,198', '264,134', '292,407', '536,450', '642,924', '145,332', '151,470', '156,422', '148,690', '175,749', '162,062', '190,601', '227,261', '421,867', '484,835', '111,934', '122,551', '116,885', '110,434', '131,507', '125,966', '13.16', '14.49', '22.39', '25.63', '23.42', '22.96', '25.83', '25.42', '26.85', '24.44', '9.50', '11.26', '17.61', '19.33', '18.04', '18.57', '19.30', '18.88', '20.09', '18.99', '11.16', '12.48', '21.01', '21.14', '19.24', '21.01', '22.79', '21.77', '21.73', '', '35.25', '35.87', '40.68', '', '40.76', '40.68', '39.96', '36.70', '39.28', '', '209.74', '223.46', '181.61', '', '177.37', '181.61', '188.10', '197.58', '198.16', '', '21,117.88', '22,004.14', '24,536.12', '', '23,529.09', '24,536.12', '25,279.75', '26,235.70', '27,412.63', '', '2,198', '2,735', '5,421', '6,535', '1,487', '1,629', '1,583', '1,500', '1,771', '1,686', '23,715', '26,636', '30,427', '36,167', '29,716', '30,427', '31,782', '33,223', '34,519', '36,167', '420', '570', '850', '1,508', '', '', '', '', '', '', '1.67', '1.58', '1.67', '', '', '', '', '', '', '', '16.42', '17.81', '14.09', '', '', '', '', '', '', '']


| 넘파이(Numpy)로 데이터 변환하기


finance_data는 모든 재무제표 정보를 담고 있지만 하나의 리스트 형태로 되어있기 때문에 재무제표 데이터를 쉽게 분석하기 위해서는 전처리가 진행되어야 합니다.


파이썬 넘파이(Numpy)를 사용하여 하나의 리스트로 된 데이터를 위 재무제표정보와 같이 14 X 10 형태의 행렬로 변환해 보도록 하겠습니다.


import numpy as np

finance_data = np.array(finance_data)
finance_data.resize(len(finance_index), 10)

다음과 같이 데이터가 행렬형태로 변환됩니다

[['2,006,535' '2,018,667' '2,395,754' '2,508,293' '620,489' '659,784'
'605,637' '584,827' '654,600' '663,198']
['264,134' '292,407' '536,450' '642,924' '145,332' '151,470' '156,422'
'148,690' '175,749' '162,062']
['190,601' '227,261' '421,867' '484,835' '111,934' '122,551' '116,885'
'110,434' '131,507' '125,966']
['13.16' '14.49' '22.39' '25.63' '23.42' '22.96' '25.83' '25.42' '26.85'
'24.44']
['9.50' '11.26' '17.61' '19.33' '18.04' '18.57' '19.30' '18.88' '20.09'
'18.99']
['11.16' '12.48' '21.01' '21.14' '19.24' '21.01' '22.79' '21.77' '21.73'
'']
['35.25' '35.87' '40.68' '' '40.76' '40.68' '39.96' '36.70' '39.28' '']
['209.74' '223.46' '181.61' '' '177.37' '181.61' '188.10' '197.58'
'198.16' '']
['21,117.88' '22,004.14' '24,536.12' '' '23,529.09' '24,536.12'
'25,279.75' '26,235.70' '27,412.63' '']
['2,198' '2,735' '5,421' '6,535' '1,487' '1,629' '1,583' '1,500' '1,771'
'1,686']
['23,715' '26,636' '30,427' '36,167' '29,716' '30,427' '31,782' '33,223'
'34,519' '36,167']
['420' '570' '850' '1,508' '' '' '' '' '' '']
['1.67' '1.58' '1.67' '' '' '' '' '' '' '']
['16.42' '17.81' '14.09' '' '' '' '' '' '' '']]


finance_data와 위에서 구한 annual_date, quarter_date 그리고 finance_index를 합쳐 재무제표를 나타내는 데이터프레임을 만들어보도록 하겠습니다.


먼저 annual_date quarter_date를 합치고 난 후 전체 데이터를 데이터프레임으로 나타내는 작업을 할 수 있습니다.

finance_date = annual_date + quarter_date

import pandas as pd
finance = pd.DataFrame(data=finance_data[0:,0:], index=finance_index, columns=finance_date)
             2015.12    2016.12    ...        2018.09 2018.12(E)
매출액 2,006,535 2,018,667 ... 654,600 663,198
영업이익 264,134 292,407 ... 175,749 162,062
당기순이익 190,601 227,261 ... 131,507 125,966
영업이익률 13.16 14.49 ... 26.85 24.44
순이익률 9.50 11.26 ... 20.09 18.99
ROE(지배주주) 11.16 12.48 ... 21.73
부채비율 35.25 35.87 ... 39.28
당좌비율 209.74 223.46 ... 198.16
유보율 21,117.88 22,004.14 ... 27,412.63
EPS(원) 2,198 2,735 ... 1,771 1,686
BPS(원) 23,715 26,636 ... 34,519 36,167
주당배당금(원) 420 570 ...
시가배당률(%) 1.67 1.58 ...
배당성향(%) 16.42 17.81 ...

[14 rows x 10 columns]


만약 연간 재무제표와 분기 재무제표 둘로 나누고 싶다면 데이터프레임의 iloc메서드와 슬라이싱으로 간단하게 나눌수 있습니다.


annual_finance = finance.iloc[:, :4]
quarter_finance = finance.iloc[:, 4:]
            2015.12    2016.12    2017.12 2018.12(E)
매출액 2,006,535 2,018,667 2,395,754 2,508,293
영업이익 264,134 292,407 536,450 642,924
당기순이익 190,601 227,261 421,867 484,835
영업이익률 13.16 14.49 22.39 25.63
순이익률 9.50 11.26 17.61 19.33
ROE(지배주주) 11.16 12.48 21.01 21.14
부채비율 35.25 35.87 40.68
당좌비율 209.74 223.46 181.61
유보율 21,117.88 22,004.14 24,536.12
EPS(원) 2,198 2,735 5,421 6,535
BPS(원) 23,715 26,636 30,427 36,167
주당배당금(원) 420 570 850 1,508
시가배당률(%) 1.67 1.58 1.67
배당성향(%) 16.42 17.81 14.09
2017.09 2017.12 ... 2018.09 2018.12(E)
매출액 620,489 659,784 ... 654,600 663,198
영업이익 145,332 151,470 ... 175,749 162,062
당기순이익 111,934 122,551 ... 131,507 125,966
영업이익률 23.42 22.96 ... 26.85 24.44
순이익률 18.04 18.57 ... 20.09 18.99
ROE(지배주주) 19.24 21.01 ... 21.73
부채비율 40.76 40.68 ... 39.28
당좌비율 177.37 181.61 ... 198.16
유보율 23,529.09 24,536.12 ... 27,412.63
EPS(원) 1,487 1,629 ... 1,771 1,686
BPS(원) 29,716 30,427 ... 34,519 36,167
주당배당금(원) ...
시가배당률(%) ...
배당성향(%) ...

[14 rows x 6 columns]


| 전체 코드


import requests
from bs4 import BeautifulSoup

URL = "https://finance.naver.com/item/main.nhn?code=005930"

samsung_electronic = requests.get(URL)
html = samsung_electronic.text

soup = BeautifulSoup(html, 'html.parser')

finance_html = soup.select('div.section.cop_analysis div.sub_section')[0]

th_data = [item.get_text().strip() for item in finance_html.select('thead th')]
annual_date = th_data[3:7]
quarter_date = th_data[7:13]

finance_index = [item.get_text().strip() for item in finance_html.select('th.h_th2')][3:]

finance_data = [item.get_text().strip() for item in finance_html.select('td')]

import numpy as np

finance_data = np.array(finance_data)
finance_data.resize(len(finance_index), 10)

finance_date = annual_date + quarter_date

import pandas as pd
finance = pd.DataFrame(data=finance_data[0:,0:], index=finance_index, columns=finance_date)

annual_finance = finance.iloc[:, :4]
quarter_finance = finance.iloc[:, 4:]


| PLUS!


아래와 같이 DataFrame의 read_html 기능을 이용해서 아주 쉽게 재무제표 데이터를 구해올 수도 있습니다. 위의 내용은 크롤링의 전반적인 내용을 담고 있기 때문에 읽어 두시고 이렇게 쉬운 방법으로 데이터를 구할 수 있다는 것도 참고하세요~

import pandas as pd
import requests

URL = "https://finance.naver.com/item/main.nhn?code=005930"

samsung_electronic = requests.get(URL)
html = samsung_electronic.text

financial_stmt = pd.read_html(samsung_electronic.text)[3]

financial_stmt.set_index(('주요재무정보', '주요재무정보', '주요재무정보'), inplace=True)
financial_stmt.index.rename('주요재무정보', inplace=True)
financial_stmt.columns = financial_stmt.columns.droplevel(2)
print(financial_stmt)
             최근 연간 실적                          ...   최근 분기 실적                      
2017.12 2018.12 2019.12 ... 2019.09 2019.12 2020.03(E)
주요재무정보 ...
매출액 2395754.00 2437714.00 2304009.00 ... 620035.00 598848.00 557762.00
영업이익 536450.00 588867.00 277685.00 ... 77779.00 71603.00 63238.00
당기순이익 421867.00 443449.00 217389.00 ... 62877.00 52270.00 47614.00
영업이익률 22.39 24.16 12.05 ... 12.54 11.96 11.34
순이익률 17.61 18.19 9.44 ... 10.14 8.73 8.54
ROE(지배주주) 21.01 19.63 8.69 ... 10.05 8.69 NaN
부채비율 40.68 36.97 34.12 ... 34.14 34.12 NaN
당좌비율 181.61 204.12 233.57 ... 235.80 233.57 NaN
유보율 24536.12 27531.92 28856.02 ... 28541.64 28856.02 NaN
EPS(원) 5421.00 6024.00 3166.00 ... 899.00 770.00 760.00
PER(배) 9.40 6.42 17.63 ... 13.73 17.63 60.23
BPS(원) 30427.00 35342.00 37528.00 ... 37600.00 37528.00 NaN
PBR(배) 1.67 1.10 1.49 ... 1.30 1.49 NaN
주당배당금(원) 850.00 1416.00 1416.00 ... NaN NaN NaN
시가배당률(%) 1.67 3.66 2.54 ... NaN NaN NaN
배당성향(%) 14.09 21.92 44.73 ... NaN NaN NaN


이 글을 공유하기

댓글(14)

  • 김주녕
    2019.07.19 17:43

    finance_html = soup.select('div.section.cop_analysis div.sub_section')[0]

    th_data = [item.get_text().strip() for item in finance_html.select('thead th')]

    이 부분에서 finance_html 값이 list여서 그런지 pycharm에서 finance_html.select 구문이 작동하지 않더라구요.....

    • 2019.07.22 09:59 신고

      댓글 달아주셔서 감사합니다!
      이상하네요.. 지금 테스트 해 봤는데 정상작동하고 있습니다.

      혹시 소스 코드에 오타가 있는 지 확인부탁드려요 될까요?

  • 2019.10.29 11:51

    비밀댓글입니다

    • 2019.11.02 22:42 신고

      늦게 답변 드려 죄송합니다ㅠ 지금 출장 중이라

      말씀하신 부분 확인하고 피드백 드리도록 하겠습니다!

  • 2019.12.18 15:20

    비밀댓글입니다

    • 2019.12.19 00:49 신고

      그러셨군요 ㅎㅎ

      strip을 쓴 이유는 혹시나 모를 앞 뒤 공백을 제거하기 위해서입니다. 만약 공백이 있다면 문자열 처리가 제대로 작동하지 않을 수 있습니다.

      크롤링할 때 이런 작업은 웬만하면 하시는 것이 좋습니다.

  • 2020.02.01 21:26 신고

    진심으로 감사드립니다.
    샘플 코드 잘 쓰겠습니다.

  • 2020.03.31 01:21

    비밀댓글입니다

    • 2020.04.01 23:41 신고

      안녕하세요 뤼프님!
      혹시 어떤 에러인지 알 수 있을까요?

      eng.kimbs@gmail.com 으로 메일 주시면 피드백 드릴 수 있습니다.

  • 2020.04.22 13:56

    비밀댓글입니다

    • 2020.04.22 23:20 신고

      안녕하세요! ㅎㅎ

      위 포스팅에 나온 코딩은 "https://finance.naver.com/item/main.nhn?code=005930" 에 딱 맞춘 상태로 되었기 때문에 위 URL과 동일한 HTML구조로 되어 있는 사이트 외에 다른 사이트 페이지에서는 쓸 수 없습니다!

      해당 URL에서 HTML구조를 파악하고 맞춤 크롤러를 만드셔야 할 것 같습니다!

  • 오교수
    2020.05.29 15:23

    좋은 포스팅 감사합니다!

    https://finance.naver.com/item/coinfo.nhn?code=005930
    여기 안에 financial summary 테이블을 불러오고 싶은데 아무리 html 파일을 뒤적거려도 못찾겠네요 ㅠㅜ
    어떻게 찾아야 할까요...ㅜ

    • 2020.05.29 20:25 신고

      오교수님 안녕하세요!

      이 페이지는 한번에 데이터를 요청할 때 바로 html 요소를 주지 않기 때문에 그렇습니다.

      이러한 데이터를 크롤링 하는 방법은 2가지가 있는데 첫번째로는 selenium이라는 툴을 이용해서 하는 것이고 두 번째는 http 통신 및 패킷을 직접 분석해서 데이터를 요청하는 작업을 해야합니다.

      두 번째 것은 매우 고급진 테크닉이므로 알려주신다 해도 어려우실테니 첫 번째 방법인 selenium으로 크롤링하시는 것을 고려해보시기 바랍니다!

      관련 포스팅은 아래를 참고해주세요~
      https://engkimbs.tistory.com/896

Designed by JB FACTORY