Telegram Bot + Selenium 주식 공모청약 알림봇 만들기 (node, js)

PARK·2021년 6월 22일
0

javaScript

목록 보기
4/4
post-thumbnail

토이 프로젝트 계획

공모청약 데이터가 있는 사이트에 접속하고 거기서 데이터를 추출해와서 활용할 계획입니다. 데이터에는 공모주 일정이 있으니, 그 일정과 오늘의 날짜가 일치하면 아침에 메세지를 보내는 간단한 토이 프로젝트입니다. 물론 사람마다 일주일 전에 메세지를 보내거나 보내지는 메세지를 세부적으로 설정할 수 있습니다.

텔레그램 설치와 Bot 사용은 인터넷을 통해 쉽게 설치할 수 있기에 접근성이 좋습니다.
selenium은 파이썬으로 사용하는 방법이 많았는데 webdriver를 사용하면서 크게 문제되지 않았습니다.

package 설정

프로젝트를 위해서 axios, chromedriver, node-schedule, nodemon, selenium-webdriver을 다운받습니다.

axios는 통신을 위해서 사용하고 nodemon은 좀 더 편리한 저장과 실행을 위해서 다운 받았습니다. chromedriver은 저의 경우 크롬을 사용하기에 다운받았습니다.

웹 드라이버 https://www.npmjs.com/package/selenium-webdriver

위에 사이트를 참조하셔서 브라우저에 맞는 것을 설치하면 될 거 같습니다.


저는 package.json을 위와 같이 설정했습니다.

selenium.js 만들기

const {webdriver, Builder, By, Key, Until} = require('selenium-webdriver');

const selenium = async() => { 
    let driver = await new Builder().forBrowser('chrome').build();
    driver.manage().window().maximize();
       
};

 selenium();

driver에 우리가 사용하고자 하는 브라우저를 설정해줍니다.
manage()는 브라우저를 관리하는 속성입니다. 윈도우 크기를 최대로 했습니다. 설정하지않으면 너무 작게 뜹니다.

https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html

공모청약일정을 가져오기 위해서 사용할 사이트입니다. selenium은 웹에서 데이터를 뽑아올 수 있는데, xpath 사용해서 테이블의 행을 빼올겁니다.


사이트에 실제로 접속해서 검사를 해보면 테이블 형태로 데이터들이 저장되어 있음을 확인하실 수 있습니다.

const {webdriver, Builder, By, Key, Until} = require('selenium-webdriver');

const selenium = async() => { 
    let driver = await new Builder().forBrowser('chrome').build();
	driver.manage().window().maximize();
  	driver.get("//");
    
  let tbody = await driver.findElement(By.xpath('/html/body/table[3]/tbody/tr/td/table[1]/tbody/tr/td[1]/table[4]/tbody/tr[2]/td/table/tbody'));

  let trs = await tbody.findElements(By.css('tr'));
 
};

 selenium();

driver.get은 시작하고자 하는 브라우저를 지정할 수 있습니다. 그 다음에 findElement는 페이지의 element를 찾는 녀석인데 속성을 다양하게 지정할 수 있습니다. tagName, id 등등이 있는데 여기서는 xpath를 사용했습니다.

let trs = await tbody.findElements(By.css('tr'));

findElements는 Element를 복수로 가져올 때 사용합니다.
중요한 것이 tbody 입니다. tbody에는 webElement객체가 저장되어 있습니다. 그렇기에 변수 tbody에 WebElement메서드를 사용할 수 있습니다. By.css('tr')을 사용해서 tbody의 행만 trs에 저장시켰습니다.

const {webdriver, Builder, By, Key, Until} = require('selenium-webdriver');

const selenium = async() => { 
    let driver = await new Builder().forBrowser('chrome').build();
	driver.manage().window().maximize();
  	driver.get("//");
    
  let tbody = await driver.findElement(By.xpath('/html/body/table[3]/tbody/tr/td/table[1]/tbody/tr/td[1]/table[4]/tbody/tr[2]/td/table/tbody'));

  let trs = await tbody.findElements(By.css('tr'));

  
 trs.forEach( key => {  
        key.getText().then( text => {
            const ipoDate = text.split(' ')[2].substring(0, 10);
            const result = text.trim().split(' ');
                        
        });
    }
    );
};

 selenium();

getText()는 xpath로 갖고 온 값을 문자열로 바꾸는 메서드입니다. 코드에서는 행 전체를 바꾼 후 공백을 기준으로 분리시켜서 활용했습니다. Promise 객체이기 때문에 then을 사용했습니다. getText()만 사용 ipoDate는 공모청약의 시작 일만 뽑았습니다. ipoDate는 오늘의 날짜를 비교하는 데에 사용됩니다. result는 테이블의 순서와 똑같이 공모주이름, 날짜순으로 저장합니다.

const {webdriver, Builder, By, Key, Until} = require('selenium-webdriver');

const selenium = async() => { 
    let driver = await new Builder().forBrowser('chrome').build();
	driver.manage().window().maximize();
  	driver.get("//");
    
  let tbody = await driver.findElement(By.xpath('/html/body/table[3]/tbody/tr/td/table[1]/tbody/tr/td[1]/table[4]/tbody/tr[2]/td/table/tbody'));

  let trs = await tbody.findElements(By.css('tr'));

    let today = new Date();
    const year = today.getFullYear();
    const month = ('0' + (today.getMonth() + 1)).slice(-2);
    const date = ('0' + today.getDate()).slice(-2); 
    
    const now = `${year}.${month}.${date}`;
  
 trs.forEach( key => {  
        key.getText().then( text => {
            const ipoDate = text.split(' ')[2].substring(0, 10);
            const result = text.trim().split(' ');
          
          if(ipoDate === now){
                axios({
                    method: 'post',
                    url: '//본인의 url',
                    data: {
                        chat_id: //본인의 id,
                        text: 
                        `공모주일정: ${result[1]}\n종목명: ${result[0]}\n확정공모가: ${result[2]}\n희망공모가: ${result[3]}\n청약경쟁률: ${result[4]}\n주간사: ${result[5]}
                        `
                    }
                });                      
          });
      }
    );
};
 selenium();

today는 오늘의 날짜를 설정하는 코드입니다. 앞에서 언급한대로 if(ipoDate === now) 비교를 해서 axios를 통해서 메세지를 보냅니다.

스케줄러 사용하기

const {webdriver, Builder, By, Key, Until} = require('selenium-webdriver');
const axios = require('axios');
const schedule = require('node-schedule');

const selenium = async() => { 
    // 앞선 코드
};

const job = schedule.scheduleJob('00 00 08 * * *', ()=>{
    console.log('completed');
    selenium()
});

아침 08시에 selenium()이 실행되도록 스케줄러 작업을 합니다. 직접 시간을 변경해서 테스트 해보기에 용이합니다.

윈도우 작업스케줄러 사용하기

위 방법은 계속해서 개발환경와 컴퓨터를 켜놓고 있어야 하기에 다소 무리가 있는 방법 같았습니다. 그래서 윈도우 작업스케줄러를 사용해봤습니다.

사용법은 쉽게 서치할 수 있지만 까다로운 부분이 있었습니다.
속성 -> 동작 -> 프로그램시작 이 부분에서 npm.cmd로 설정해야 작동됩니다. node selenium, npm start 등은 되지 않았습니다. 아마 package에서 nodemon을 설정해서 그런듯 합니다.

그 다음은

속성 -> 조건 -> 전원 이 부분에서 "이 작업을 실행하기 위해 절전 모드 종료"를 체크했습니다. 이렇게 설정해놓으면 전원이 꺼져 있는 경우가 아니면 실행됩니다. 절전 상태, 컴퓨터가 켜져있는 상태 모두 작동합니다.

const {webdriver, Builder, By, Key, Until} = require('selenium-webdriver');

const selenium = async() => { 
    //
 trs.forEach( key => {  
        key.getText().then( text => {
            //
          if(ipoDate === now){
                axios({
                    //
                }); 
		driver.close();
          });
      }
    );
};
 selenium();

driver.close()을 axios 종료 바로 뒷 부분에 넣어주면 웹 페이지가 켜지고, 메세지를 보내고, 켜진 웹 페이지를 종료시킵니다.

시간을 설정해서 테스트를 해보니 잘 작동됩니다.

아쉬운 점

  • trs를 tbody에서 뽑았듯이 td로 trs를 분리하면 다양한 메세지 형태를 보낼 수 있는데 그렇게 하지 못하고 substring을 한 것은 아쉽다.

  • date를 설정하는 부분에서 페이지 상의 일정 형식과 맞추기 위해서 코드를 작성했는데 계속 거슬린다. 날짜를 좀 더 효율적인 방법으로 비교할 수 없는지 궁금하다. 찾기 어려웠다.

  • 라이브러리 스케줄러에서 작업 스케줄러로 바꾼 것은 좋았지만, 더 좋은 방법을 찾아봐야겠다.

profile
익숙한 것에 작별을 고해야한다

0개의 댓글