배경

CNN 사이트에서 제공하고 있는 Fear and Greed Index는 주식시장의 심리상태를 보여주는 대표적인 지수라고 할 수 있습니다.

Fear and Greed Index - Investor Sentiment | CNN
CNN’s Fear & Greed Index is a way to gauge stock market movements and whether stocks are fairly priced. The index uses seven market indicators to help answer the question: What emotion is driving the market now?

MARKET MOMENTUM, STOCK PRICE STRENGTH, STOCK PRICE BREADTH, PUT AND CALL OPTIONS, MARKET VOLATILITY, SAFE HAVEN DEMAND, JUNK BOND DEMAND 각각의 항목을 나타내는 지수를 산출하여 종합한 인덱스로 보시면 되겠는데요, 각 항목에 대한 내용은 추후 살펴보는 것으로 하고 이번 포스팅에서는 Fear-and-Greed Index의 과거 데이터를 크롤링하여 파일로 저장하는 방법에 대해 설명하려고 합니다.

사이트 상단에 보이는 Fear-Greed Index의 시계열 데이터를 크롤링해보려고 합니다.

사전연구

Interactive Chart의 데이터를 크롤링 하기

웹페이지상의 차트 데이터를 가져오는 방법은 Medium posting을 참고하였고, Chrome Devtool을 활용하여 차트 데이터를 json으로 제공하고 있는 url endpoint를 찾아내었습니다.

링크를 클릭해 보면 "fear_and_greed_historical" 키 값에 원하는 시계열 데이터가 들어가 있는 것을 확인해 볼수 있습니다.

💡
url 내의 일자(yyyy-mm-dd)를 설정하여 해당 기준일 이후 최근일까지의 일간 index 데이터 조회도 가능하니 참고해주세요~
https://production.dataviz.cnn.io/index/fearandgreed/graphdata/2023-02-10

너무 간단하게 성공한 것이 아닐까?

사실 이렇게 간단한 방법으로 규격화된 json 데이터를 추출할 수 있엇다면 따로 포스팅을 남기지는 않았을것 같은데요😋
문제는 바로 브라우저가 아닌 프로그래밍 코드로 위 데이터를 가져오려고 할 때, 서버 정책으로 요청이 Blocked 당하는 것이었습니다.
이럴때 바로 유용한 방법이 제가 예전에 포스팅한 Headless Browser 를 사용하는 것입니다!

Javascript - puppeteer 라이브러리를 이용해 해외 ETF 정보 크롤링 하기(Vanguard 예제)
Headless browser 기능을 사용할 수 있는 javascript puppeteer 라이브러리를 이용하여 웹페이지 데이터를 크롤링해 봅니다.

코딩하기

다행히 위 포스트에서 기본 프로젝트 셋업 방법을 설명하여 놓았으니 참고하여 주시고, 하단과 같은 기본적인 코드베이스에서 시작해 보도록 하겠습니다.

const puppeteer = require("puppeteer");

const run = async () => {
  try {
    const browser = await puppeteer.launch({
      args: ["--no-sandbox"],
    });
    const page = await browser.newPage();

  } catch (e) {
    console.log(e);
  }
};

run();
index.js

이제 우리가 추출을 원하는 page url로 이동하고 적절한 selector 값을 선정하여 데이터 부분을 잘 오려(?)내면 되는데요,
이번의 경우에는 <pre></pre> 태그내에 json 데이터가 잘 감싸져 있기 때문에 selector 값을 'pre'로 선정하였습니다~

const puppeteer = require("puppeteer");
const fs = require("fs");

const run = async () => {
  try {
    const browser = await puppeteer.launch({
      args: ["--no-sandbox"],
    });
    const page = await browser.newPage();
    await page.goto('https://production.dataviz.cnn.io/index/fearandgreed/graphdata');
    const selector = 'pre';
    await page.waitForSelector(selector);

    let data = await page.$eval(selector, (element) => element.textContent);
    let data_json = JSON.parse(data);
    
    console.log(data_json);
    browser.close();   
   
  } catch (e) {
    console.log(e);
  }
};

run();
index.js

node index.js 를 통해 json 데이터가 잘 찍히는 것을 확인할 수 있습니다.


node index.js


(out){
(out)  fear_and_greed: {
(out)    score: 70.1142857142857,
(out)    rating: 'greed',
(out)    timestamp: '2023-02-10T23:44:47+00:00',
(out)    previous_close: 72.1714285714286,
(out)    previous_1_week: 76.2571428571429,
(out)    previous_1_month: 51.8285714285714,
(out)    previous_1_year: 33.6
(out)  },
(out)  fear_and_greed_historical: {
(out)    timestamp: 1676072687000,
(out)    score: 70.1142857142857,
(out)    rating: 'greed',
(out)    data: [
(out)      [Object], [Object], [Object], [Object], [Object], [Object],
(out) ...

데이터 전처리

이제 아래와 같은 데이터전처리 기능을 추가하고 csv 파일형태로 저장하는 코드까지 완성하여 봅니다.

  • index의 timestamp 값을 'yyyy-mm-dd'와 같은 형태로 변환하기
  • Array of object 데이터 형식을 csv string 형식으로 바꾸기
const puppeteer = require("puppeteer");
const fs = require("fs");

const run = async () => {
  try {
    const browser = await puppeteer.launch({
      args: ["--no-sandbox"],
    });
    const page = await browser.newPage();
    await page.goto('https://production.dataviz.cnn.io/index/fearandgreed/graphdata');
    const selector = 'pre';
    await page.waitForSelector(selector);

    let data = await page.$eval(selector, (element) => element.textContent);
    let data_json = JSON.parse(data);

    let data_array = data_json.fear_and_greed_historical.data

    browser.close();
    const csv_data = [
    [
      "datetime",
      "index_value",
      "rating"
    ],
    ...data_array.map(item => [
      timestamptodatestr(item.x),
      item.y,
      item.rating
    ])
  ]
   .map(e => e.join(","))
   .join("\n");
  
   console.log(csv_data);
   fs.appendFileSync("./index_data.csv", csv_data);
  } catch (e) {
    console.log(e);
  }
};

const timestamptodatestr = (ts) => {
    const d = new Date(ts);
    let datestr = d .toISOString().slice(0,10);
    return datestr;
}
run();
index.js

아래와 같이 데이터가 console창에 잘 출력되고 index_data.csv 파일로 저장된 것을 확인할 수 있습니다~!


node index.js


(out)datetime,index_value,rating
(out)2022-02-11,28.48571428571429,fear
(out)2022-02-14,25.685714285714287,fear
(out)2022-02-15,29.97142857142857,fear
(out)2022-02-16,32.08571428571428,fear
(out)...

정리

서버측에서 브라우저 이외의 요청을 막은 경우, Headless Browser 활용을 통해 데이터를 가져오는 유용한 예제였던 것 같습니다. (비이상적인 많은 요청은 서버측 로직에서 추가적인 패널티를 받을 수 있음을 주의해야 할 것 같네요~)
다음 포스팅은 여기서 추출한 시계열 데이터를 활용하여 간단한 백테스팅을 Google Sheet에서 진행해 보려고 합니다~ 많은 관심 부탁드립니다😊