나만의, 나에 의한, 나를 위한 웹사이트!

gonudayo·2021년 7월 7일
0
post-thumbnail
post-custom-banner

"목표를 시각화하는 것이다. 지금껏 공책에 계획을 세우고 진행과정을 적었다. 하지만, 자주 확인하는 것도 아니고, 눈에 잘 들어오는 것도 아니었다. 그래서 개인 웹사이트를 만들어, 목표를 개시해두고 진행과정을 표시해둘 수 있으면 좋겠다고 생각했다."

1. Preview

1) 전체 코드

완성된 웹 사이트
바로가기

깃헙 리포지토리
Netlify
AWS

2) 웹사이트 구성

(1) 기능

  1. 주식 실시간 자산
  2. 코인 실시간 자산
  3. 백준 푼 문제 수 + 변동
  4. 깃허브 커밋 수 + 변동

(2) 사용 기술

  1. html/css/js (웹사이트 제작)
  2. python, node.js (AWS Lambda API 제작)

2. 웹사이트 배포

웹사이트 호스팅은 Netlify 를 이용하였다.

GitHub를 연결하면 바로 정적 웹사이트를 호스팅 해주는 서비스 이다.
쉽고 간편한데다가, 무료이다!
Netlify

3. JS/AWS Lambda

1) 실시간 코인 시세 가져오기

(1) 코인 시세 업비트 API 사용하기

  • 특정 코인의 시세를 실시간으로 받기 위해 업비트에서 제공하는 코인시세 API 를 활용한다.
  • 업비트 api 에서 json 형태로 받은 데이터를 ajax 로 파싱한다.
$(function() {
    $.ajax({
        url: "https://api.upbit.com/v1/ticker?markets=원화-코인",
        dataType: "json",
        success: function(data) {
            console.log(data[0].trade_price)
        }
    })
})

2) 실시간 주가 가져오기

코인 시세와 다르게 실시간 주가는 직접만든 API 라 그런지 2~3초의 딜레이가 발생한다.

(1) 주가를 파싱하는 AWS Lambda 함수

  • 주식 api는 간편히 이용할 수 있는 오픈API가 없어서 AWS Lambda 를 활용한다.
import json
import urllib.request
from bs4 import BeautifulSoup

def lambda_handler(event, context):
    url = "https://www.marketwatch.com/investing/stock/종목코드/"
    soup = BeautifulSoup(urllib.request.urlopen(url).read(), "html.parser")
    a_tags = soup.find_all('bg-quote',{"class":"value"})
    result_list = []
    for i in a_tags:
        result_list.append(i.get_text())
    return {
        'statusCode': 200,
        'body': json.dumps(result_list)
    }
    

BeautifulSoup

python 에서 html 데이터를 추출 하기 위해 BeautifulSoup 패키지를 사용한다.

from bs4 import BeautifulSoup
  1. 'python' 이름의 폴더 경로에서 pip3 명령어를 사용하여 bs4를 설치한다.
    pip install bs4 -t .
  2. 압축파일의 상위 폴더가 python 이 되도록 압축한다.
  3. 해당 zip 파일을 AWS Lambda Layer에 업로드 한다.

(3) 환율 시세 업비트 API

  • 위에서 달러로 받은 주가를 실시간 환율로 계산하기 위해 업비트에서 제공하는 환율 API 를 사용한다.
$(function() {
    $.ajax({
        url: "https://quotation-api-cdn.dunamu.com/v1/forex/recent?codes=FRX.KRWUSD",
        dataType: "json",
        success: function(data) {
            console.log(data[0].cashSellingPrice)
        }
    })
})

3) 백준 푼 문제 수 & 깃 허브 커밋 수의 변동량 가져오기

푼 문제 수를 가져오는 것은 위와 같은 방식을 사용한다면 쉽게 해결된다고 생각했다. 하지만, 1일 단위의 변동량을 계산하기 위해서는 데이터를 저장할 수 있는 공간이 필요했다. 전혀 접해보지 않았던 영역의 문제라 오랜 시간 헤매었다. 그러다가 NoSQL 방식의 AWS DynamoDB를 활용한다면 쉽게 해결할 수 있다는 사실을 알게 되었다.

(1) AWS DynamoDB에 데이터를 저장하는 AWS Lambda 함수 (WriteMessage)

const AWS = require('aws-sdk');
const axios = require('axios');
const cheerio = require('cheerio');
const url = "https://www.acmicpc.net/user/gonudayo";
const url2 = "https://github.com/gonudayo?tab=overview&from=";

let today = new Date();
let year = today.getFullYear();
let month = ("0" + (today.getMonth() + 1)).slice(-2);
let date = ("0" + today.getDate()).slice(-2);

let resultArr = [];
let problems = 0;
var string;
var no = 0;

const getBoj = async () => {
    try {
        return await axios.get(url);
    } catch (error) {}
};

//백준에서 총 푼 문제 수를 파싱한다.
getBoj().then(html => {

    const $ = cheerio.load(html.data);
    let parentTag = $("tbody tr");

    parentTag.each(function(i, elem) {
        let itemObj = $(this).find("td").text();
        resultArr.push(itemObj);
    });
    problems = resultArr[1];
});

const getGithub = async () => {
    try {
        return await axios.get(url2 + year + "-01-01");
    } catch (error) {}
};

//깃허브에서 총 커밋 수를 파밍한다.
getGithub().then(html => {

    const $ = cheerio.load(html.data);
    let parentTag = $("body div.js-yearly-contributions");

    parentTag.each(function(i, elem) {
        let itemObj = $(this).find("h2").text();
        string = itemObj;
    });
    no = (string.replace(/[^0-9]/g, '')).slice(0, -4);
});

const ddb = new AWS.DynamoDB.DocumentClient({
    region: 'ap-northeast-2'
});

exports.handler = async (event, context, callback) => {
    const requestId = context.awsRequestId;

    await getBoj().then(() => getGithub().then(() => createMessage(requestId).then(() => {
        callback(null, {
            statusCode: 201,
            body: '',
            headers: {
                'Access-Control-Allow-Origin': '*'
            }
        });
    }).catch((err) => {
        console.error(err);
    })));
};

function createMessage(requestId) {

    const params = {

        TableName: 'Message',
        Item: {
            'messageId': requestId,
            'timevalue': Number(year + month + date), // 1일 단위기에 date까지 저장한다.
            'countvalue': problems,
            'commitvalue': no
        }
    };
    return ddb.put(params).promise();
}

axios-cheerio

node.js 에서 html 데이터를 추출 하기 위해 axios, cheerio 모듈을 사용한다.

  1. axios : 이 모듈은 url통신을 위한 모듈이다.
    cheerio : axios로 받은 html 파일에서 데이터를 추출하기 위해 사용한다.
  2. node_modules 폴더를 만들어 이동한다.
  3. npm 명령어를 사용하여 axios-cheerio를 설치한다.
npm install axios -t .
npm install cheerio -t .
  1. node_modules 폴더를 최상위 폴더로 하는 zip 파일로 압축하여 AWS Lambda Layer에 업로드 한다.

Amazon EventBridge

수시로 실행하여 데이터베이스에 불필요한 공간을 차지하지 않기 위해, 당일 23시59에 함수가 자동으로 실행되게 설정한다.

  1. WriteMessage 함수의 트리거를 Amazon EventBridge 로 만든다.
  2. 예약 표현식 : cron(58,59 14 * * ? *) //서울 기준 23시 58분과 59에 한번씩 실행
  3. WriteMessage 함수를 처음(혹은 시간이 꽤 흐른 뒤) 실행하면 푼 문제 수 값이 반드시 '0' 으로 나온다. 뒤 이어 바로 실행하면 정상적인 값이 나오기 때문에 두번 실행하는 방법을 선택했다. (해당 문제의 원인을 안다면 제게 알려주십시오...)
    -> 자바스크립트 특성에 대한 이해도 부족했다. 비동기 처리를 통해 문제를 해결하였다.

(2) AWS DynamoDB에 데이터를 읽는 AWS Lambda 함수 (ReadMessage)

const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({
    region: "ap-northeast-2"
});
var table_name = "Message"; 
exports.handler = (event, context, callback) => {
    let today = new Date();
    let tdaysago= new Date(Date.parse(today) - 2 * 1000 * 60 * 60 * 24); //이틀전
    let startYear = tdaysago.getFullYear();
    let endYear = today.getFullYear();
    let startMonth = ("0" + (tdaysago.getMonth() + 1)).slice(-2);
    let endMonth = ("0" + (today.getMonth() + 1)).slice(-2);
    let startdate = ("0" + tdaysago.getDate()).slice(-2);
    let enddate = ("0" + (today.getDate() -1)).slice(-2);

    const params = {
        
        TableName: table_name,
        ProjectionExpression: "#timevalue, messageId, countvalue, commitvalue",
        FilterExpression: "#timevalue between :start and :end",
        ExpressionAttributeNames: {
            "#timevalue": "timevalue",
        },
        ExpressionAttributeValues: { //이틀 전부터 오늘 데이터까지 스캔
            ":start": Number(startYear + startMonth + startdate),
            ":end": Number(endYear + endMonth + enddate)
        }
    };

    ddb.scan(params, onScan);

    function onScan(err, data) {
        if (err) callback(null, err);
        else callback(null, data);
    }

};

참조 : AWS 개발자 가이드 : Node.js 및 DynamoDB 데이터 쿼리 및 스캔

(3) API에서 받은 값의 변동량을 계산하는 JS코드

$(function() {
    $.ajax({
        url: "API 주소",
        dataType: "json",
        success: function(data) {
            $.each(data, function(index, item) {
                function createArray(rows, columns) { //2차원 배열을 만든다.
                    var arr = new Array(rows);
                    for (var i = 0; i < rows; i++) {
                        arr[i] = new Array(columns);
                    }
                    return arr;
                }
                var arr = createArray(data.Count, 3); //arr[data.Count][3]
                var j = 0;
                for (var i = 0; i < data.Count; i++) {
                    if (item[i].countvalue != 0&&item[i].commitvalue != 0) {
                        arr[j][0] = item[i].timevalue;
                        arr[j][1] = item[i].countvalue;
						arr[j][2] = item[i].commitvalue;
                        j++;
                    }
                }
                arr.sort(function(a, b) { //날짜 내림차순 정렬
                    return b[0] - a[0];
                });
              if(Math.floor(arr[0][0]/10000)!=Math.floor(arr[1][0]/10000)) arr[1][2]=0;
              //깃허브 총 커밋 수는 연도별로 수집하기에 새해가 되면 초기화
              var baekjoon_rate = arr[0][1] - arr[1][1];
              var github_commit = arr[0][2] - arr[1][2];
              
              document.getElementById('baekjoon').innerHTML = arr[0][1] + '(+' + baekjoon_rate + ')';
              document.getElementById('github').innerHTML = arr[0][2] + '(+' + github_commit + ')';
              
              if (baekjoon_rate == 0) document.getElementById('baekjoon').style.color = "#FF0000";
              else document.getElementById('baekjoon').style.color = "#01DF01";
              if (github_commit == 0) document.getElementById('github').style.color = "#FF0000";
              else document.getElementById('github').style.color = "#01DF01";
              //변동 값이 0 이면 적색, 1 이상이면 녹색으로 나타나도록 한다.
            })
        }
    })
})

4. 마치며

넓게 본다면 개발자는 그저 완벽한 코딩만 해서 끝나는 게 아니다. 고객이 원하는 바(혹은 문제를 해결)를 이룰 수 있도록 비즈니스를 하는 것이다. 그 수단 중 하나가 프로그래밍인 것이다.

나는 가장 가까이 있는 고객인 나의 요구를 해결하는 일을 해보았다.
다음에는 내 친구를 위한 서비스를 제공하는 웹사이트를 만들어보겠다.
서비스 범위를 점차 늘리며, 동시에 실력도 늘린다.

profile
초신성 백엔드 개발자
post-custom-banner

0개의 댓글