Post List

2018년 11월 21일 수요일

R을 이용한 네이버 금융에서 수정주가 크롤링




대부분의 블로그에서 국내 주가를 크롤링 할때
다음 금융(http://finance.daum.net/)을 사용하고는 했습니다.

그 이유는 사실 간단합니다.
수정주가를 제공하는 곳이 사실상 
다음 금융이 유일했기 때문이죠.


하지만 해당 사이트의 웹페이지 구조가 바뀌면서
크롤링이 번거롭게 되는 비극이 발생했습니다.


네이버 금융(https://finance.naver.com/)에서도 주가를 제공하고는 있지만
이곳을 잘 활용하지 않던 이유는 크게 두가지 입니다.



1. 데이터가 페이지에 나누어 있어 크롤링의 번거로움


네이버 금융의 시세(https://finance.naver.com/item/sise.nhn?code=005930)에는
일자별 주가를 제공하고 있습니다.




그러나 한 페이지당 10영업일 (2주)의 데이터 밖에 없기 때문에
1년치 (52주)의 데이터를 얻기 위해서는

for loop 구문을 통해 26 페이지 까지의 데이터를 크롤링 해야하며
이런 작업은 굉장히 시간이 오래 소요됩니다.



2. 수정주가 미제공





사실 이 이유가 가장 크다고 볼 수 있습니다.
5월 4일 삼성전자 주가가 큰 폭으로 떨어진것 처럼 보이지만
액면분할을 고려한다면 사실 그렇지 않습니다.

그러나 네이버에서는 이러한 액면분할을 고려한 수정주가가
제공되지 않는 단점이 있습니다.


그러나 네이버 금융에서도 이런 두가지 문제를
아주 깔끔쓰하게 해결하는 방법이 있습니다.


먼저 크롬에서 삼성전자 종목의 '차트'탭을 들어갑니다.




위처럼 플래쉬가 차단되어 나오지 않을 경우
주소창의 왼쪽 자물쇠 버튼을 클릭한 다음
Flash를 허용으로 바꾼 후 새로고침을 누르면 됩니다.





삼성전자의 역사적 주가가 잘 나오게 됩니다.
더욱이 2018년 5월 이전, 즉 액면분할 이전의 주가도
수정주가로 환산되어 나오는 것이 확인됩니다.

따라서 해당 차트에 제공되는 데이터를 따올수만 있다면
수정주가 문제는 자동으로 해결되게 됩니다.

이번엔 F12를 눌러서 개발자도구 화면을 열고 새로고침을 눌러
해당 페이지가 어떠한 데이터를 가지고 오는지 보도록 합니다.






개발자도구 화면에서 Network를 눌러보면
어떠한 데이터들이 다운로드 되어있는지 확인이 됩니다.

대부분은 페이지에 들어가는 그림파일이며,
우리가 주목할 것은 가장 아래쪽에 위치한

sise.nhn?symbol=005930&timeframe=day&count=500&requestType=0

부분입니다.

이는 차트를 그리는데 필요한 데이터를 받아오는 부분이기 때문입니다.
해당 탭을 클릭하여 Request URL 페이지를 접속해 보도록 하겠습니다.





얼핏 보기에도 일자와 주가에 해당하는 데이터가 있는 듯 합니다.
각 데이터에 대한 설명은 다음과 같습니다.


 symbol="005930" name="삼성전자" count="500" timeframe="day" precision="0" origintime="19900103">


symbol은 티커를,
name은 종목 명을,
count는 몇일에 해당하는 데이터인지를, 
timeframe은 일/주/월 데이터 여부를
origintime은 최초 일자를 의미합니다.



 data="20161107|32940|33000|32680|32800|153238"/>

(1) 20161107 은 해당 일자를
(2) 32940 는 시가를
(2) 33000 는 고가를
(3) 32680 는 저가를
(4) 32800 는 종가를
(5) 153238 는 거래량을 의미합니다.

이 중 우리에게 가장 필요하는 것은 (1)일자와 (4) 종가 이겠죠.

그렇다면 해당 페이지의 HTML 구조를 뜯어보도록 하겠습니다.

사실 뜯어볼 것도 없이
item 태그의 date 속성에 모든 데이터가 있음이
한눈에 보입니다.

그렇다면 이러한 데이터를 R로 가져오는 코드를 살펴보도록 합니다.


library(lubridate)
library(httr)
library(rvest)
library(xts)


먼저 필요한 패키지를 불러옵니다.
lubridate는 날짜를 클렌징 하는 패키지,
httrrvest는 크롤링에 필요한 패키지,
xts는 시계열 형태로 바꾸는데 필요한 패키지 


tick = "005930"
url = paste0("https://fchart.stock.naver.com/sise.nhn?symbol="
             ,tick,"&timeframe=day&count=1000&requestType=0")


tick은 삼성전자의 티커에 해당하는 005930를 입력하며,
url 부분에는 paste0을 이용하여 url을 완성해 줍니다.

결과적으로 url은 다음과 같으며,
for loop를 이용한다면 전종목의 데이터도 받을 수 있습니다.


> url
[1] "https://fchart.stock.naver.com/sise.nhn?symbol=005930&timeframe=day&count=500&requestType=0"



GET(url) %>%
  read_html %>%
  html_nodes("item")

GET() 함수를 이용하여 url에 해당하는 데이터를 받아온 후,
read_html() 함수를 이용하여 html 정보를 불러옵니다.
그 후, html_nodes() 함수를 이용하여
item 태그에 해당하는 정보들을 모두 불러오도록 합니다.





item 태그에 해당하는 데이터만 제대로 발라집니다.
이 중 data 속성에 해당하는 부분만 추가로 발라내도록 합니다.

GET(url) %>%
  read_html %>%
  html_nodes("item") %>%
  html_attr("data")


html_attr() 함수를 추가해주어 "data" 속성을 찾아내도록 합니다.
그 결과는 다음과 같습니다.




속성에 해당하는 데이터만이 선택되게 됩니다.
또한 각 데이터는 |를 통해 구분이 되고 있으므로,
strsplit() 함수를 이용하여 데이터를 나누어 보도록 하겠습니다.


data = GET(url) %>%
  read_html %>%
  html_nodes("item") %>%
  html_attr("data") %>%
  strsplit("\\|")

|를 기준으로 데이터를 나누기 위해
역슬러쉬 두개(\\)를 이용하여 이스케이프 선언을 해줍니다.




해당 내용이 저장된 data 변수를 확인해보면
각 리스트에 |를 기준으로 글자가 쪼개어져 있음이 확인됩니다.

이젠 이중에서 일자에 해당하는 첫번째 글자와
종가에 해당하는 다섯번째 글자만 선택하도록 하겠습니다.


data = lapply(data, function(x) {
  x[c(1, 5)] %>% t() %>% data.frame()
})

data = do.call(rbind, data)


먼저 데이터가 리스트 형식이므로 lapply() 함수를 적용해주며,
첫번째와 다섯번째 데이터를 선택해 줍니다.

그 후 t() 함수를 통해 열을 행의 형태로 바꾸어 준 후,
data.frame() 함수를 통해 데이터 프레임 형식으로 바꾸어 줍니다.

마지막으로 do.call() 함수를 통해 해당 리스트를
행의 형태로 묶어주도록 합니다.




data 변수를 확인해보면
첫번째 열에는 날짜가, 두번째 열에는 종가가 정리되어 있음이 확인됩니다.


> str(data)
'data.frame': 500 obs. of  2 variables:
 $ X1: Factor w/ 500 levels "20161107","20161108",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ X2: Factor w/ 356 levels "32800","32880",..: 1 2 3 4 5 6 7 8 9 10 ...


str() 함수를 통해 해당 데이터의 구조를 살펴보면
모두 팩터 형태로 이루어져 있습니다.

따라서 다음과 같은 클랜징 작업이 필요합니다.

1. 두번째 열을 숫자 형태로 바꾸기
2. 첫번째 열을 날짜 형태로 변경. 행이름으로 지정 후 삭제
3. 전체 데이터를 xts 형태로 바꾸기


data[,2] = as.numeric(as.character(data[,2]))


먼저 첫번째 작업에 해당하는
두번째 열을 숫자 형태로 바꾸어 줍니다.

팩터 형태의 데이터를 바로 숫자로 바꾸게 되면 형태가 망가지므로
먼저 as.character()를 통해 문자열로 변경한 후,
as.numeric()을 통해 숫자 형태로 변경해 줍니다.


rownames(data) = ymd(data[,1]) %>% as.character
data[,1] = NULL


이번에는 두번째 작업에 해당하는
날짜 형태 변경, 행이름 지정 후 삭제를 해주도록 합니다.

먼저 ymd() 함수를 이용하여 yyyymmdd로 되어있는 데이터를
yyyy-mm-dd로 변경해준 후 문자열로 바꾸어줍니다.

그 후 rownames()를 통해 행이름으로 지정해준 후,
첫번째 열은 NULL을 통해 삭제해 줍니다.


data = as.xts(data)

마지막으로 as.xts() 함수를 통해 해당 데이터를 시계열로 바꾸어 줍니다.


plot(data)



이처럼 네이버 금융을 잘만 이용한다면,
긴 기간 동안의 수정주가를 매우 빠르게 받을 수도 있습니다.

댓글 13개:

  1. data = do.call(rbind, data)

    이 부분에서 오류가 발생하는데,
    혹시 해결방법을 알수 있을까요??

    Error in rbind(deparse.level, ...) :
    numbers of columns of arguments do not match

    답글삭제
  2. 하..1번으로 했었는데...감사합니다 ㅜㅜ 2번을 찾을수가없었는데.. 크롬으로보다보니 차트를 신경안썼었네요

    답글삭제
    답글
    1. 도움이 되셨다니 기분이 좋군요

      삭제
  3. 큰 도움이 되는 내용이었습니다.
    파이썬으로 크롤링하는 것보다 더 간명하게 느껴지고
    바로 Data 분석을 할 수 있어서 더 편리하네요.

    감사합니다.

    답글삭제
  4. 같은 방법으로 크롤링하던중 LG유플러스종목에서는 다음과 같은 오류를 띄우는데 인코딩을 한국어로 바꾸어도 안되는군요... 방법이 있을까요?
    Error in doc_parse_raw(x, encoding = encoding, base_url = base_url, as_html = as_html, :
    Input is not proper UTF-8, indicate encoding !
    Bytes: 0xC0 0xAF 0xC7 0xC3 [9]

    답글삭제
    답글
    1. 댓글 작성자입니다.
      해결되어 답글답니다.
      read_html()함수에서 encoding='EUC-KR'로 설정을 해주어야 뻑나는 자료가 안생깁니다 !

      삭제
  5. 아 하루종일 Python으로 시도하다가 실패를 했는데, R로는 한방에 에러없이 원하는 데이타를 얻었네요. 너무 쉽고, 명쾌한 해설 감사드립니다. 저자님 덕분에 오늘 코딩실력이 한단계 업글됐어요.

    답글삭제
  6. 안녕하세요. 제 블로그에 해당 게시물을 링크하고 데이터 가져오는 부분 참고하겠습니다.! 혹시 불쾌하시다면 말씀주세요!

    답글삭제
  7. 안녕하세요, Chapter5에 개별종목 지표크롤링을 따라하는데, read_csv()에서
    Error in doc_parse_raw(x, encoding = encoding, base_url = base_url, as_html = as_html, :
    Failed to parse text
    와 같이 encoding에 문제가 있는것 같습니다.
    'EUC-KR' 이나, 'CP949'로 해봤는데 여전히 해결이 안되네요.
    혹시, 뭐가 잘못됐는지 좀 봐주실수 있나요?
    아래에 복사해서 옮깁니다.
    > down_url= 'http://file.krx.co.kr/download.jspx'
    > down_ind= POST(down_url, query= list(code=otp),add_headers(referer= gen_otp_url,encoding='EUC-KR')) %>%
    + read_html(excoding='EUC-KR') %>%
    + html_text() %>%
    + read_csv()

    답글삭제