"주식 재무제표 크롤링"의 두 판 사이의 차이
DB CAFE
(→회사 사업보고서 10건 크롤링 하는 예제) |
(→자기주식 취득공시 크롤링) |
||
(같은 사용자의 중간 판 6개는 보이지 않습니다) | |||
8번째 줄: | 8번째 줄: | ||
− | ==== | + | ==== 자기주식 취득공시 크롤링 ==== |
# 해당 주소 http://dart.fss.or.kr/api/search.xml?auth=APIKEY&crp_cd=005930&start_dt=19990101&bsn_tp=A001 | # 해당 주소 http://dart.fss.or.kr/api/search.xml?auth=APIKEY&crp_cd=005930&start_dt=19990101&bsn_tp=A001 | ||
22번째 줄: | 22번째 줄: | ||
library(XML) | library(XML) | ||
library(methods) | library(methods) | ||
+ | library(RMySQL) | ||
corpCode <- xmlToDataFrame("D:/dev/R/corpCode.xml") | corpCode <- xmlToDataFrame("D:/dev/R/corpCode.xml") | ||
32번째 줄: | 33번째 줄: | ||
list_reprt_code <- c('11011', '11012', '11013', '11014') # 분기 리포트 | list_reprt_code <- c('11011', '11012', '11013', '11014') # 분기 리포트 | ||
+ | # # | ||
+ | # # if (mysqlHasDefault()) { | ||
+ | # # DB Connection | ||
+ | con <- dbConnect(MySQL(), | ||
+ | host = '주소', | ||
+ | dbname = 'db명', | ||
+ | user = '유저명', | ||
+ | password='비번') | ||
+ | |||
+ | dbSendQuery(con, "SET NAMES utf8;") | ||
+ | dbSendQuery(con, "SET CHARACTER SET utf8;") | ||
+ | dbSendQuery(con, "SET character_set_connection=utf8;") | ||
+ | |||
+ | # | ||
+ | # # connection info & table schma info | ||
+ | # dbListTables(con) | ||
+ | # dbListFields(con, "tb_self_meme_company") | ||
+ | # # } | ||
+ | # | ||
+ | meme_comp.table <- dbGetQuery(con, "select '한글테스트',a.* from tb_self_meme_company2 a") # Query Test 2 | ||
+ | head(meme_comp.table) | ||
+ | # class(df.table) | ||
+ | |||
+ | |||
+ | # Company List | ||
+ | corpCode <- xmlToDataFrame("D:/dev/R/corpCode.xml") | ||
+ | compList.df = corpCode$list | ||
+ | #start.date = "19990101" | ||
corp_code = "00385363" | corp_code = "00385363" | ||
− | + | ||
− | |||
− | |||
for(i in list_bsns_year){ | for(i in list_bsns_year){ | ||
for(j in list_reprt_code){ | for(j in list_reprt_code){ | ||
− | url = paste0("https://opendart.fss.or.kr/api/tesstkAcqsDspsSttus.json?crtfc_key=",api.key,"&corp_code=",corp_code,"&bsns_year=", | + | url = paste0("https://opendart.fss.or.kr/api/tesstkAcqsDspsSttus.json?crtfc_key=",api.key,"&corp_code=",corp_code,"&bsns_year=",i,"&reprt_code=",j) |
# print(url) | # print(url) | ||
data = fromJSON(url) | data = fromJSON(url) | ||
# data.df = data$list | # data.df = data$list | ||
− | + | alldata <- rbind(alldata,data$list) | |
# data.df.rcept_no = data.df$rcept_no | # data.df.rcept_no = data.df$rcept_no | ||
# data.df %>% head() | # data.df %>% head() | ||
48번째 줄: | 75번째 줄: | ||
} | } | ||
− | + | alldata %>% head() | |
+ | alldata %>% tail() | ||
</source> | </source> | ||
168번째 줄: | 196번째 줄: | ||
=== 네이버 증권 재무제표 === | === 네이버 증권 재무제표 === | ||
https://engkimbs.tistory.com/625 | https://engkimbs.tistory.com/625 | ||
− | + | *파이썬 코드 | |
<source lang=python> | <source lang=python> | ||
import requests | import requests | ||
205번째 줄: | 233번째 줄: | ||
</source> | </source> | ||
+ | |||
+ | === 네이버 증권 재무제표 WITH 판다스 === | ||
+ | <source lang=python> | ||
+ | import requests | ||
+ | from bs4 import BeautifulSoup | ||
+ | import os | ||
+ | import datetime as dt | ||
+ | import pandas as pd | ||
+ | # user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' | ||
+ | session = requests.Session() | ||
+ | # session.headers.update( {'User-agent': user_agent, 'referer': None}) | ||
+ | |||
+ | # url_test = 'https://comp.fnguide.com/SVO2/asp/SVD_Finance.asp?pGB=1&gicode=A005380&cID=&MenuYn=Y&ReportGB=D&NewMenuID=103&stkGb=701' | ||
+ | url_test = 'https://m.stock.naver.com/api/html/item/financialInfo.nhn?type=annual&code=284740' | ||
+ | r = session.get(url_test) | ||
+ | # r.encoding='utf-8' | ||
+ | # r.text | ||
+ | # print(r.text) | ||
+ | data = pd.read_html(r.text) | ||
+ | # print(data) | ||
+ | # print(type(data)) | ||
+ | # print(len(data)) | ||
+ | print(data[0]) | ||
+ | KRX = pd.read_excel('D:/DEV/R/all_company.xlsx') | ||
+ | |||
+ | KRX = KRX[['종목코드', '종목명']] | ||
+ | # KRX['종목코드'] = KRX['종목코드'].apply(lambda x: '{0:0>6}'.format(x)) | ||
+ | # print(KRX) | ||
+ | |||
+ | code = KRX['종목코드'] | ||
+ | code_list = [] | ||
+ | for cd in code.values: | ||
+ | cd = str(cd) | ||
+ | cd = ('0'*(6-len(cd)))+cd | ||
+ | code_list.append(cd) | ||
+ | |||
+ | # print(code_list) | ||
+ | for z, code in enumerate(code_list[:int(len(code_list) * 0.3)]): | ||
+ | try: | ||
+ | url_test = 'https://m.stock.naver.com/api/html/item/financialInfo.nhn?type=annual&code=%s' % code | ||
+ | r = session.get(url_test) | ||
+ | # r.encoding = 'utf-8' | ||
+ | |||
+ | data = pd.read_html(r.text) | ||
+ | |||
+ | IS_temp = data[0] | ||
+ | # print(IS_temp) | ||
+ | # IS_temp.index = IS_temp['기간'].values | ||
+ | # IS_temp.drop(['IFRS(연결)', '전년동기', '전년동기(%)'], inplace=True, axis=1) | ||
+ | |||
+ | # for i, name in enumerate(IS_temp.index): | ||
+ | # if '참여한' in name: | ||
+ | # name = name.strip() | ||
+ | # name = name.replace('계산에 참여한 계정 펼치기', '') | ||
+ | # name = name.replace(' ', '') | ||
+ | # IS_temp.rename(index={str(IS_temp.index[i]): str(name)}, inplace=True) | ||
+ | |||
+ | # IS_temp = IS_temp.T | ||
+ | # IS_temp = IS_temp.reset_index() | ||
+ | IS_temp.rename(columns={'index': 'date'}, inplace=True) | ||
+ | IS_temp.insert(0, '종목코드', code) | ||
+ | print(IS_temp[3]) | ||
+ | if z == 0: | ||
+ | IS_data = IS_temp | ||
+ | else: | ||
+ | IS_data = pd.concat([IS_data, IS_temp]) | ||
+ | except KeyError as e: | ||
+ | print(e, code) | ||
+ | except ValueError as e: | ||
+ | print(e, code) | ||
+ | |||
+ | # print(IS_data) | ||
+ | # IS_data.to_excel('D:/DEV/R/export_sample.xlsx', sheet_name='new_name') | ||
+ | # IS_temp = data[0]4 | ||
+ | # IS_temp.index =IS_temp['IFRS(연결)'].values | ||
+ | # IS_temp.drop(['IFRS(연결)', '전년동기', '전년동기(%)'], inplace=True, axis=1) | ||
+ | |||
+ | # for i, name in enumerate(IS_temp.index): | ||
+ | # # 저 글씨가 껴있는 애들만 걸리도록 설정을 해서 | ||
+ | # if '참여한' in name: | ||
+ | # name = name.strip().replace('계산에 참여한 계정 펼치기', '') | ||
+ | # # 공백이 있는지 모르곘는데, 그냥 지우고, 우리가 지우고자 하는 글씨도 지워버린다음에 | ||
+ | # name = name.replace(' ', '') | ||
+ | # # 빈ㅌ칸이 있을 수 있으니, 빈칸들도 날려버립시다. | ||
+ | # IS_temp.rename(index = {str(IS_temp.index[i]): str(name)}, inplace = True) | ||
+ | # # rename으로 index를 다시 설정해주면 됩니다. | ||
+ | # code = 'A005380' | ||
+ | # IS_temp.insert(0, '종목코드', code) | ||
+ | # print(IS_temp) | ||
+ | </source> | ||
+ | |||
[[category:주식]] | [[category:주식]] | ||
+ | [[category:R]] |
2021년 1월 27일 (수) 09:04 기준 최신판
thumb_up 추천메뉴 바로가기
- DBA { Oracle DBA 명령어 > DBA 초급 과정 > DBA 고급 과정 }
- 튜닝 { 오라클 튜닝 목록 }
- 모델링 { 데이터 모델링 가이드 }
목차
1 R에서 전자공시시스템(DART) API를 이용한 크롤링[편집]
- 금융감독원의 전자공시 시스템(http://dart.fss.or.kr/)
- 오픈 API 인증키 신청 필요 , API KEY 발급
- 하루 동안 요청할 수 있는 횟수 10,000 번
- 한번에 최대 10개까지의 데이터 받을수 있음.
- 아무런 쿼리도 입력하지 않으면 단순히 최신 공시 10건을 출력
1.1 자기주식 취득공시 크롤링[편집]
- 해당 주소 http://dart.fss.or.kr/api/search.xml?auth=APIKEY&crp_cd=005930&start_dt=19990101&bsn_tp=A001
- :(A002은 반기, A003은 분기)
- auth 는 발급받은 API key 입력
- corp_code 는 회사코드 다운로드(https://opendart.fss.or.kr/api/corpCode.xml?crtfc_key=API키)
- 데이터를 xml,json 형태( .../api/search. 뒤의 xml을 json으로만 바꾸어주면 됩니다.)
library(httr)
library(rvest)
library(jsonlite)
library(XML)
library(methods)
library(RMySQL)
corpCode <- xmlToDataFrame("D:/dev/R/corpCode.xml")
data.df = corpCode$list
api.key = "OPEN API 키값"
#start.date = "19990101"
list_bsns_year <- c('2015', '2016', '2017', '2018', '2019','2020') #
list_reprt_code <- c('11011', '11012', '11013', '11014') # 분기 리포트
# #
# # if (mysqlHasDefault()) {
# # DB Connection
con <- dbConnect(MySQL(),
host = '주소',
dbname = 'db명',
user = '유저명',
password='비번')
dbSendQuery(con, "SET NAMES utf8;")
dbSendQuery(con, "SET CHARACTER SET utf8;")
dbSendQuery(con, "SET character_set_connection=utf8;")
#
# # connection info & table schma info
# dbListTables(con)
# dbListFields(con, "tb_self_meme_company")
# # }
#
meme_comp.table <- dbGetQuery(con, "select '한글테스트',a.* from tb_self_meme_company2 a") # Query Test 2
head(meme_comp.table)
# class(df.table)
# Company List
corpCode <- xmlToDataFrame("D:/dev/R/corpCode.xml")
compList.df = corpCode$list
#start.date = "19990101"
corp_code = "00385363"
for(i in list_bsns_year){
for(j in list_reprt_code){
url = paste0("https://opendart.fss.or.kr/api/tesstkAcqsDspsSttus.json?crtfc_key=",api.key,"&corp_code=",corp_code,"&bsns_year=",i,"&reprt_code=",j)
# print(url)
data = fromJSON(url)
# data.df = data$list
alldata <- rbind(alldata,data$list)
# data.df.rcept_no = data.df$rcept_no
# data.df %>% head()
}
}
alldata %>% head()
alldata %>% tail()
1.2 공시번호로 사업보고서 확인[편집]
- 필요한 값은 공시번호에 해당하는 rcp_no 값 (data.df$rcept_no) 만 data.df.rcp 변수에 저장
- dart 홈페이지 공시들의 url 파악 필요
- 정기공시를 검색해보면 00 년 사업보고서 예시
1.3 재무제표 파일 다운로드[편집]
- 첨부되어 있는 재무제표 파일들을 다운로드 받기 위한 각 공시별 dcm 구하기
- dcm을 뽑아내기 위해 크롬 > 개발자도구 > xpath를 이용하여 추출
- //*[@id="north"]/div[2]/ul/li[1]/a
- 해당 부분의 값을 R에서 추출하는 법
# 위 페이지 url 정보를 GET() 함수를 통해 가져오며,read_html()을 통해 html 정보를 읽을 후,xpath 값으로 해당 노드의 정보를 읽어오도록 합니다.
url.business.report = 'http://dart.fss.or.kr/dsaf001/main.do?rcpNo=20180402005019'
req = GET(url.business.report)
req = read_html(req) %>% html_node(xpath = '//*[@id="north"]/div[2]/ul/li[1]/a')
req {xml_node} "#download"
onclick="openPdfDownload('20180402005019', '6060273'); return false;"> [1] "/images/common/viewer_down.gif" style="cursor:pointer;" alt="다운로드" title="다운로드">
req 변수 확인해보면 onclick 뒤의 openPdfDownload 부분이 있으며 앞의 20180402005019 부분은 위에서 나온 rcp_no,위의 6060273 부분이 해당 파일의 dcm에 해당.
dcm은 각 공시에 해당하는 문서번호로 이해.
req = req %>% html_attr('onclick')
dcm = stringr::str_split(req, ' ')[[1]][2] %>% readr::parse_number()
- html_attr() 함수를 이용해 'onclick' 부분의 데이터만 뽑아
- stringr 패키지의 str_split() 함수를 통해 캐릭터 값을 나눠준 후 두번째 값만을 뽑아내기
- 마지막으로 readr 패키지의 parse_number() 함수를 통해 해당 값에서 숫자 값만을 뽑아주도록 합니다.
- 정규표현식을 아신다면 해당작업이 훨씬 쉽게 가능
1.3.1 홈페이지 첨부된 엑셀 파일이 다운로드[편집]
- 상단의 다운로드 부분을 클릭한 후 팝업창이 뜨면, 다시 개발자 도구를 열어줍니다.
- 재무제표 항목을 클릭하면 파일이 다운로드
http://dart.fss.or.kr/pdf/download/excel.do
위의 url에 데이터를 요청하며 쿼리에 해당하는 부분은 rcp_no는 위에서 구한 값(공시번호),dcm_no 역시 위에서 찾아낸 문서번호,lang은 한국어인 ko가 있습니다.
R에서 post 형식으로 나타내면 다음과 같습니다.
query.base = list(
rcp_no = '20180402005019',
dcm_no = dcm
)
down.excel = POST('http://dart.fss.or.kr/pdf/download/excel.do', query = query.base)
down.excel
Response [http://dart.fss.or.kr/pdf/download/excel.do?rcp_no=20180402005019&dcm_no=5026126]
Date: 2019-02-18 14:08
Status: 200
Content-Type: application/vnd.ms-excel
Size: 74.8 kB
down.excel 변수를 확인해보면 엑셀 파일이 연결되어 있음이 확인됩니다.
writeBin(content(down.excel, "raw"), paste0(ticker, "_", data.df.rcp[i], '.xls'))
writeBin() 함수를 통해 해당 파일을 다운로드 받으며 저장 이름은 티커_rcp.xls 로 하도록 합니다.
모든 재무제표 항목이 포함된 엑셀파일이 잘 다운로드 됨이 확인됩니다.
df = readxl::read_excel( paste0(ticker, "_", data.df.rcp[i], '.xls'), sheet = 2)
df
# A tibble: 62 x 4
`연결 재무상태표` ..2 ..3 ..4
<chr> <chr> <chr> <chr>
1 제 49 기 2017.12.31 현재 NA NA NA
2 제 48 기 2016.12.31 현재 NA NA NA
3 제 47 기 2015.12.31 현재 NA NA NA
4 (단위 : 백만원) NA NA NA
5 NA 제 49 기 제 48 기 제 47 기
6 자산 NA NA NA
7 유동자산 146982464 141429704 124814725
8 현금및현금성자산 30545130 32111442 22636744
9 단기금융상품 49447696 52432411 44228800
10 단기매도가능금융자산 3191375 3638460 4627530
# ... with 52 more rows
read_excel() 함수를 통해 다운로드 받은 파일을읽어올 수도 있습니다.
처음 API를 통한 json 형태로 얻은 10개의 rcp를 통해 최근 10년치 엑셀 파일을 모두 다운로드 받는 방법은 for loop를 통해 해결할 수 있습니다.
<source lang=r>
for (i in 1 : length(data.df.rcp)) {
url.business.report = paste0('http://dart.fss.or.kr/dsaf001/main.do?rcpNo=',data.df.rcp[i])
req = GET(url.business.report)
req = read_html(req) %>% html_node(xpath = '//*[@id="north"]/div[2]/ul/li[1]/a')
req = req %>% html_attr('onclick')
dcm = stringr::str_split(req, ' ')[[1]][2] %>% readr::parse_number()
query.base = list(rcp_no = '20180402005019',dcm_no = dcm,lang = 'ko')
down.excel = POST('http://dart.fss.or.kr/pdf/download/excel.do',query = query.base)
writeBin(content(down.excel, "raw"), paste0(ticker, "_", data.df.rcp[i], '.xls'))
Sys.sleep(2)
print(i)
}
2 네이버 증권 재무제표[편집]
https://engkimbs.tistory.com/625
- 파이썬 코드
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:]
3 네이버 증권 재무제표 WITH 판다스[편집]
import requests
from bs4 import BeautifulSoup
import os
import datetime as dt
import pandas as pd
# user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
session = requests.Session()
# session.headers.update( {'User-agent': user_agent, 'referer': None})
# url_test = 'https://comp.fnguide.com/SVO2/asp/SVD_Finance.asp?pGB=1&gicode=A005380&cID=&MenuYn=Y&ReportGB=D&NewMenuID=103&stkGb=701'
url_test = 'https://m.stock.naver.com/api/html/item/financialInfo.nhn?type=annual&code=284740'
r = session.get(url_test)
# r.encoding='utf-8'
# r.text
# print(r.text)
data = pd.read_html(r.text)
# print(data)
# print(type(data))
# print(len(data))
print(data[0])
KRX = pd.read_excel('D:/DEV/R/all_company.xlsx')
KRX = KRX[['종목코드', '종목명']]
# KRX['종목코드'] = KRX['종목코드'].apply(lambda x: '{0:0>6}'.format(x))
# print(KRX)
code = KRX['종목코드']
code_list = []
for cd in code.values:
cd = str(cd)
cd = ('0'*(6-len(cd)))+cd
code_list.append(cd)
# print(code_list)
for z, code in enumerate(code_list[:int(len(code_list) * 0.3)]):
try:
url_test = 'https://m.stock.naver.com/api/html/item/financialInfo.nhn?type=annual&code=%s' % code
r = session.get(url_test)
# r.encoding = 'utf-8'
data = pd.read_html(r.text)
IS_temp = data[0]
# print(IS_temp)
# IS_temp.index = IS_temp['기간'].values
# IS_temp.drop(['IFRS(연결)', '전년동기', '전년동기(%)'], inplace=True, axis=1)
# for i, name in enumerate(IS_temp.index):
# if '참여한' in name:
# name = name.strip()
# name = name.replace('계산에 참여한 계정 펼치기', '')
# name = name.replace(' ', '')
# IS_temp.rename(index={str(IS_temp.index[i]): str(name)}, inplace=True)
# IS_temp = IS_temp.T
# IS_temp = IS_temp.reset_index()
IS_temp.rename(columns={'index': 'date'}, inplace=True)
IS_temp.insert(0, '종목코드', code)
print(IS_temp[3])
if z == 0:
IS_data = IS_temp
else:
IS_data = pd.concat([IS_data, IS_temp])
except KeyError as e:
print(e, code)
except ValueError as e:
print(e, code)
# print(IS_data)
# IS_data.to_excel('D:/DEV/R/export_sample.xlsx', sheet_name='new_name')
# IS_temp = data[0]4
# IS_temp.index =IS_temp['IFRS(연결)'].values
# IS_temp.drop(['IFRS(연결)', '전년동기', '전년동기(%)'], inplace=True, axis=1)
# for i, name in enumerate(IS_temp.index):
# # 저 글씨가 껴있는 애들만 걸리도록 설정을 해서
# if '참여한' in name:
# name = name.strip().replace('계산에 참여한 계정 펼치기', '')
# # 공백이 있는지 모르곘는데, 그냥 지우고, 우리가 지우고자 하는 글씨도 지워버린다음에
# name = name.replace(' ', '')
# # 빈ㅌ칸이 있을 수 있으니, 빈칸들도 날려버립시다.
# IS_temp.rename(index = {str(IS_temp.index[i]): str(name)}, inplace = True)
# # rename으로 index를 다시 설정해주면 됩니다.
# code = 'A005380'
# IS_temp.insert(0, '종목코드', code)
# print(IS_temp)