티스토리 뷰

사실 nodeJS 책을 봤는데 그 책에 크롤링 cheerio, nodemailer, exceljs, node-cron에 대해 잠깐 배울 수 있었다.

이것들을 배우고 나니 써먹고싶어서 친한 친구에게 뭐 좀 도와줄까? 하고 물어서..ㅋㅋ

증권업계에 종사하는 친구를 위해 아침 7시에 각종 지수를 모아서 엑셀로 만들어 메일로 보내주는 nodejs 코드를 작성했다.

cron.schedule(
  '0 7 * * *',
  async () => {
    const indexes = await getIndex();
    const excelFile = await genExcel(indexes);
    sendMail(excelFile);
  },
  {
    scheduled: true,
    timezone: 'Asia/Seoul',
  }
);

매일 아침 7시에 동작하는 크론을 작성했다. getIndex() 함수는 원하는 지수들을 전부 가져오는 함수이다.

친구가 필요로 하는 지수들이 정확했기에 일단 매개변수 없이 하나의 동작만 하는 함수로 작성했다. 

그리고 그 지수들로 Excel을 생성하는 함수인 genExcel() 함수에 방금 가져온 지수를 매개변수로 주고

excelFile이 생성되면 이것을 sendMail()함수에 보내서 메일이 발송되도록 처리했다.

const getIndex = async () => {
  try {
    const [
      responseKorea,
      responseUSA,
      responseTOPX,
      responseEXCHANGE,
      responseSwedenEXCHANGE,
    ] = await Promise.all([
      axios.get(urlKoreaIndex),
      axios.get(urlUSAIndex),
      axios.get(urlTOPXIndex),
      axios.get(urlEXCHANGEIndex),
      axios.get(urlSwedenExchage),
    ]);

    const $korea = cheerio.load(responseKorea.data);
    const $usa = cheerio.load(responseUSA.data);
    const $topx = cheerio.load(responseTOPX.data);
    const $exchange = cheerio.load(responseEXCHANGE.data);
    const $swedenExchange = cheerio.load(responseSwedenEXCHANGE.data);

    return {
      KOSPI: $korea('#KOSPI_now').text(),
      KOSDAQ: $korea('#KOSDAQ_now').text(),
      DOW: $usa(dowSelector).text(),
      NASDAQ: $usa(nasdaqSelector).text(),
      SNP500: $usa(snpSelector).text(),
      TOPX: $topx('.spt_con strong').text(),
      USAEX: $exchange(usaExSelector).text(),
      JAPANEX: $exchange(japanExSelector).text(),
      EUROEX: $exchange(euroExSelector).text(),
      SWEDENEX: $swedenExchange(swedenExSelector).text(),
    };
  } catch (error) {
    console.error(error);
    return null;
  }
};

생각보다 지수가 한꺼번에 모인 사이트를 찾기 어려워서 이곳저곳 들어가서 html을 가져왔다.

처음에는 지수를 가져올때마다 await을 걸어서 총 5번 await 이 걸리도록 코드를 작성했는데,

html을 가져오는 사이트들은 서로 전혀 상관없었기에 병렬적으로 처리해 주기 위해 Promise.all을 사용했다.

dowSelector 같은 곳에 문자열로 된 selector들이 저장되어 있다.

이렇게 해서 원하는 지수들을 가져온 뒤에 리턴 시켜준다.

const genExcel = async (data) => {
  const workbook = new ExcelJS.Workbook();
  const firstSheet = workbook.addWorksheet('지수리스트');
  firstSheet.columns = excelHeader;
  firstSheet.addRow(data);

  const excel = await workbook.xlsx.writeBuffer();
  return excel;
};

엑셀은 이렇게 만들어준다. excelHeader에 이미 매개변수로 받는 data와 key들을 맞춰놓았기 때문에

간단하게 작성할 수 있다.

const send = async (data) => {
  const transporter = nodemailer.createTransport(config);
  const result = await transporter.sendMail(data);
  return result.messageId;
};

module.exports = { send };
const sendMail = async (buffer) => {
  if (!Buffer.isBuffer(buffer)) {
    throw new Error('Invalid buffer object.');
  }

  const filename = `${Date.now()}_각종지수.xlsx`;
  const fromEmail = process.env.MAIL_FROM;
  const toEmail =
    process.env.ENV === 'dev'
      ? process.env.MAIL_TO_DEV
      : process.env.MAIL_TO_PROD;

  const result = await nodemailer.send({
    from: fromEmail,
    to: toEmail,
    subject: '각종지수 엑셀발송',
    text: '문의는 변기원에게',
    attachments: [
      {
        filename,
        content: buffer,
        contentType:
          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      },
    ],
  });

  if (!result?.length) {
    throw new Error('Failed to send email.');
  }
  return result;
};

다른 모듈에서 send 함수를 쉽게 사용할 수 있도록 만들어주고 에러 처리를 위해 result.messageId를 리턴해주자

메일을 받을 사람도 내 친구밖에 없으므로 환경변수에 담아서 작성해 주고 nodemailer.send()를 실행시켜서 메일을 보낸다.

그리고 buffer를 받아서 첨부해 주자.

생각해 보니 위에 genExcel 함수를 genExcelBuffer 같은 거로 바꾸는 게 낫겠다.

 

이렇게 하고 나니 하루 오전 7시에 한 번만 발송되는 게 조~금 아쉽더라. 

그래서 프런트엔드 화면을 만들어서 api를 붙이고 클릭하면(GET요청을 보내면) 그 시간 기준으로 지수를 종합해서

메일로 보내주는 api를 만들었다.

 

일하다가 기분 좋으라고 꽃가루도 날려줬다.

누군가에게 도움이 되니 공부할 맛이 난다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함