기본 콘텐츠로 건너뛰기

(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 란 매크로...

[Python] 크롤링 HTTP Forbidden 403 문제 해결

  코드에는 문제가 없는데 HTTP 403 Forbidden이 발생하길래.. 검색해보니까 서버사이드에서 저 같은 불법 크롤러를 거르는 메쏘드가 있는 모양입니다. 아래의 링크를 참고해서 해결했습니다 https://howtoworld.tistory.com/52 urllib에서 Request를 import 해줘야합니다. 1 2 3 4 def  hellenicshipping():     url  =  Request( "https://www.hellenicshippingnews.com/category/shipping-news/dry-bulk-market" ,headers = { 'User-Agent' :  'Mozilla/5.0' })     html  =  urlopen(url)     savefile  =   './hsn.txt' Colored by Color Scripter cs

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

처음으로 Node.js로 웹 데이터 수집을 해봤는데, 글자가 자꾸 깨져서 수십번의 실패 끝에 되는 방법을 찾았습니다. 웹에는 iconv와 iconv-lite 두 가지 방법이 나오는데, iconv는 무슨 문제에선가 설치가 잘 안됩니다. 그래서 iconv-lite를 이용해서 진행했는데 이 경우에는 아래 순서로 코드가 진행될 것입니다. Request >> Cheerio >> iconv-lite 여기서 Cheerio와 iconv-lite 모듈 사용 위치가 중요한데, 만약 잘못되었다면 '占쏙옙'또는 '�'을 만나게 됩니다. 웹에서 검색해보면 인코딩이 잘못됐으니 로드된 데이터를 iconv-lite로 바꾸라고 하는데, 모듈 사용이 적절하게 되지 않고 데이터를 읽어들이면 이미 깨진 데이터를 저장하기 때문에 역으로 디코딩 자체도 당연히 제대로 안됩니다. 그래서 여기저기서 주워온 지식들을 합쳐보니까 아래와 같이 하면 해결이 됐습니다 ㅎ.ㅎ 오늘도 웹에 계신 수많은 선생님들 감사합니당!!! <방법> 1. request 옵션에서 인코딩은 Null로 설정 2. 아래와 같이 Cheerio에서 불러들일 때 iconv-lite의 iconv.decode(Target 변수, 'EUC-KR') 3. 마지막으로 데이터 출력할 때는 별도의 모듈을 사용하지 않고 .text()로 읽어들인다.