'잡다구리 정보' 카테고리의 다른 글

노트북 패널 액정 교체  (0) 2020.09.13
맥 Parallels ENG 언어 삭제하기  (0) 2020.08.25
Win PE 포맷  (0) 2020.07.30

스프링

홈컨트롤러에서 DB 조차 객체 클래스로 옮겨서 일반화 하는 것을 했다.

 

노드

서버 라우터와의 통신 없이 자바스크립트를 이용해 클라이언트단 내에서 데이터를 수집해 그래프를 그리는 것, 그리고 지난주에 했던 엑셀 파일에 문제가 있어서 수정을 마무리 했다.

 

알고리즘

크러스컬에 이어 프림 정리하고... 정렬은 대충만 봐서 다시 정리해야겠다.

 

그리고 본의 아니게 노트북이 업데이트 후 망가져서 이틀 날리고 ㅜㅜ

 

주말에 정리할 것...

1. 문제원형 전자정부 인수인계문서 보고 따라하기...

2. 알고리즘 짝 맞추기, 수열, bfsdfs, 조합순열

3. 알고리즘 이진트리, 펜윅트리, 알파베타 가지치기

4. 크러스컬, 프림 흐름 도식화, 다익스트라

5. 정렬, KMP, BoyerMoore, Heap

6. DP

음... 시간이 되려나 이거 다 하는게...? 아무튼... 주말에 최대한 다 하고..... 다음주는 음.... 스프링 퀵 스타트 책을 일주일 안에 다 봐보자...

'개발자 > TIL' 카테고리의 다른 글

TIL 20.10.05 ~ 20.10.15  (0) 2020.10.15
TIL 20.09.28 ~ 20.10.04  (0) 2020.10.04
TIL 20.09.14 ~ 20.09.20  (0) 2020.09.20
TIL 20.09.08 ~ 20.09.13  (0) 2020.09.13
TIL 20.09.04 ~ 20.09.07  (0) 2020.09.08

역할

파이썬 팀장 : 팀을 자바와 파이썬으로 나누어 프로젝트에 착수. 그 중 파이썬 팀장을 맡음.

개발 역할 : 헬스 데이터 ETL, 파이썬 인수인계 문서 작성

 

힘들었던 것

통일되지 않은 데이터, 장기간 데이터를 쌓으며 계속해서 변칙적인 데이터 처리를 누적하며 로직이 매우 길어져 가독성 및 디버깅이 어려워짐 > 코드를 기능별로 모듈화 시키고 그것들을 함수화 함으로써 극복.

 

배운 것

인수인계 문서의 중요성 및 유용함.

 

코드

github.com/sbpark88/Java_Study/tree/master/2조_헬스케어

 

sbpark88/Java_Study

Contribute to sbpark88/Java_Study development by creating an account on GitHub.

github.com

인수인계 문서

문제원형실습 파이썬 인수인계.pdf
3.79MB

 

PPT 일부 발췌

프로젝트 기간 : 20.06.16 ~ 20.06.23

역할

반 내에서 각 팀로 전체 CRUD 중 업무 분담. CSS, 학생, 선생님으로 나뉘었고 우리팀은 선생님 데이터를 CRUD 로 만드는 작업을 진행.

개발 역할 : Update 구현, 비밀번호 SHA-256 암호화 처리, 각 페이지 및 업데이트 액션에서의 세션 처리.

 

힘들었던 것

단순 페이지의 로그인 여부를 확인하는 세션 처리는 쉬웠지만 '수정'이라던가 '탈퇴' 등의 각각의 액션별 세션처리를 하는 것이 힘들었다. > 지속적인 디버깅으로 극복.

 

배운 것

각 팀별로 코드를 진행하기 이전 테이블정의를 하고, 변수명, 코드 작성 규칙 등의 세부 지침이 없으니 통합이 쉽지 않았다. 반드시 개발 이전 개발의 방향을 회의를 통해 정의를 하고 시작해야 함을 배웠다.

 

코드

github.com/sbpark88/Java_Study/tree/master/TeamProject%20ver%202.0.0%20delete까지%20다%20됨

 

sbpark88/Java_Study

Contribute to sbpark88/Java_Study development by creating an account on GitHub.

github.com

 

결과 일부

 

 

 

역할

발표 : 해당 프로젝트의 최종 발표를 진행.

개발 역할 : 원본 코드에서 일부 불필요하거나 잘못된 코드 수정 및 삭제, Enum 정리(문서화).

 

힘들었던 것

처음 해보는 디버깅이었고, 클래스와 함수가 많이 사용된 것을 처음 접해 코드를 이해하는데 힘들었다 > 코드의 전체 로드맵을 시각화 함으로써 극복.

블록체인을 이해한 다음 활용할 수 있는 방안에 대한 답을 얻지 못 함.(상업성 실패)

 

배운 것

디버깅 툴의 사용.

블록체인의 작동 이해.

 

코드

github.com/sbpark88/Python_Study/tree/master/lecture/TeamToyProject%202%20Blockchain

 

sbpark88/Python_Study

Contribute to sbpark88/Python_Study development by creating an account on GitHub.

github.com

Enum정리 문서

Enum.pdf
0.43MB

 

PPT 일부 발췌

코드의 전체 흐름을 시각화

 

 

 

 

 

 

 

 

 

역할

프로젝트 팀장 : 해당 프로젝트의 팀장을 맡아 각 팀원별 잘하는 능력 파악과 업무 분배, 각자의 역할만 집중할 수 있도록 협업 관리.

개발 역할 : 다나와 사이트 크롤링, 코드 리팩토링, CentOS 서버 구축

 

힘들었던 것

깃 활용이 익숙치 않아 코드 관리와 공유에 어려움 > 주어진 프로젝트 기간이 있어 공유폴더와 공유 엑셀 시트를 활용하여 극복.

크롤링클릭을 방해하기 위한 랜덤 광고 > 클릭 대신 Keys.ENTER 를 넘겨 광고를 극복.

 

배운 것

코드의 길이가 길어짐에 따라, 변수 이름의 중요성, 코드를 각 역할별로 쪼개 모듈화, 함수화 하는 것의 중요성을 배웠다.

협업에 있어 의사소통의 중요성을 배웠다.

 

코드

github.com/sbpark88/Python_Study/tree/master/lecture/TeamToyProject%201%20Web%20Crawling

 

sbpark88/Python_Study

Contribute to sbpark88/Python_Study development by creating an account on GitHub.

github.com

 

PPT 일부 발췌

당시 DB 활용을 배우기 데이터의 주기, 용량은 제외

깃에 익숙치 않아 당시 공유폴더와 엑셀시트를 이용한 코드 버전 관리를 진행

광고 클릭 피하기

문자열을 타이틀로 지정하고, 기능 보유 유무를 O로 표기

크롤링

크롤링을 위해 cheerio라는 것을 사용한다.

github.com/cheeriojs/cheerio

 

cheeriojs/cheerio

Fast, flexible, and lean implementation of core jQuery designed specifically for the server. - cheeriojs/cheerio

github.com

var request = require('request');
var cheerio = require('cheerio');

app.get('/getMenu', function (req, res) {
  request("http://www.kopo.ac.kr/kangseo/content.do?menu=262", function(err, res2, body) {
    const $ = cheerio.load(body);
    var menus = [];

    for(var i = 0; i < 5; i++) {
      menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
    }
    // console.log(menus);

    res.send(menus);
  })
})

// const를 사용해 URL 요청을 option으로 따로 분리할 수 있다.
app.get('/getMenu', function (req, res) {
  const options = {
    url:'http://www.kopo.ac.kr/kangseo/content.do?menu=262',
    method: 'GET'
  }
  request.get(options, function(err,httpResponse,body){
    const $ = cheerio.load(body);
    var menus = [];

    for(var i = 0; i < 5; i++) {
      menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
    }

    res.send(menus);
  });
})

따라서

app.js

var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app).listen(80);
console.log("Server is running...")

var mysql = require('mysql');
var request = require('request');
var cheerio = require('cheerio');
const ExcelJS = require('exceljs');

var bodyParser = require("body-parser");
// extended : 객체 안의 객체를 파싱 가능하게 하려면 true로 해야한다.
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json({limit: '50mb'}));

app.get('/', function (req, res) {
  res.sendfile("main.html");
});
app.get('/main', function (req, res) {
  res.sendfile("main.html");
});

app.get('/getMenu', function (req, res) {
  const options = {
    url:'http://www.kopo.ac.kr/kangseo/content.do?menu=262',
    method: 'GET'
  }
  request.get(options, function(err,httpResponse,body){
    const $ = cheerio.load(body);
    var menus = [];

    for(var i = 0; i < 5; i++) {
      menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
    }

    res.send(menus);
  });
})

 

main.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <span id="menuArea"></span>
    <br />
    <input type="button" id="drawGraph" value="그래프그리기">
    <input type="button" id="downloadExcel" value="엑셀다운로드">
    <div id="graph" style="width: 800px; height: 500px;"></div>
  </body>
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  <script type="text/javascript">
    var week = ['월','화','수','목','금'];
    var menus;

    $.ajax({
      url : '/getMenu',
      type : 'GET',
      data : {

      },
      success : function (data) {
        menus = data;  // 바깥 배열이 요일별로 분리, 안쪽 배열이 메뉴별로 분리된 매트릭스
        for(var i = 0; i < menus.length; i++) {
          // 바깥 배열 길이만큼 돌며 요일을 출력
          $("#menuArea").append(week[i] + "요일<br>");
          // console.log(week[i] + "요일<br>");

          // 셀렉트 박스를 만들기 위해 appendingText에 한 번에 담아서 출력하기
          for(var j = 0; j < menus[i].length; j++) {
            var appendingText = "";
            // i 요일의 j 번째 메뉴를 출력
            // appendingText += menus[i][j] + `<select id = "s${i}_${j}"`;
            appendingText += menus[i][j] + `<select name="s${i}">`;

            // select 의 수량을 10개 만든다.
            for (var k = 0; k < 11; k++) {
              appendingText += `<option value = ${k}>${k}</option>`;
            }
            appendingText += `<select><br>`;
            $("#menuArea").append(appendingText);
            // console.log(appendingText);
          }
          $("#menuArea").append("<br>");
        }
      }
    });

    // 그래프 그리기
    $("#drawGraph").click(function() {
    
    });

    // 엑셀 다운로드
    $("#downloadExcel").click(function() {
    
    });
  </script>
</html>

 

그래프

이미 html에 가져온 데이터만을 이용해 자바스크립트로 그려낼 것이기 때문에 main.html 안에서 해결한다.

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>

    <div id="graph" style="width: 800px; height: 500px;"></div>
  </body>
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  <script type="text/javascript">
    var week = ['월','화','수','목','금'];
    var menus;

    // 그래프 그리기
    $("#drawGraph").click(function() {
      var averageArr = [];
      console.log($("#count").val());

      // 월 ~ 금 반복문
      for (var i = 0; i < menus.length; i++) {
        var dailyAverage = 0;
        // 각 요일별 메뉴의 반복문
        var dailyScore = $(`select[name=s${i}]`);
        for (var j = 0; j < dailyScore.length; j++) {
          // 각 메뉴별 1 ~ 10 점 중 어떤 것이 선택되었는지를 확인
          for (var k = 0; k < 11; k++) {
            if (dailyScore[j][k].selected) {
              dailyAverage +=k; // 각 요일별 메뉴들의 점수의 총 합
            }
          }
        }
        dailyAverage = dailyAverage / dailyScore.length // 각 요일별 메뉴의 점수들의 총 합을 메뉴의 개수로 나눠 평균을 계산
        averageArr.push([week[i], dailyAverage]);
        console.log(averageArr);
      }

      google.charts.load('current', {'packages':['corechart', 'line']});
      google.charts.setOnLoadCallback(drawCurveTypes);

      function drawCurveTypes() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', '요일');
        data.addColumn('number', '점수');

        data.addRows(averageArr);

        var options = {
          hAxis: {
            title: '요일'
          },
          vAxis: {
            title: '만족도'
          },
          series: {
            1: {curveType: 'function'}
          }
        };

        var chart = new google.visualization.LineChart(document.getElementById('graph'));
        chart.draw(data, options);
      }
    });

    // 엑셀 다운로드
    $("#downloadExcel").click(function() {

    });
  </script>
</html>

 

 

엑셀

엑셀로 만들어주기 위해 exceljs를 사용한다.

app.js (엑셀 파일을 만드는 라우터)

const ExcelJS = require('exceljs');

// ajax로 데이터 넘겨받아 엑셀형식으로 만들기
app.post('/getExcel', async function (req, res) {
  var menus = req.body.menus;
  var scores = req.body.scores;

  const workbook = new ExcelJS.Workbook();
  const sheet = workbook.addWorksheet('menu');

  const alpahbet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
  const dayOfTheWeek = ['월','화','수','목','금','토','일'];
  var menusMaxLength = 0;
  var roundedAverage = [];

  // 월, 화, 수, 목, 금을 도는 for문
  for (var i = 0; i < menus.length; i++) {
    sheet.getCell(`${alpahbet[i*2]}1`).value = dayOfTheWeek[i]; // 1행에 월, 화, 수, 목, 금
    scores[i] = scores[i].map( str => parseInt(str) );  // str to integer (점수 array 형변환)

    // 각 요일별 메뉴의 개수만큼 도는 for문 (각 요일의 메뉴와 메뉴별 점수를 엑셀에 삽입한다.)
    for (var j = 0; j < menus[i].length; j++) {
        sheet.getCell(`${alpahbet[i*2]}${j+2}`).value = menus[i][j].trim();
        sheet.getCell(`${alpahbet[i*2+1]}${j+2}`).value = scores[i][j];
    }
    // 각 요일별 메뉴의 평균 점수를 계산하고, 이를 배열에 담아둔다.
    // reduce 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
    // acc : 누적값, (,0 : 초기값), curr : 현재값... 그러니까 파이썬 식으로 표현하면 sum = sum + curr 이런거다.
    const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);
    const average = sum / menus[i].length;
    roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현

    // 메뉴의 길이 중 최대로 긴 값의 크기를 구해서 저장한다. (요일마다 메뉴의 길이가 다를 수 있으니까.)
    if (menus[i].length > menusMaxLength) menusMaxLength = menus[i].length;
  }

  // 요일마다 메뉴의 길이가 다른 경우가 있기 때문에 위에서 메뉴를 모두 채우는 동안 배열에 저장해둔 다음 한 번에 출력한다.
  for (var i = 0; i < menus.length; i++) {
  sheet.getCell(`${alpahbet[i*2]}${menusMaxLength+3}`).value = '평균';
  sheet.getCell(`${alpahbet[i*2+1]}${menusMaxLength+3}`).value = roundedAverage[i];  // 계산한 평균을 입력
  }

  // await : 파일이 다 만들어 질 때까지 기다려준다. (callback 함수와 같은 기능. 최근에 생겨난 함수다.)
  await workbook.xlsx.writeFile('menu.xlsx');
  // 엑셀 다 만들었다고 클라이언트에 응답을 보냄.
  res.send("ok");
});

/getExcel 라우터가 main.html에서 보낸 ajax request를 받아서 body에 실어 보낸 데이터를 분해, 엑셀 파일로 만든다.

exceljs를 사용하며, sheet.getCell();을 통해 각 셀에 데이터를 삽입한다.

주의 : 이 때 각 요일별 메뉴의 개수가 달라지는 주가 존재한다. 따라서 최대값을 구한 다음 마지막 줄 최종 평균은 위 for문이 끝난 다음 다른 for문을 통해 구현한다.

// 월, 화, 수, 목, 금을 도는 for문
for (var i = 0; i < menus.length; i++) {
  sheet.getCell(`${alpahbet[i*2]}1`).value = dayOfTheWeek[i]; // 1행에 월, 화, 수, 목, 금
  scores[i] = scores[i].map( str => parseInt(str) );  // str to integer (점수 array 형변환)

  // 각 요일별 메뉴의 개수만큼 도는 for문 (각 요일의 메뉴와 메뉴별 점수를 엑셀에 삽입한다.)
  for (var j = 0; j < menus[i].length; j++) {
      sheet.getCell(`${alpahbet[i*2]}${j+2}`).value = menus[i][j].trim();
      sheet.getCell(`${alpahbet[i*2+1]}${j+2}`).value = scores[i][j];
  }
  // 각 요일별 메뉴의 평균 점수를 계산하고, 이를 배열에 담아둔다.
  // reduce 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
  // acc : 누적값, (,0 : 초기값), curr : 현재값... 그러니까 파이썬 식으로 표현하면 sum = sum + curr 이런거다.
  const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);
  const average = sum / menus[i].length;
  roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현

  // 메뉴의 길이 중 최대로 긴 값의 크기를 구해서 저장한다. (요일마다 메뉴의 길이가 다를 수 있으니까.)
  if (menus[i].length > menusMaxLength) menusMaxLength = menus[i].length;
}

// 요일마다 메뉴의 길이가 다른 경우가 있기 때문에 위에서 메뉴를 모두 채우는 동안 배열에 저장해둔 다음 한 번에 출력한다.
for (var i = 0; i < menus.length; i++) {
sheet.getCell(`${alpahbet[i*2]}${menusMaxLength+3}`).value = '평균';
sheet.getCell(`${alpahbet[i*2+1]}${menusMaxLength+3}`).value = roundedAverage[i];  // 계산한 평균을 입력
}

reduce 함수

developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

 

Array.prototype.reduce()

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

developer.mozilla.org

sum 함수가 없어서 reduce를 사용해 배열의 합을 구한 것에 주목한다.

const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);

 

마찬가지로 반올림의 자리수를 따로 지정할 수 없어 round함수를 사용하기 전, 10을 곱하고 다시 10으로 나누는 것에 주목한다. 만약 소수점 둘째 자리까지 남기고 싶다면 100을 곱한 다음 100으로 나누면 된다.

roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현

 

await

마지막으로 콜백함수와 동일한 기능을 하지만, 기존 콜백함수 구현보다 훨씬 짧고 간결한 await가 있다. 단, 나온지 얼마 안 되어서 지원되지 않는 라이브러리들이 있을 수 있다.

// await : 파일이 다 만들어 질 때까지 기다려준다. (callback 함수와 같은 기능. 최근에 생겨난 함수다.)
await workbook.xlsx.writeFile('menu.xlsx');

 

기존의 콜백함수 형식

// 콜백함수
res.sendFile(__dirname + '/menu.xlsx', function(err) {  // 파일 보내기가 완료 되면
  fs.unlinkSync(__dirname + '/menu.xlsx');  // 파일을 삭제해라.
});

 

main.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <span id="menuArea"></span>
    <br />
    <input type="button" id="drawGraph" value="그래프그리기">
    <input type="button" id="downloadExcel" value="엑셀다운로드">
    <div id="graph" style="width: 800px; height: 500px;"></div>
  </body>
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  <script type="text/javascript">
    var week = ['월','화','수','목','금'];
    var menus;

    $.ajax({
      url : '/getMenu',
      type : 'GET',
      data : {

      },
      success : function (data) {
		... 생략
      }
    });

    // 그래프 그리기
    $("#drawGraph").click(function() {

    });

    // 엑셀 다운로드
    $("#downloadExcel").click(function() {
      var scores = [];

      for (var i = 0; i < menus.length; i++) {
        scores[i] = [];
        var dailyScore = $(`select[name=s${i}]`);
        for (var j = 0; j < dailyScore.length; j++) {
          for (var k = 0; k < 11; k++) {
            if (dailyScore[j][k].selected) {
              scores[i].push(k);
            }
          }
        }
      }
      console.log(scores);
      console.log(menus);
      $.ajax({
        url : '/getExcel',
        type : 'POST',
        data : {
          scores : scores,
          menus : menus
        },
        success : function (res) {
          console.log(res);
          location.href = '/getExcelFile';
        }
      });

    });
  </script>
</html>

ajax로 /getExcel 라우터에 요청을 보낸 다음, 라우터가 파일을 다 만들었다고 응답을 보내면, success에서 다시 /getExcelFile 라우터에 요청을 보내 엑셀을 다운로드 하게 된다. 즉, 엑셀을 만드는 라우터(/getExcel)와 엑셀 파일을 전송하는 라우터(/getExcelFile)를 클라이언트의 html 파일 내의 자바스크립트 로직이 연결을 하게 된다는 것을 알 수 있다.

app.js (엑셀 파일을 전송하는 라우터)

// 위에서 파일 만들기가 완료되었다는 응답이 가면 ajax가 이쪽 라우터로 이동시켜준다.
app.get('/getExcelFile', function (req, res) {
  res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  res.setHeader("Content-Disposition", "attachment; filename=menu.xlsx");

  var fs = require('fs');
  // 콜백함수
  res.sendFile(__dirname + '/menu.xlsx', function(err) {  // 파일 보내기가 완료 되면
    fs.unlinkSync(__dirname + '/menu.xlsx');  // 파일을 삭제해라.
  });
});

 

 

전체 코드

app.js

var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app).listen(80);
console.log("Server is running...")

var mysql = require('mysql');
var request = require('request');
var cheerio = require('cheerio');
const ExcelJS = require('exceljs');

// var connection = mysql.createConnection({
//   host: 'localhost',
//   port: 3308,
//   user:'root',
//   password: '1234',
//   database: 'javascript'
// });
var bodyParser = require("body-parser");
// extended : 객체 안의 객체를 파싱 가능하게 하려면 true로 해야한다.
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json({limit: '50mb'}));

app.get('/', function (req, res) {
  res.sendfile("main.html");
});
app.get('/main', function (req, res) {
  res.sendfile("main.html");
});

// app.get('/getMenu', function (req, res) {
//   request("http://www.kopo.ac.kr/kangseo/content.do?menu=262", function(err, res2, body) {
//     const $ = cheerio.load(body);
//     var menus = [];
//
//     for(var i = 0; i < 5; i++) {
//       menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
//     }
//     // console.log(menus);
//
//     res.send(menus);
//   })
// })

app.get('/getMenu', function (req, res) {
  const options = {
    url:'http://www.kopo.ac.kr/kangseo/content.do?menu=262',
    method: 'GET'
  }
  request.get(options, function(err,httpResponse,body){
    const $ = cheerio.load(body);
    var menus = [];

    for(var i = 0; i < 5; i++) {
      menus.push($('td')[i*4+2].children[1].children[0].data.replace(/\n/g, "").split(","));
    }

    res.send(menus);
  });
})

// ajax로 데이터 넘겨받아 엑셀형식으로 만들기
app.post('/getExcel', async function (req, res) {
  var menus = req.body.menus;
  var scores = req.body.scores;

  const workbook = new ExcelJS.Workbook();
  const sheet = workbook.addWorksheet('menu');

  const alpahbet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
  const dayOfTheWeek = ['월','화','수','목','금','토','일'];
  var menusMaxLength = 0;
  var roundedAverage = [];

  // 월, 화, 수, 목, 금을 도는 for문
  for (var i = 0; i < menus.length; i++) {
    // console.log(`${alpahbet[i*2]}1 = ${dayOfTheWeek[i]}`);
    sheet.getCell(`${alpahbet[i*2]}1`).value = dayOfTheWeek[i]; // 1행에 월, 화, 수, 목, 금
    scores[i] = scores[i].map( str => parseInt(str) );  // str to integer (점수 array 형변환)

    // 각 요일별 메뉴의 개수만큼 도는 for문 (각 요일의 메뉴와 메뉴별 점수를 엑셀에 삽입한다.)
    for (var j = 0; j < menus[i].length; j++) {
        // console.log(`${alpahbet[i*2]}${j+2} = ${menus[i][j].trim()}`);
        // console.log(`${alpahbet[i*2+1]}${j+2} = ${scores[i][j]}`);
        sheet.getCell(`${alpahbet[i*2]}${j+2}`).value = menus[i][j].trim();
        sheet.getCell(`${alpahbet[i*2+1]}${j+2}`).value = scores[i][j];
    }
    // 각 요일별 메뉴의 평균 점수를 계산하고, 이를 배열에 담아둔다.
    // reduce 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
    // acc : 누적값, (,0 : 초기값), curr : 현재값... 그러니까 파이썬 식으로 표현하면 sum = sum + curr 이런거다.
    const sum = scores[i].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0);
    const average = sum / menus[i].length;
    roundedAverage[i] = ( Math.round(average*10) ) / 10; // 소수점 첫째 자리까지 표현

    // 메뉴의 길이 중 최대로 긴 값의 크기를 구해서 저장한다. (요일마다 메뉴의 길이가 다를 수 있으니까.)
    // console.log(`${alpahbet[i*2]}${menus[i].length+3} = 평균`);
    if (menus[i].length > menusMaxLength) menusMaxLength = menus[i].length;
  }

  // 요일마다 메뉴의 길이가 다른 경우가 있기 때문에 위에서 메뉴를 모두 채우는 동안 배열에 저장해둔 다음 한 번에 출력한다.
  for (var i = 0; i < menus.length; i++) {
  sheet.getCell(`${alpahbet[i*2]}${menusMaxLength+3}`).value = '평균';
  sheet.getCell(`${alpahbet[i*2+1]}${menusMaxLength+3}`).value = roundedAverage[i];  // 계산한 평균을 입력
  }

  // await : 파일이 다 만들어 질 때까지 기다려준다. (callback 함수와 같은 기능. 최근에 생겨난 함수다.)
  await workbook.xlsx.writeFile('menu.xlsx');
  // 엑셀 다 만들었다고 클라이언트에 응답을 보냄.
  res.send("ok");
});

// 위에서 파일 만들기가 완료되었다는 응답이 가면 ajax가 이쪽 라우터로 이동시켜준다.
app.get('/getExcelFile', function (req, res) {
  res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  res.setHeader("Content-Disposition", "attachment; filename=menu.xlsx");

  var fs = require('fs');
  // 콜백함수
  res.sendFile(__dirname + '/menu.xlsx', function(err) {  // 파일 보내기가 완료 되면
    fs.unlinkSync(__dirname + '/menu.xlsx');  // 파일을 삭제해라.
  });
});

 

main.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <span id="menuArea"></span>
    <br />
    <input type="button" id="drawGraph" value="그래프그리기">
    <input type="button" id="downloadExcel" value="엑셀다운로드">
    <div id="graph" style="width: 800px; height: 500px;"></div>
  </body>
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  <script type="text/javascript">
    var week = ['월','화','수','목','금'];
    var menus;

    $.ajax({
      url : '/getMenu',
      type : 'GET',
      data : {

      },
      success : function (data) {
        menus = data;  // 바깥 배열이 요일별로 분리, 안쪽 배열이 메뉴별로 분리된 매트릭스
        for(var i = 0; i < menus.length; i++) {
          // 바깥 배열 길이만큼 돌며 요일을 출력
          $("#menuArea").append(week[i] + "요일<br>");
          // console.log(week[i] + "요일<br>");

          // 셀렉트 박스를 만들기 위해 appendingText에 한 번에 담아서 출력하기
          for(var j = 0; j < menus[i].length; j++) {
            var appendingText = "";
            // i 요일의 j 번째 메뉴를 출력
            // appendingText += menus[i][j] + `<select id = "s${i}_${j}"`;
            appendingText += menus[i][j] + `<select name="s${i}">`;

            // select 의 수량을 10개 만든다.
            for (var k = 0; k < 11; k++) {
              appendingText += `<option value = ${k}>${k}</option>`;
            }
            appendingText += `<select><br>`;
            $("#menuArea").append(appendingText);
            // console.log(appendingText);
          }
          $("#menuArea").append("<br>");
        }
      }
    });

    // 그래프 그리기
    $("#drawGraph").click(function() {
      var averageArr = [];
      console.log($("#count").val());

      // 월 ~ 금 반복문
      for (var i = 0; i < menus.length; i++) {
        var dailyAverage = 0;
        // 각 요일별 메뉴의 반복문
        var dailyScore = $(`select[name=s${i}]`);
        for (var j = 0; j < dailyScore.length; j++) {
          // 각 메뉴별 1 ~ 10 점 중 어떤 것이 선택되었는지를 확인
          for (var k = 0; k < 11; k++) {
            if (dailyScore[j][k].selected) {
              dailyAverage +=k; // 각 요일별 메뉴들의 점수의 총 합
            }
          }
        }
        dailyAverage = dailyAverage / dailyScore.length // 각 요일별 메뉴의 점수들의 총 합을 메뉴의 개수로 나눠 평균을 계산
        averageArr.push([week[i], dailyAverage]);
        console.log(averageArr);
      }

      google.charts.load('current', {'packages':['corechart', 'line']});
      google.charts.setOnLoadCallback(drawCurveTypes);

      function drawCurveTypes() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', '요일');
        data.addColumn('number', '점수');

        data.addRows(averageArr);

        var options = {
          hAxis: {
            title: '요일'
          },
          vAxis: {
            title: '만족도'
          },
          series: {
            1: {curveType: 'function'}
          }
        };

        var chart = new google.visualization.LineChart(document.getElementById('graph'));
        chart.draw(data, options);
      }
    });

    // 엑셀 다운로드
    $("#downloadExcel").click(function() {
      var scores = [];

      for (var i = 0; i < menus.length; i++) {
        scores[i] = [];
        var dailyScore = $(`select[name=s${i}]`);
        for (var j = 0; j < dailyScore.length; j++) {
          for (var k = 0; k < 11; k++) {
            if (dailyScore[j][k].selected) {
              scores[i].push(k);
            }
          }
        }
      }
      console.log(scores);
      console.log(menus);
      $.ajax({
        url : '/getExcel',
        type : 'POST',
        data : {
          scores : scores,
          menus : menus
        },
        success : function (res) {
          console.log(res);
          location.href = '/getExcelFile';
        }
      });

    });
  </script>
</html>

 

 

cheerio 모듈 읽어볼만한 글

magic.wickedmiso.com/142

 

[Node.js] SCRAPING을 위한 cheerio 모듈과 cheerio-httpcli 모듈

발췌 : 자바스크립트와 Node.js를 이용한 웹 크롤링 테크닉 ※ 스크래핑이란? -. 웹의 세계에서 흔히 말하는 '스크래핑'은 웹 사이트에서 HTML 데이터를 수집하고, 특정 데이터를 추출, 가공하여 저��

magic.wickedmiso.com

 

+ Recent posts