Post List

2019년 7월 21일 일요일

한경컨센서스에서 상장이후 주가 크롤링 하기 (정규표현식 사용)


한경컨센서스(http://hkconsensus.hankyung.com)의 기업 레포트에 새로운 기능이 생겼습니다. 리포트 첨부파일 외에 기업정보와 차트를 볼 수 있는 항목이 추가되었습니다.

기업정보를 클릭하시면 각종 재무 데이터가 보이며(해당 페이지도 크롤링 가능합니다.), 차트를 클릭하면 주가데이터가 차트로 표현됩니다.


그래프를 보면 highchart로 구현되며, 하단의 셀렉션 부분을 보면 상장이후부터 모든 기간을 선택할 수 있습니다. 따라서 상장이후 모든 주가를 따올 수 있는 굉장히 좋은 먹잇감이 됩니다. 이제 이 데이터를 어디서 물고 오는지 개발자도구 화면을 통해 찾아보도록 하겠습니다.




개발자도구 화면을 연 상태에서 차트 탭을 선택하면 나오는 항목들 중, 1) chart.chartList와 2) chart.report 항목이 있습니다. 각각을 클릭하여 링크를 들어가보면 알겠지만 1)번은 주가데이터를 불러오는, 2)번은 이벤트 데이터를 불러오는 url입니다.

1)번 url에 접속해보도록 하겠습니다.

http://hkconsensus.hankyung.com/apps.chart/chart.chartList?callback=jQuery112406638263512384042_1563692725032&report_type=CO&business_code=105560&_=1563692725033


[Date.UTC(연도, 월, 날짜), 주가] 형태로 굉장히 퍼가기 쉽게 구성되어 있습니다. 정규표현식을 조금만 활용하면 굉장히 쉽게 날짜와 주가부분만 추출할 수 있을거 같은 기분입니다.

library(httr)
library(rvest)
library(stringr)
library(lubridate)
library(dplyr)
library(timetk)

url = 'http://hkconsensus.hankyung.com/apps.chart/chart.chartList?callback=jQuery112406638263512384042_1563692725032&report_type=CO&business_code=105560&_=1563692725033'
data = GET(url)

먼저 필요한 패키지들을 불러옵니다. 없는 패키지는 깔아주세요. 그 후, GET() 함수를 통해 해당 url 정보를 받아옵니다.

data_parse = read_html(data) %>%
  html_text() 

head(data_parse)

[1] "jQuery112406638263512384042_1563692725032([[Date.UTC(2008,9,11),47000],\n[Date.UTC(2008,9,14),50700],\n[Date.UTC(2008,9,15),53100],\n[Date.UTC(2008,9,16),51000],\n[Date.UTC(2008,9,17),43400],\n[Date.UTC(2008,9,18),38000],\n[Date.UTC(2008,9,21),39050],\n[Date.UTC(2008,9,22),39200],\n[Date.UTC(2008,9,23),38500],\n[Date.UTC(2008,9,24),36800],\n[Date.UTC(2008,9,25),36800],\n[Date.UTC(2008,9,28),40000],\n[Date.UTC(2008,9,29),37600],\n[Date.UTC(2008,9,30),32000],\n[Date.UTC(2008,9,31),35000],\n[Date.UTC(2008,9,32),32000],\n[Date.UTC(2008,10,4),33050],\n[Date.UTC(2008,10,5),35800],\n

read_html() 함수로 html 정보를 받아온 후, html_text() 함수를 통해 텍스트 부분만을 읽어오면 웹페이지에 있는 내용을 그대로 읽어오게 됩니다. 이 중 우리가 필요한 것은 (yyyy, m, dd), price 에 해당하는 부분입니다. 정규표현식을 이용해 해당 부분만을 읽어오도록 하겠습니다.

data_parse = data_parse %>%
  str_match_all('\\([0-9]+,[0-9]+,[0-9]+\\),[0-9]+') %>%
  unlist() 

head(data_parse)

[1] "(2008,9,11),47000" "(2008,9,14),50700" "(2008,9,15),53100" "(2008,9,16),51000"
[5] "(2008,9,17),43400" "(2008,9,18),38000"

먼저 괄호에 해당하는 (와 ) 부분은 이스케이프(\\)를 이용하여 고유 텍스트로 지정해줍니다. 이를 통해 (숫자, 숫자, 숫자),숫자) 형식으로 이루어진 모든 데이터를 읽었으며 이는 (yyyy,mm,dd), price를 나타냅니다. 이 중 괄호는 필요가 없으므로 다시한번 정규표현식을 이용해 숫자 부분만을 추출하도록 하겠습니다.


data_parse = data_parse %>% str_match_all("[0-9]+")
head(data_parse)

[[1]]
     [,1]   
[1,] "2008" 
[2,] "9"    
[3,] "11"   
[4,] "47000"

[[2]]
     [,1]   
[1,] "2008" 
[2,] "9"    
[3,] "14"   
[4,] "50700"

[[3]]
     [,1]   
[1,] "2008" 
[2,] "9"    
[3,] "15"   
[4,] "53100"

숫자에 해당하는 부분만 추출한 결과, 각 리스트에는 연도, 월, 날짜, 주가가 들어가게 되었습니다. 이제 해당 정보를 테이블로 가공해주도록 합니다.

data_divide = do.call(cbind, data_parse) %>% t()

unique(data_divide[,2])
 [1] "9"  "10" "11" "0"  "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8" 

unique(data_divide[,3])
 [1] "11" "14" "15" "16" "17" "18" "21" "22" "23" "24" "25" "28" "29" "30" "31" "32" "4"  "5"  "6" 
[20] "7"  "8"  "12" "13" "19" "20" "26" "27" "2"  "3"  "9"  "10"

cbind()를 이용해 연도-월-날짜-주가 끼리 열로 묶어준 후, t()를 이용해 아래로 길게 변형해줍니다.

그 후 월에 해당하는 데이터를 분석해보면 0부터 11까지만 숫자가 존재하며, 일에 해당하는 데이터를 분석해보면 2부터 32까지 숫자가 존재합니다. 각각 날짜를 실제 주가와 비교해보면 월에 해당하는 숫자는 하나씩 더해주어야 하며, 일에 해당하는 숫자는 하나씩 빼주어야 합니다. (자바스크립트가 그렇게 인식하나 보죠?)

data_divide[,2] = as.numeric(data_divide[,2]) + 1
data_divide[,3] = as.numeric(data_divide[,3]) - 1

data_ymd = str_c(data_divide[,1], data_divide[,2], data_divide[,3], sep = '-') %>% ymd()
data_price = data_divide[, 4] %>% as.numeric()

월에 해당하는 2번째 열은 numeric으로 바꿔준 후 값을 하나씩 더해주며, 일에 해당하는 3번째 열은 numeric으로 바꿔준 후 값을 하나씩 빼줍니다. 그 후, str_c() 함수를 이용해 연-월-일을 합쳐주며, ymd()를 통해 Date 형태로 변형해줍니다. 주가에 해당하는 4번째 열도 numeric 형태로 변형해 줍니다.

data_final = data.frame(data_ymd, data_price) %>%
  tk_xts(., date_var = data_ymd)

head(data_final)
           data_price
2008-10-10      47000
2008-10-13      50700
2008-10-14      53100
2008-10-15      51000
2008-10-16      43400
2008-10-17      38000

마지막으로 날짜에 해당하는 data_ymd와 주가에 해당하는 data_price를 묶어준 후, tk_xts() 함수를 통해 xts 형태로 변형해줍니다. 최종 결과를 확인해보면 깔끔하게 인덱스는 날짜, 데이터는 주가로 정리되어 있습니다.

다음으로 이를 함수로 만들어 보도록 하겠습니다.

get_price = function(ticker) {
  
  url = paste0('http://hkconsensus.hankyung.com/apps.chart/chart.chartList?callback=jQuery112409375031790630459_1563253655447&report_type=CO&business_code=',ticker,'&_=1563253655448')
  data = GET(url)
  
  data_parse = read_html(data) %>%
    html_text() %>%
    str_match_all('\\([0-9]+,[0-9]+,[0-9]+\\),[0-9]+') %>%
    unlist() 
  
  data_parse = data_parse %>% str_match_all("[0-9]+")
  head(data_parse)
  
  data_divide = do.call(cbind, data_parse) %>% t()
  data_divide[,2] = as.numeric(data_divide[,2]) + 1
  data_divide[,3] = as.numeric(data_divide[,3]) - 1
  
  data_ymd = str_c(data_divide[,1], data_divide[,2], data_divide[,3], sep = '-') %>% ymd()
  data_price = data_divide[, 4] %>% as.numeric()
  
  data_final = data.frame(data_ymd, data_price) %>%
    tk_xts(., date_var = data_ymd)
}


인자로 티커를 입력하면 이에 해당하는 상장 이후 주가를 모두 가져오게 했습니다.

price = get_price('005930')
plot(price)



삼성전자의 상장이후 주가를 매우 빠르게 불러옵니다. 중간에 주가가 훅 떨어지는걸 봐서는 수정주가를 반영하지는 않는 듯 합니다.


애널리스트 레포트에서 다룰 리가 없는 초소형주 주가도 한번 받아보도록 하겠습니다. (에스마크: 030270)

price = get_price('030270')
plot(price)



잘 받아 집니다....

댓글 4개:

  1. 안녕하세요 궁금한것이 있어 글을 남깁니다...
    R을 이용하신것 같은데...혹시...구글스프레드시트를 활용하는 방법은 없을까요?

    답글삭제
  2. In tk_xts_.data.frame(data = data, select = select, date_var = date_var, :
    Non-numeric columns being dropped: data_ymd

    데이터는 나오긴하는데...위와같이 에러가 나네요 혹시 뭐가 문제일까요?

    답글삭제
  3. 또한 엑셀이나 CSV로 내보내려하는데...
    여러개의 종목코드에 대한 데이터를 하나의 엑셀파일로 저장할수 있나요?

    답글삭제
  4. 혹시 중국 재무비율 데이터도 받을 수 있나요?

    답글삭제