기본 콘텐츠로 건너뛰기

(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)

#毎日育ちゃん可愛い大会 예시의 매크로 파일을 테스트 할 때는 저장된 폴더를 사용하실 폴더로 꼭 바꿔주세요! (pptx파일) pptx파일 (xlsx파일) 예제데이터파일   Macro파일 ★ 진행목적 : 왜 이걸 사용합니까 . 1) 행이나 열 , 또는 Sheet 과 같이 다른 특성을 가지는 1,2,3 차 데이터배열에 대한 처리 방법을 지금까지 설명드렸습니다 . 2) 그럼 이제 , 다른 파일에서 데이터를 읽어올 방법을 알아볼 필요가 있습니다 . 어째선가 회사의 데이터를 처리하다보면 , 주기적인 이름의 엑셀 파일 특정 Sheet, Cell 에 있는 경우가 많았습니다 . 3) 엑셀에서 이미 열려있는 파일의 참조는 ‘=‘ 을 사용하면 가능하지만 , 닫힌 파일은 데이터를 읽지 못합니다 . 4) 그래서 이를 처리하기 위해 VBA 의 ‘ExecuteExcel4Macro( 주소 )’ 를 사용합니다 ! ★ 다른 파일의 참조는 어떻게 합니까 ? 1) 열려 있는 다른 파일의 데이터를 읽는 방식은 ‘=‘ 을 입력하고 해당 Cell 을 클릭하면 됩니다 ! 2) 그러면 아래와 같이 (=‘ 파일이 있는 폴더 [ 파일명 ]Sheet 명 ’!Cell 주소 ) 의 형태로 기록 이 됩니다 . ★ 닫힌 파일에 대해서는 INDIRECT 는 사용이 되질 않습니다 ! 1) INDIRECT 로는 처리가 되질 않습니다 . 2) 어째선가 전에 사용하던 INDIRECT 를 사용하고 싶지만 , 사용이 되질 않습니다 . 검색을 해봐도 안된다는 답변만 있네요 ! 3) 파일이 하나 두 개라면 , 이전과 같이 ‘=‘ 를 쓰면 되겠지만 , 그러면 자동화를 통한 효율화가 불가능해지겠죠 ! 4) 그래서 이를 처리하기 위해 VBA 의 ‘ExecuteExcel4Macro( 주소 )’ 를 사용합니다 ! ★ ExecuteExcel4Macro 는 어떻게 사용합니까 ? 1) VBA 의 ExecuteExcel4Macro 란 매크로...

(VBA) 004 - Object 이름으로 이미지 복사 붙여넣기

(VBA) 004- Object 이름으로 이미지 복사 붙여넣기 진행목적  :  왜 이걸 사용합니까 . → Object  이름을 사용해서 주기적 복사가 가능한 경우가 있습니다 .     ( 예  :  사진의 이름이  ‘ 사진  1’ ‘ 사진  2’…  로 되어있거나 , ‘Picture 1’, ‘Picture 2’…  로 됨 ) (설명자료는 여기) 설명자료 ( 예제파일은 여기 ) 예제파일 (VBA 진행에 대해) 이제 VBA 세션 자료를 필요할 때 보려고 이 블로그에 남기려고 해요. 앞선 001-003도 옮겨야겠습니다. 올해 내에 100가지 주제를 가지고 포스팅 할 수 있도록 할게요. ( 中谷 育 さんのイメージで に対して) 本当に可愛い中谷 育さんのイメージが 含まれています。 ありがとうございます。 何か問題があったら、教えてください。 直ちに処理します。

(Node.js) XLSX 모듈 사용 / 행렬 파싱 및 조건에 맞는 데이터만 추출

요번에는 Node.js로 아래와 같이 KRX에서 코스피/코스닥 상장사 정보를 취득한 후 네이버에서 원하는 조건에 맞는 정보만 크롤링하는 모듈을 만들어보려고 합니다! 그러면 주식하는 친구들은 조건식을 걸어놓기만 해도 손 안대고 틈틈히 자동으로 수집된 정보를 확인할 수 있게 되겠네요!  (이후에는 자동 메일링까지 추가할 건데 우선은 크롤링해서 유의미한 XLSX로 Export하는 것 까지를 먼저 만들려고 합니다!) 네이버 주식에서 페이지를 확인해보니 종목코드를 기준으로 페이지 주소가 매칭이 되고, 그 유니크한 종목 코드에 유니크한 Selector를 확인하면 데이터 크롤링이 되겠네요! ----- 저는 KRX 사이트에서 업체리스트를 xls로 받아온 뒤에 추출된 결과를 중간에 result.xls로 먼저 저장해놨다가 크롤링할 때 다시 리딩해서 쓰는 방식을 구현해보려고 합니다. VBA / C++ 데이터 처리 방식이 익숙하기도 하고 나중에 불필요하면 떼버리면 되니까요? XLSX Import하는 모듈은 앞서 설명을 드렸었고요, 데이터를 Readfile한 뒤에 조건에 맞는거만 옮겨담는 작업이 필요한데요, 이를 위해 stackoverflow에서 parsing하는 알고리즘을 참고해서 아래와 같이 변형합니다! https://stackoverflow.com/questions/30859901/parse-xlsx-with-node-and-create-json 원본은 data[row][headers[col]] = value인데, XLSX에서 가상의 sheet에 데이터를 넣어주려면 array of arrays 방식이 되어야 하기 때문에, 아래와 같이 우선 빈 배열을 선언하고, row / column을 동적할당(new array()) 후 push 하는 방식으로 처리해줘야 합니다. 제가 아는 방법 중엔 이게 제대로 동작을 하기 때문에 이렇게 바꿔서 사용했습니다. 조건식은 나중에 입력받겠지만,...