(Node.js) XLSX로 결과 출력하기 / 모듈 디자인 Exporting / Node.js modular design

Node.js에서 XLSX 라이브러리를 제공하기 때문에,

웹 크롤링 -> XLSX Export -> 주기적 메일링
하는 방식으로 데이터 수집이 가능할 것 같다고 생각했습니다.

그래서 이번에는
1) 전에 만들었던 웹크롤러 모듈화
2) XLSX 라이브러리를 사용해서 외부로 Export
3) 모듈화한 것을 외부에서 호출

요래 3가지를 한 번 해봤습니다!

전 근본없는 node.js 코딩을 하기 때문에 먼저 원문 내용을 읽으시는 것이 좋겠습니다.
https://github.com/SheetJS/js-xlsx

이제 시작입니다.

-----
기본적으로 아래와 같이 XLSX 라이브러리를 불러오고 , 내장 wb를 하나 만들어야 합니다. 가상의 Workbook 같은걸로 이해해주셔요.

// ----- XLSX Initialization part ----- //

const XLSX = require('xlsx');
/* Initializing a free workbook & worksheet name*/
let wb = XLSX.utils.book_new();

// ----- End of XLSX Initialization part ----- //




-----
그 다음 let ws_data =[]; 빈 Array를 선언해버립시다.

그러면, Title과 url을 URL이 undefined가 아닐 때만 dynamic allocation하는 concept으로 아래와 같이 설정합니다.
새 행마다 new로 dynamic allocation을 하고, push로 열에다가 밀어넣는다! 는 concept입니다.
엑셀에서 행렬로 그려보면 대충 저런 모습일 것입니다.

                        열        열       열
행 new array -> push() push() push()
행 new array -> push() push() push()
행 new array -> push() push() push()
행 new array -> push() push() push()
행 new array -> push() push() push()



-----
여기서부터는 XLSX로 Export하는 부분입니다.

1) aoa_to_sheet 는 위에서 누적한 데이터를 worksheet로 바꿔줍니다. (아마도 메모리에서요)
2) book_append_sheet worksheet를 workbook에다가 추가 해줍니다. (아마도 메모리에서요??)
3) writeFile을 거치면 드디어 파일이 하나 생성됩니다. 감동!
4) 사용이 끝난 array를 비워줍니다.

이 부분은 Trial & Error + Github 선생님들 말씀에 예제 따라해서 해보니까 되는 코드를 써놨습니다.


-----
이 부분은 위의 Function을 Export해서 쓸 수 있게하는 부분입니다.
Module.Export = (함수 이름)을 하면 외부에서 호출할 수 있습니다.



만약에 Module.Export.(어떤 이름) = (함수 이름)
요래 하면 저 어떤 이름을 사용할 수 있게 됩니다.

예로 Module.Export.BigAndBeautifulHomi = HomiOfShims

이렇게 쓴다면(File이 ./test.js에 있다고 칩시다.), 외부에서는
let homi = require('./test') // 외부 모듈 import할 때는 .js를 빼야하네요!

homi.BigAndBeautifulHomi()
이렇게 호출해야 하게 됩니다. 모듈에 여러가지 함수가 있다면 각 함수마다 이름을 이쁘게 붙여줘야 하겠쥬~

-------
이제는 외부에서 호출하는 단계입니다.
TestField를 밑과 같이 적고, 해당 함수에 변수를 넣고 돌려봅니다.

불행히도 제가 Node.js 입문자라서 저 변수를 배열로 전달하는 방법을 아직 모릅니다.. ㅠㅠ 오늘 3시간은 찾아본 것 같은데 능력부족입니다. 이게 다 ㅠㅠ



잘 출력되는 것을 확인했습니다.


제가 참 좋아하는 XLSX 파일이 아름답게 정리되어서 기분이 좋습니다.

그럼 다음 단계에서는 메일링 시스템을 구축해서 콤퓨타만 켜놓으면 주기적으로 데이터를 수집해서 메일로 쏴주는 모듈과 함께 조립해보려고 합니다.


-----

(여기서부터는 헛소리입니다.)
1. 제가 전자공학 학사에 회사에서 주업무는 물리학 쪽이고, 보조 업무에 초~중급 레벨의 VBA,C++,Python을 주로 쓰고 있는데요,
대량 데이터를 거의 100% 인덱스 기준으로 처리하다보니까 저런 FIFO가 왜 필요한지 처음 배우고 12년 동안 전혀 이해를 못했습니다.
근데 오늘 push를 써보니까 굳이 인덱싱을 안해도 되고 여튼 좋은 함수인거 같습니다.(특히 인덱싱하다가 조건문 헷갈리면 가끔 등골 싸합니다;;)
아마 오늘 이후부터 제가 만든 모듈에는 저런 pop-like 테크닉이 들어간 걸 많이 쓸 거 같습니다.

2. 사내망 데이터 수집 후 관련 인원들에게 뿌려주려고 배우기 시작한 언어인데 Node.js가 파이썬이랑 유사하게 생산성이 엄청 좋은 것 같습니다.
저같이 저레벨 유저도 Ctrl C+V로 엑셀 파일 하나 쓰는게 가능하다니요~ 타이빵라!


----- Crawler module -----
/*

  TestPjt Title : Shims TestCrawler with xlsx writing module
 
  Use VSCode to edit script code of this program for this Node.js Module
  First written on August 2nd
*/

/*
  Pjt modified to General Purpose WebCrawling & XLSX file exporter
  Modifed on August 3rd

*/

const cheerio = require('cheerio');
const request = require('request');
const Iconv = require('iconv-lite');
const log = console.log;

// ----- XLSX Initialization part ----- //

const XLSX = require('xlsx');
/* Initializing a free workbook & worksheet name*/
let wb = XLSX.utils.book_new();

// ----- End of XLSX Initialization part ----- //

// Module Export Part
module.exports = homiOfShims;

// ----- Start of function description ----- //
function homiOfShims(homiOptions){
  // --- GodKimchi's Advice(190804) - make options as set of variables
  // so it would be more good to see at user-input part
let {
targetSite,
targetEncoding,
H_body,
H_parents,
H_children,
H_exportWb,
H_exportWs,
H_titleTarget,
H_titleAttr,
} = homiOptions;
  // --- end of advice point

  if (targetEncoding == 'EUC-KR') {
    targetEncoding = null
  }
  const requestOptions  = { method: 'GET'
    ,uri: targetSite
    ,headers: {"User-Agent": "Mozilla/5.0"}
    ,encoding: null
  };
       
  /* make worksheet */

  function requestCallback(error, response, H_body){
    let ws_data = [];
    let count = 0;
    if (!error) {
      // Data extractor when encoding == EUC-KR
      // GodKimchi's Advice(190804) - Using Three variable operator to reduce entire code
      const $ = cheerio.load(targetEncoding == null ? Iconv.decode(H_body, 'EUC-KR') : H_body);
      // Crawling part
      const $bodyList = $(H_parents).children(H_children);
      $bodyList.each(function(i, elem) {
      let titleText = $(this).find(H_titleTarget).text();
      let titleUrl = $(this).find(H_titleAttr).attr('href')
        if(titleUrl != undefined) {
          log('title : ' + titleText);
          log('url : ' + titleUrl);
          ws_data[count] = new Array();
          ws_data[count].push(titleText);
          ws_data[count].push(titleUrl);
          count = count + 1
        }
      });
    };
  /* Add the worksheet to the workbook */
  let ws = XLSX.utils.aoa_to_sheet(ws_data);
  XLSX.utils.book_append_sheet(wb, ws, H_exportWs);
  /* Write workbook into XLSX File*/
  XLSX.writeFile(wb, H_exportWb);
  ws_data =[];
  }

  request(requestOptions, requestCallback);
};


----- Execute module -----
let homi = require('./Shims_ReqCrawII')
let async = require('async')

async.waterfall([
    function(callback){
        let homiOptions = {
            targetSite : 'https://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001',
            targetEncoding : 'EUC-KR',
            H_body : 'body',
            H_parents : '#main_content > div.list_body.newsflash_body > ul.type06_headline',
            H_children : 'li',
            H_exportWb : 'YouveGotHomi-ed.xlsx',
            H_exportWs : 'HomiedPage1',
            H_titleTarget : 'dl > dt > a',
            H_titleAttr : 'dl > dt > a'
        }
        // --- GodKimchi's Advice(190804) - make options as set of variables
        // Variable set would be transferred to module as a set, and seperated in module side.
homi(homiOptions);
        // --- end of advice point
        callback(null);
    },
    function(){
    let homiOptions = {
        targetSite : 'http://gamewoori.com/board/board.html?code=gamewoori_board9',
        targetEncoding : 'EUC-KR',
        H_body : 'tbody',
        H_parents : 'div.bbs-table-list > table > tbody > tr',
        H_children : 'td',
        H_exportWb : 'YouveGotHomi-ed.xlsx',
        H_exportWs : 'HomiedPage2',
        H_titleTarget : 'div.tb-left > a',
        H_titleAttr : 'div.tb-left > a'
        }
homi(homiOptions);
    }
])

댓글

이 블로그의 인기 게시물

(VBA) 009 - 닫힌 파일에서 데이터 읽어오기 (ExecuteExcel4Macro)

(Node.js) EUC-KR을 Cheerio - Iconv-lite로 불러올 때