본문 바로가기

Data Scraping/#Quant Portfolio

[퀀트] R을 활용한 퀀트 투자 포트폴리오 만들기 (6)

Chapter 6 금융 데이터 수집하기 (심화)

 

6.1 수정주가 크롤링

이번 챕터에서는 수정주가, 재무제표, 가치지표를 크롤링함

국내 중소형주나 종목의 수정주가를 크롤링하기 위해 네이버 금융판을 이용함

6.1.1 개별종목 주가 크롤링

네이버 금융의 차트 탭에서 사용하는 데이터를 url에서 확인 > 날짜별 수정주가 기준의 시가, 고가, 저가, 종가, 거래량 데이터를 받아옴

이때 다른 종목의 데이터를 받아오고 싶다면 url 주소의 symbol= 뒤 티커만 변경해주면 됨

library(stringr)

# 이전 챕터에서 저장한 파일 불러오기
KOR_ticker = read.csv('data/KOR_ticker.csv', row.name = 1)
print(KOR_ticker$'종목코드'[1])

(결과) 6자리 중 앞 '00' 이 지워진 상태

[1] 5930

왼쪽에 0을 추가해 6자리로 만들어줌

KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0')
library(xts)

# data 폴더 내 KOR_price 폴더 생성
ifelse(dir.exists('data/KOR_price'), FALSE, dir.create('data/KOR_price'))

[1] FALSE

# 이후 loop문에서 i를 변경하여 모든 종목의 주가 다운로드
i = 1
name = KOR_ticker$'종목코드'[i] # name 에 티커를 입력

# xts 함수는 빈 시계열 데이터 생성
# Sys.Date() 함수로 현재 날짜를 기준으로 order.by
price = xts(NA, order.by = Sys.Date())
print(price)

(결과) 빈 시계열 데이터 생성 후 현재 날짜로 ordering

            [,1]
 2020-05-31   NA
library(httr)
library(rvest)

# name엔 위에서 받은 티커가 있음
url = paste0('https://fchart.stock.naver.com/sise.nhn?symbol=',name,'&timeframe=day&count=500&requestType=0')

# url 페이지의 데이터 불러오기
data = GET(url)
data_html = read_html(data, encoding = 'EUC-KR') %>% # HTML 정보 읽기
	html_nodes('item') %>% # item 태그와
    html_attr('data') # data 속성의 데이터를 추출

print(head(data_html)

(결과) | 로 구분된 날짜, 주가, 거래량 데이터가 추출됨

 [1] "20180516|49200|50200|49150|49850|15918683"
 [2] "20180517|50300|50500|49400|49400|10365440"
 [3] "20180518|49900|49900|49350|49500|6706570" 
 [4] "20180521|49650|50200|49100|50000|9020998" 
 [5] "20180523|50600|52000|50400|51800|17095490"
 [6] "20180524|52000|52000|51100|51400|8289275"

위 데이터를 테이블 형태로 변환

library(readr)

# 구분자로 이루어진 데이터를 테이블 형태로 변환시켜주는 read_delim() 함수
price = read_delim(data_html, delim = '|')
print(head(price))

(결과) 각 열은 날짜, 시가, 고가, 저가, 종가, 거래량을 의미

   A tibble: 6 x 6
   `20180516` `49200` `50200` `49150` `49850` `15918683`

 1   20180517   50300   50500   49400   49400   10365440
 2   20180518   49900   49900   49350   49500    6706570
 3   20180521   49650   50200   49100   50000    9020998
 4   20180523   50600   52000   50400   51800   17095490
 5   20180524   52000   52000   51100   51400    8289275
 6   20180525   51000   52800   50800   52700   15207266

위 데이터 중 원하는 날짜, 종가만을 선택해서 추출

library(lubridate)
library(timetk)

price = price[c(1, 5)] # 1열과 5열만 선택해 price에 저장
price = data.frame(price) # 데이터프레임으로 변형
colnames(price) = c('Date', 'Price') # 열 이름 지정
price[, 1] = ymd(price[, 1]) # ymd() 함수로 데이터 형태를 yyyy-mm-dd로 변형
price = tk_xts(price, date_var = Date) # 시계열 데이터로 변형 

print(tail(price))

(결과)

            Price
 2020-05-22 48750
 2020-05-25 48850
 2020-05-26 49250
 2020-05-27 49900
 2020-05-28 50400
 2020-05-29 50700

위 테이블을 폴더에 저장

write.csv(price, paste0('data/KOR_price/', name, '_price.csv'))

 

6.1.2 전 종목 주가 크롤링

위의 개별 종목 크롤링 코드에서 for loop 문을 통해 i만 변경해주면 전 종목 크롤링이 가능

library(httr)
library(rvest)
library(stringr)
library(xts)
library(lubridate)
library(readr)

# 티커 만들어주기
KOR_ticker = read.csv('data/KOR_ticker.csv', row.names = 1)
print(KOR_ticker$'종목코드'[1])

KOR_ticker$'종목코드' = str_pad(KOR_ticker$'종목코드', 6, side = c('left'), pad = '0')

# 폴더 생성
ifelse(dir.exists('data/KOR_price'), FALSE, dir.create('data/KOR_price'))

# i를 계속 변경해주면서 모든 종목의 주가 다운로드
for(i in 1:nrow(KOR_ticker)) {
	price = xts(NA, order.by = Sys.Date()) # 빈 시계열 데이터 생성
    name = KOR_ticker$'종목코드'[i] # 티커 부분 선택
    
    # tryCatch() 함수는 오류 발생 시 이를 무시하고 다음 루프로 진행
    tryCatch({
    	# url 생성
        url = paste0('https://fchart.stock.naver.com/sise.nhn?symbol=', 
        name, '&timeframe=day&count=500&requestType=0')
        
        # 데이터 다운로드 (개별 종목과 동일한 과정)
        data = GET(url)
        data_html = read_html(data, encoding = 'EUC-KR') %>% 
        	html_nodes("item") %>%
            html_attr("data")
            
            # 데이터 나누기
            price = read_delim(data_html, delim = '|')
            
            # 필요한 열만 선택 후 데이터 클렌징
            price = price[c(1, 5)] # 1, 5열만
            price = data.frame(price) # df로 변환
            colnames(prcie) = c('Date', 'Price') #열 이름 지정
            price[, 1] = ymd(price[, 1]) # 데이터 형태 변형
            
            rownames(price) = price[, 1] # 행 이름 지정
            price[, 1] = NULL 
        }, error = function(e) {
        
        	# 오류 발생 시 해당 종목명을 출력하고 다음 루프로 이동
            warning(paste0("Error in Ticker: ", name))
        })
        
        # 다운로드 받은 파일을 생성한 폴더 내 csv 파일로 저장
        write.csv(price, paste0('data/KOR_price/', name, '_price.csv'))
        
        # 무한 크롤링 방지 타임슬립 (2초)
        Sys.sleep(2)
    }
    

 

6.2 재무제표 및 가치지표 크롤링

가치지표의 경우 Company Guide 사이트에서 크롤링할 것임

 

6.2.1 재무제표 다운로드

http://comp.fnguide.com/SVO2/ASP/SVD_Finance.asp?pGB=1&gicode=A005930 에서 원하는 데이터를 가져올 수 있음

library(httr)
library(rvest)

# 폴더 생성
ifelse(dir.exists('data/KOR_fs'), FALSE, dir.create('data/KOR_fs'))

# 로케일 언어 영어로 설정
Sys.setlocale("LC_ALL", "English")

# GET 방식으로 url 내의 내용을 받아옴
url = paste0('http://comp.fnguide.com/SVO2/ASP/SVD_Finance.asp?pGB=1&gicode=A005930)
data = GET(url, user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) # 웹브라우저 구별 입력
							# 웹 브라우저 리스트 http://www.useragentstring.com/pages/useragentstring.php 에서 확인
							AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'))

data = data %>%
	read_html() %>% # HTML 내용 읽기
    html_table() # 테이블만 추출
    
# 로케일 언어 한국어로 재지정
Sys.setlocale("LC_ALL", "Korean")

lapply(data, function(x) {
	head(x, 3)})

(결과)

 [[1]]
   IFRS(연결)   2017/12   2018/12   2019/12 2020/03
 1     매출액 2,395,754 2,437,714 2,304,009 553,252
 2   매출원가 1,292,907 1,323,944 1,472,395 348,067
 3 매출총이익 1,102,847 1,113,770   831,613 205,185
 
   전년동기 전년동기(%)
 1  523,855         5.6
 2  327,465         6.3
 3  196,391         4.5
 
 [[2]]
   IFRS(연결) 2019/06 2019/09 2019/12 2020/03 전년동기
 1     매출액 561,271 620,035 598,848 553,252  523,855
 2   매출원가 359,447 399,939 385,545 348,067  327,465
 3 매출총이익 201,824 220,096 213,302 205,185  196,391
 
   전년동기(%)
 1         5.6
 2         6.3
 3         4.5
 
 [[3]]
                          IFRS(연결)   2017/12   2018/12
 1                              자산 3,017,521 3,393,572
 2 유동자산계산에 참여한 계정 펼치기 1,469,825 1,746,974
 3                          재고자산   249,834   289,847
 
     2019/12   2020/03
 1 3,525,645 3,574,575
 2 1,813,853 1,867,397
 3   267,665   284,549
 
 [[4]]
                          IFRS(연결)   2019/06   2019/09
 1                              자산 3,429,401 3,533,860
 2 유동자산계산에 참여한 계정 펼치기 1,734,335 1,860,421
 3                          재고자산   312,470   309,088
 
     2019/12   2020/03
 1 3,525,645 3,574,575
 2 1,813,853 1,867,397
 3   267,665   284,549
 
 [[5]]
                     IFRS(연결) 2017/12 2018/12 2019/12
 1     영업활동으로인한현금흐름 621,620 670,319 453,829
 2                   당기순손익 421,867 443,449 217,389
 3 법인세비용차감전계속사업이익        
 
   2020/03
 1 118,299
 2  48,849
 3        
 
 [[6]]
                     IFRS(연결) 2019/06 2019/09 2019/12
 1     영업활동으로인한현금흐름  65,949 138,266 197,171
 2                   당기순손익  51,806  62,877  52,270
 3 법인세비용차감전계속사업이익                        
 
   2020/03
 1 118,299
 2  48,849
 3

위 코드로 만들어진 재무제표 테이블 중 1, 3, 5번째 테이블만 선택

data_IS = data[[1]]
data_BS = data[[3]]
data_CF = data[[5]]

print(names(data_IS))

(결과)

 [1] "IFRS(연결)"  "2017/12"     "2018/12"    
 [4] "2019/12"     "2020/03"     "전년동기"   
 [7] "전년동기(%)"
# 포괄손익계산서 테이블에서 '전년동기', '전년동기(%)'열 삭제
data_IS = data_IS[,  1:(ncol(data_IS) -2)]

데이터 클렌징

data_fs = rbind(data_IS, data_BS, data_CF) # 세 테이블 행으로 묶기
data_fs[, 1] = gsub('계산에 참여한 계정 펼치기', '', data_fs[, 1]) # gsub() 함수로 글자 삭제

data_fs = data_fs[!duplicated(data_fs[, 1]), ] # 중복되는 계정명 삭제

rownames(data_fs) = NULL # 행 이름 초기화
rownames(date_fs) = data_fs[, 1] # 열의 계정명을 행 이름으로 변경
data_fs[, 1] = NULL # 1열은 삭제

data_fs = data_fs[, substr(colnames(data_fs), 6, 7) == '12] # substr() 함수로 12월 결산 데이터(끝 글자가 12인 열) 만 선택

print(head(data_fs))

(결과)

                    2017/12   2018/12   2019/12
 매출액           2,395,754 2,437,714 2,304,009
 매출원가         1,292,907 1,323,944 1,472,395
 매출총이익       1,102,847 1,113,770   831,613
 판매비와관리비     566,397   524,903   553,928
 인건비              67,972    64,514    64,226
 유무형자산상각비    13,366    14,477    20,408
sapply(data_fs, typeof)

(결과)

     2017/12     2018/12     2019/12 
 "character" "character" "character"

위 데이터는 문자형이므로 숫자형으로 변경해줌

library(stringr)

data_fs = sapply(data_fs, function(x) { # 각 열에 함수 적용
	str_replace_all(x, ',', '') %>% # ',' 제거 후
    	as.numeric() # 숫자형 데이터로 변환
}) %>%
	data.frame(., row.name = rownames(data_fs)) # df로 변환, 행 이름은 그대로 유지
    
print(head(data_fs))

(결과) 숫자형으로 변경된 데이터 프레임 형태

                  X2017.12 X2018.12 X2019.12
 매출액            2395754  2437714  2304009
 매출원가          1292907  1323944  1472395
 매출총이익        1102847  1113770   831613
 판매비와관리비     566397   524903   553928
 인건비              67972    64514    64226
 유무형자산상각비    13366    14477    20408
sapply(data_fs, typeof)

(결과)

 X2017.12 X2018.12 X2019.12 
 "double" "double" "double"

 


본 게시글은 다음 강의를 참고하여 공부한 내용을 기록한 것입니다.

https://www.fastcampus.co.kr/courses/202382/clips/

 


 

I'm a Senior Student in Data Science ! 

데이터 사이언스를 공부하고 있는 4학년 학부생의 TIL 블로그입니다. 게시글이 도움 되셨다면 구독과 좋아요 :)