728x90
반응형

재사용성이 많은 함수들은 모듈로 만들어두면 매우 편하다. 예를 들면 아래처럼 driver 를 선언하는 함수는 각 라우터마다 동일하게 필요하다.

        // 웹드라이버 설정
        let driver = await new webdriver.Builder()
        .forBrowser('chrome')
        .setChromeOptions(new chrome.Options().windowSize({width: 1920, height: 1080}))
        .setChromeService(new chrome.ServiceBuilder(process.env.CHROMEDRIVER_PATH))
        .build();

 

또한 driver 메소드들을 우리 입맛에 맞게끔 만들어 두면 개발할 때도 훨씬 수월할 것이다.

 

참고로 selenium javacscript의 메소드에 관한 내용들은 아래 사이트에서 자세히 확인할 수 있다.

 

Index

 

www.selenium.dev

 

 

The Selenium Browser Automation Project

Selenium automates browsers. That's it!

www.selenium.dev

1. modules 밑에 모듈을 내보내는 파일을 만든다.

 

/mdules/selenium/driverHandler.ts

import webdriver, { WebDriver } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome';
import firefox from 'selenium-webdriver/firefox'

// dotenv
import dotenv from 'dotenv'
dotenv.config();

// new driver 반환 함수
export const getDriverHandler = async () : Promise<WebDriver> => {
    const driver =  await new webdriver.Builder()
        .forBrowser('chrome')
        .setChromeOptions(new chrome.Options().windowSize({width: 1920, height: 1080}))
        .setChromeService(new chrome.ServiceBuilder(process.env.CHROMEDRIVER_PATH))
        .build();

    return driver;
}

 

/modules/index.ts

export * from "./db/expireHandler"

export * from "./selenium/driverHandler"

 

2. 라우터에서 모듈을 가져와 쓴다.

...

// modules
import { 
    getDriverHandler,
 } from '../modules';
 
 ...
 
// 웹드라이버 설정
let driver = await getDriverHandler();

 

이런식으로 여러개의 모듈을 만들 수 있다.

 

/modules/selenium/alertHandler.ts

import { until, WebDriver } from "selenium-webdriver";

// alert 확인 누르기
export const alertCloseAccept = async (driver : WebDriver ) => {

    // alert 창 뜰 때까지 기다림
    await driver.wait(until.alertIsPresent());

    // alert 로 드라이버 이동
    let alert = await driver.switchTo().alert();

    // alert text 사용하는 곳
    let alertText = await alert.getText();

    // 확인버튼 클릭
    await alert.accept();

    // 다시 원래 컨텐츠로 드라이버 이동
    await driver.switchTo().defaultContent();
}

// alert 닫기 누르기
export const alertCloseDismiss = async (driver : WebDriver ) => {

    // alert 창 뜰 때까지 기다림
    await driver.wait(until.alertIsPresent());

    // alert 로 드라이버 이동
    let alert = await driver.switchTo().alert();

    // alert text 사용하는 곳
    let alertText = await alert.getText();

    // 확인버튼 클릭
    await alert.dismiss();

    // 다시 원래 컨텐츠로 드라이버 이동
    await driver.switchTo().defaultContent();
}

// prompt 핸들러
export const promptCloseHandler = async (driver : WebDriver, text : string) => {

    // alert 창 뜰 때까지 기다림
    await driver.wait(until.alertIsPresent());

    // alert 로 드라이버 이동
    let alert = await driver.switchTo().alert();
    
    // prompt text 전송
    await alert.sendKeys(text)
    
    // 확인버튼 클릭
    await alert.accept();
    
    // 다시 원래 컨텐츠로 드라이버 이동
    await driver.switchTo().defaultContent(); 
}

 

/modules/selenium/navigationHandler.ts

import { WebDriver } from "selenium-webdriver";

export const naviGet = async ( driver : WebDriver, url : string) => {
    await driver.get(url);
}

export const naviBack = async (driver : WebDriver) => {
    await driver.navigate().back();
}

export const naviForward = async (driver : WebDriver) => {
    await driver.navigate().forward();
}

export const naviRefresh = async (driver : WebDriver) => {
    await driver.navigate().refresh();
}

 

/modules/selenium/cookieHandler.ts

import {  IWebDriverCookie, WebDriver } from "selenium-webdriver";

// 쿠키 추가하기
export const addCookie = async (driver : WebDriver, cookie : IWebDriverCookie ) => {
    await driver.manage().addCookie(cookie)
}

// 쿠키 하나 가져오기
export const getOneCookie = async (driver : WebDriver, key : string) : Promise<IWebDriverCookie> => {
    return await driver.manage().getCookie(key);
}

// 쿠키 전부 가져오기 
export const getAllCookie = async (driver : WebDriver) : Promise<IWebDriverCookie[]> => {
    return await driver.manage().getCookies();
}

// 쿠키 하나 지우기
export const deleteOneCookie = async (driver : WebDriver,  key : string) => {
    return await driver.manage().deleteCookie(key);
}

// 쿠키 전부 지우기
export const deleteAllCookie = async (driver : WebDriver) => {
    return await driver.manage().deleteAllCookies();
}

 

/modules/selenium/popupHandler.ts

import { WebDriver } from "selenium-webdriver";

export const popupClose = async (driver : WebDriver ) => {

    // window handles 0 빼고 모든 윈도우 닫음
    (await driver.getAllWindowHandles()).map(async (item : string, index: number)=> {
        if(index > 0) {
            await driver.switchTo().window((await driver.getAllWindowHandles())[index]);
            driver.close();
        }
    })

    // 메인 윈도우로 이동
    await driver.switchTo().window((await driver.getAllWindowHandles())[0]);

}

 

/modules/selenium/jqueryHandler.ts

import { By, WebDriver } from "selenium-webdriver";

// 해당 document의 value값을 바꿔준다.
export const JqChangeValueByID = async (driver : WebDriver, id : string, value: string ) => {
    await driver.executeScript(`document.getElementById('${id}').value='${value}';`)
}

export const JqSetAttribute = async (driver : WebDriver, id : string, attribute: string[] ) => {
    await driver.executeScript(`document.getElementById('${id}').setAttribute('${attribute[0]}','${attribute[1]}')`)
}

export const JqRemoveAttribute = async (driver : WebDriver, id : string, attribute: string ) => {
    await driver.executeScript(`document.getElementById('${id}').removeAttribute("${attribute}")`)
}

 

/modules/selenium/fileHandler.ts

import { Locator, until, WebDriver } from "selenium-webdriver";

// 경로에 맞는 파일 등록 ( 인증서 등 )
export const fileRegister = async (driver : WebDriver, func : Locator, path: string[] ) => { 
    let pathString : string = "";
    
    // 받은 배열을 줄바꿈을 포함한 문자열로 바꿔준다.
    path.map((item : string, index: number) => {
        if(index < path.length - 1) {
            pathString = pathString + item + " \n "
        }
        else {
            pathString = pathString + item
        }
    })
    
    const el = await driver.wait(until.elementLocated(func), 2000);
    return await driver.wait(until.elementIsVisible(el), 2000).sendKeys(pathString);

 

/modules/selenium/findHandler.ts

import { By, until, WebDriver } from "selenium-webdriver";

export const findElementById = async (driver: WebDriver, id: string, timeout = 2000) => {
    return await driver.wait(until.elementLocated(By.id(id)), timeout);
};
  
export const findElementByName = async (driver: WebDriver, name: string, timeout = 2000) => {
  return await driver.wait(until.elementLocated(By.name(name)), timeout);
};

export const findElementByCss = async (driver: WebDriver, css: string, timeout = 2000) => {
  return await driver.wait(until.elementLocated(By.css(css)), timeout);
};
  
export const findElementByXpath = async (driver: WebDriver, xpath: string, timeout = 2000) => {
  return await driver.wait(until.elementLocated(By.xpath(xpath)), timeout);
};

export const findElementByClass = async (driver: WebDriver, classname: string, timeout = 2000) => {
  return await driver.wait(until.elementLocated(By.className(classname)), timeout);
};

export const findElementByTagName = async (driver: WebDriver, tag: string, timeout = 2000) => {
  return await driver.wait(until.elementLocated(By.tagName(tag)), timeout);
};

export const findElementsById = async (driver: WebDriver, id: string) => {
  return  await driver.findElements(By.id(id));
};

export const findElementsByName = async (driver: WebDriver, name: string) => {
  return  await driver.findElements(By.name(name));
};

export const findElementsByCss = async (driver: WebDriver, css: string) => {
  return  await driver.findElements(By.css(css));
};

export const findElementsByXpath = async (driver: WebDriver, xpath: string) => {
  return  await driver.findElements(By.xpath(xpath));
};

export const findElementsByClass= async (driver: WebDriver, classname: string) => {
  return  await driver.findElements(By.className(classname));
};

export const findElementsByTagName= async (driver: WebDriver, tagName: string) => {
  return  await driver.findElements(By.tagName(tagName));
};

 

/modules/selenium/sleepHandler.ts

export const sleep = (ms : number) => {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

 

 

/modules/index.ts

export * from "./db/expireHandler"

export * from "./selenium/alertHandler"
export * from "./selenium/cookieHandler"
export * from "./selenium/driverHandler"
export * from "./selenium/fileHandler"
export * from "./selenium/jqueryHandler"
export * from "./selenium/navigationHandler"
export * from "./selenium/popupHandler"
export * from "./selenium/findHandler"
export * from "./selenium/sleepHandler"
728x90
반응형
728x90
반응형

1. node-json-db 와 shortid 를 설치해준다. 

$ npm i node-json-db shortid @types/shortid --save

 

2. config 폴더 아래 db 설정을 해준다.

/config/nodejsondb.ts

import { JsonDB } from 'node-json-db';
import { Config } from 'node-json-db/dist/lib/JsonDBConfig'

const db = new JsonDB(new Config("myDataBase", true, false, '/'));

db.push('/one/lastdate', "");
db.push('/one/data',[]);

export {db}

 

3. 라우터에 결과값을 저장하는 메소드를 사용해준다.

추가로 검색한 데이터는 로그로 남겨둔다.

/routers/one.ts

import express, { Request, Response, NextFunction } from 'express';
var router = express.Router();

// middlewares
import { httpLoggingMiddleware } from '../middlewares';

// logger
import {
    loggerHttp,
    loggerDebug,
    loggerError,
    loggerInfo 
    } from '../config/winston';

// selenium
import webdriver from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome';

// node-json-db
import { db } from '../config/nodejsondb';

router.get('/',httpLoggingMiddleware, async function (req: Request, res: Response, next: NextFunction) {
    // 웹드라이버 설정
    let driver = await new webdriver.Builder()
    .forBrowser('chrome')
    .setChromeOptions(new chrome.Options().windowSize({width: 1920, height: 1080}))
    .setChromeService(new chrome.ServiceBuilder(process.env.CHROMEDRIVER_PATH))
    .build()
    try {

        // 브라우저에 접속
        await driver.get('https://typo.tistory.com/')

        // 현재 주소 가져오기
        const text = await driver.getCurrentUrl();

        // 저장 데이터
        const savedata = {
            id : shortid.generate(),
            date : new Date,
            result : text
        }

        // 데이터베이스에 저장 
        db.push('/one[]', savedata);

        // log 저장
        loggerInfo.info(JSON.stringify(savedata))

        res.send({success: true, result : text});
    }
    catch(Err) {
        loggerError.info(JSON.stringify(Err))
        console.log(Err)
        driver.close();
        res.send({success: false, result : Err})
    }
});

export default router;

 

여기서 중요한 점은 db에서 접근하는 경로 "/one" 와 라우터의 경로인 "/one"( '/'제외 )의 문자열를 일치하게 만들었다는 점이다.

이 방법으로 우린 미들웨어에서 라우터의 경로에 맞게 db에 손쉽게 접근할 수 있다. ( request.originalUrl 사용 )

아직은 이해가 안 될 수 있지만 차근차근 알게 될 것이다.

 

보통 크롤링 api 서버를 열어두고 제한을 안두면 무분별하게 라우터에 접속해서 userAgent 또는 접속 IP가 막힐 수도 있다.

때문에 userAgent를 바꿔주거나 IP를 우회하는 방법도 있지만 살짝 불법 느낌이 있어서 여기서는 전에 접속했던 시간을 비교해서 5분 간격으로 크롤링이 가능하게끔 만들어보겠다.

 

방법은 미들웨어에서 url path와 db path를 비교해서 전에 크롤링했던 데이터의 시간을 보고, 5분이 넘었는지 안넘었는지 확인 후

만약 5분이 안지난 상태면 res.send 메소드를 사용해 실패 메세지를 전송하도록 하겠다.

 

4. timeInterval 미들웨어를 만들어준다.

/middlewares/timeInterval.ts

import express, { Request, Response, NextFunction } from 'express';

const {   
    loggerInfo,
    loggerError,
    loggerHttp,
    loggerDebug,
 } = require('../config/winston');

// node-json-db
import { db } from '../config/nodejsondb';

// module
import { expireHandler } from '../modules';

export const timeIntervalMiddleware = async (req: Request, res: Response, next: NextFunction) => {
    try {
        // url
        let url = req.originalUrl;

        // 30분이 지난 데이터는 지워줌
        expireHandler(url);

        // 전체 데이터베이스 조회
        const data = await db.getData('/');

        // 처음 크롤링 할 때는 key값 ( 데이터베이스 주소 )이 없는 상태이므로 패스
        // 라우터 경로에서 '/'를 뺀 값이랑 데이터베이스의 키 값이랑 같다.
        if(!Object.keys(data).includes(url.split('/')[1])  || !(await db.getData(url + "[-1]"))) {
            next();
        }
        // 데이터가 존재할 경우 시간을 현 시간과 비교함
        else {
        	expireHandler(url);
        
            // 현재 시간
            let nowTime = new Date()
            let beforeTime = new Date(await (await db.getData(url + "[-1]")).date); // 제일 최신 데이터의 조회 시간
            let diff = (nowTime.getTime() - beforeTime.getTime()) / (1000*60); // 분 으로 계산한다.
            // 전 시간과 비교하여 차이가 5분 미만일 때
            if (diff < 5) {
                res.send({success: false, result: "5분 미만입니다."})
            }
            else {
                next();
            }
        }
        // next();
    }
    catch (Err) {
        console.log(Err)
        loggerError.info(Err)
        res.send({ success: false, result: "timeInterverError" });
    }
}

 

/middlewares/index.ts

export * from "./winston"
export * from "./timeInterval"

 

/routers/one.ts

...

// middlewares
import { httpLoggingMiddleware, timeIntervalMiddleware } from '../middlewares';

...

router.get('/', timeIntervalMiddleware ,httpLoggingMiddleware, async function (req: Request, res: Response, next: NextFunction) {

...

 

5. 서버를 실행하고 브라우저를 켜보자.

 

크롤링이 시작하기도 전에 실패 메세지가 도착했다.

 

6. 일정 시간이 지난 데이터는 지워주는 모듈을 만든다.

node-json-db 에는 expire을 설정해주는 메소드가 없는거 같다. 때문에 일정 시간이 지난 데이터를 지워주는 모듈을 만들어봤다.

모듈화에 대해선 다음 포스트에서 제대로 다루겠다.

 

/modules/db/expireHandler.ts

// node-json-db
import { db } from '../../config/nodejsondb';

// logger
import { loggerError } from '../../config/winston';

export const expireHandler = async (dbPath: string) => {
    try {
        let nowTime = new Date(); // 현재 시간

        // 데이터 베이스 안 데이터들
        const datalist = await db.getData(dbPath);

        let index = 0; // 기준점이 되는 index

        // 데이터 매핑 및 시간 차이 계산해서 30분 지난 데이터는 삭제
        await datalist.map(async (item : any, index2: number) => {
            let beforeTime =  new Date(item.date); // 데이터들 저장된 시간
            let diff = (nowTime.getTime() - beforeTime.getTime()) / (1000*60); // 데이터들 시간 차이
            // 저장된 지 30분이 지나면 index 체크
            if(diff > 30) {
                index = index + 1;
            }
        })

         // 설정한 시간을 기준점으로 잡고  그 이전에 만든 데이터를 다 삭제한다. (앞에서부터 shift)
        for (let i = 0; i < index; i ++) {
            await db.delete(dbPath + '/data[0]');
        }
    }
    catch(Err) {
        loggerError.info(JSON.stringify(Err))
    }
}

 

/modules/index.ts

export * from "./db/expireHandler"

 

 

미들웨어에 적용시킨다.

/middlewares/timeInterval.ts

...

// module
import { expireHandler } from '../modules';

export const timeIntervalMiddleware = async (req: Request, res: Response, next: NextFunction) => {
    try {
        // 30분이 지난 데이터는 지워줌
        expireHandler(req.originalUrl)
        
...
728x90
반응형
728x90
반응형

 

1. 제 어느정도 세팅했으니 selenium 을 사용해보자. 먼저 설치한다.

$ npm install --save @types/selenium-webdriver selenium-webdriver

 

2. 크롬드라이버의 위치를 .env 파일에 추가해준다. ( 환경 변수 설정 )

/.env

CHROMEDRIVER_PATH="C:\\Users\\Administrator\\Downloads\\chromedriver_win32\\chromedriver.exe"

 

3. one.ts 라우터 파일에 다음과 같이 현재 주소를 가져오는 로직을 구성해준다.

/routers/one.ts

import express, { Request, Response, NextFunction } from 'express';
var router = express.Router();

// middlewares
import { httpLoggingMiddleware } from '../middlewares'

// logger
import {
    loggerHttp,
    loggerDebug,
    loggerError,
    loggerInfo 
    } from '../config/winston';

// selenium
import webdriver from 'selenium-webdriver'
import chrome from 'selenium-webdriver/chrome'

router.get('/',httpLoggingMiddleware, async function (req: Request, res: Response, next: NextFunction) {
    try {
        // 웹드라이버 설정
        let driver = await new webdriver.Builder()
        .forBrowser('chrome')
        .setChromeOptions(new chrome.Options().windowSize({width: 1920, height: 1080}))
        .setChromeService(new chrome.ServiceBuilder(process.env.CHROMEDRIVER_PATH))
        .build()

        // 브라우저에 접속
        await driver.get('https://typo.tistory.com/')

        // 현재 주소 가져오기
        const text = await driver.getCurrentUrl();

        // 현재 주소 출력
        console.log(text)

        res.send({success: true, result : text});
    }
    catch(Err) {
        logger.error(JSON.stringify(Err))
        console.log(Err)
        res.send({success: false, result : Err})
    }
});

export default router;

 

4. 서버를 실행하고 localhost:1234/one 로 접속한다.

정상적으로 뜨는 것을 확인할 수 있다.

728x90
반응형
728x90
반응형

Selenium을 파이썬에서만 쓸 수 있을 줄 알았는데 NodeJS에서도 된다는 말을 듣고 javascript로 만들어볼까 한다.

 

1. Chrome 을 다운로드한다.

https://www.google.com/chrome/index.html

 

Chrome 웹브라우저

더욱 스마트해진 Google로 더 간편하고 안전하고 빠르게.

www.google.com

 

2. 아래 사진처럼 크롬 버전을 익혀둔다.

3. 크롬 버전을 확인했으면 앞자리 수에 맞는 크롬 드라이버를 설치한다.

 

ChromeDriver - WebDriver for Chrome - Downloads

Current Releases If you are using Chrome version 104, please download ChromeDriver 104.0.5112.29 If you are using Chrome version 103, please download ChromeDriver 103.0.5060.53 If you are using Chrome version 102, please download ChromeDriver 102.0.5005.61

chromedriver.chromium.org

 

4. 윈도우로 설치해준다. ( 버전은 다를 수 있습니다. )

 

5. 압축을 해제한 후 해당 파일 경로를 알아둔다. ( 나중에 셀레니움에서 사용 )

 

 

5. NodeJS와 VSCode를 설치한다.

https://nodejs.org/ko/download/

 

다운로드 | Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

개인적으로 생각하는 쓰기 좋은 vscode typescript 확장들

  1. ES7+ React/Redux/React-Native snippets
  2. ESLint
  3. Live Server
  4. Prettier

 

6. npm init 및 필요 구성 모듈을 설치해준다.

$ npm init
$ npm i express typescript ts-node nodemon @types/node @types/express

 

7. typscript를 위한 옵션을 설정하기 위해 다음 명령어를 입력해준다.

$ npx tsc --init

 

/tsconfig.json

{
  "compilerOptions": {
    "target": "es6", // 어떤 버전으로 컴파일할지 작성 
    "module": "commonjs", //어떤 모듈 방식으로 컴파일할지 설정
    "outDir": "./dist",	//컴파일 후 js 파일들이 생성되는 곳
    "rootDir": ".",	//루트 폴더
    "strict": true,	//strict 옵션 활성화
    "moduleResolution": "node",	//모듈 해석 방법 설정: 'node' (Node.js)
    "esModuleInterop": true,
     "jsx": "react"
  }
}

 

8. app.ts 파일을 만든다.

/app.ts

import express, { Request, Response, NextFunction } from 'express';

const app = express();


app.get('/welcome', (req: Request, res: Response, next: NextFunction) => {
    res.send('welcome!');
});

app.listen('1234', () => {
    console.log(`
  ################################################
  🛡️  Server listening on port: 1234🛡️
  ################################################
`);
});

 

9. pakage.json 파일을 수정해준다.

...
"scripts": {
    "start": "node dist/app.js", 
    "build": "tsc -p .", 
    "dev": "nodemon --watch \"src/**/*.ts\" --exec \"ts-node\" app.ts"
  }
 ...
  • build : typscript 파일을 js 파일로 컴파일해준다.
  • start : 컴파일한 js 파일로 서버를 실행한다.

 

10. 빌드하고 시작해본다.

$ npm run build
$ npm run start

 

정상적으로 뜨는 것을 확인할 수 있다.

 

또한 내가 express를 쓸 때 기본적으로 사용하는게 body-parser와cookie-parser, dotenv인데, 자세한 내용은 검색하면 나온다.

 

나중에 json 형식으로 통신할 때나 쿠키를 사용할 때, 중요한 정보를 환경 변수로 사용할 때 대비하자.

 

11. body-parser , cookie-parser, dotenv를 설치한다.

$ npm install --save body-parser @types/body-parser cookie-parser @types/cookie-parser dotenv

 

 

12. app.ts 파일을 수정해준다.

import express, { Request, Response, NextFunction } from 'express';

// parser
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';

// dotenv
import dotenv from 'dotenv'
dotenv.config();

const app = express();

app.use(bodyParser.json())
app.use(cookieParser())


app.get('/welcome', (req: Request, res: Response, next: NextFunction) => {
    res.send('welcome!');
});

app.listen('1234', () => {
    console.log(`
  ################################################
  🛡️  Server listening on port: 1234🛡️
  ################################################
`);
});

 

13. 좀 더 짜임새있는 구조를 위해 미리 폴더들을 만들어두자.

14. routers 밑에 파일을 만들고, app.ts 파일을 수정해준다.

/routers/one.ts

import express, { Request, Response, NextFunction } from 'express';
var router = express.Router();

router.get('/', async function (req: Request, res: Response, next: NextFunction) {
    res.send('welcome!');
});

export default router;

 

/app.ts

import express, { Request, Response, NextFunction } from 'express';

// parser
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';

// router
import oneRouter from './routers/one'

const app = express();

app.use(bodyParser.json())
app.use(cookieParser())

app.use('/one',oneRouter)

app.listen('1234', () => {
    console.log(`
  ################################################
  🛡️  Server listening on port: 1234🛡️
  ################################################
`);
});

 

15. logging 미들웨어를 만들기 위해 다음 명령어로 설치해준다.

$ npm install winston @types/winston winston winston-daily-rotate-file

 

/config/winston.ts

import winston, { info } from 'winston';
import winstonDaily from 'winston-daily-rotate-file';

const logDir = 'logs';  // logs 디렉토리 하위에 로그 파일 저장
const { combine, timestamp, printf } = winston.format;

// Define log format
const logFormat = printf(info => {
  return `${info.timestamp} ${info.level}: ${info.message}`;
});

/*
 * Log Level
 * error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
 */

const loggerError = winston.createLogger({
  format: combine(
    timestamp({
      format: 'YYYY-MM-DD HH:mm:ss',
    }),
    logFormat,
  ),
  transports: [
    // error 레벨 로그를 저장할 파일 설정
    new winstonDaily({
      level: 'info',
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/error',  // error.log 파일은 /logs/error 하위에 저장 
      filename: `%DATE%.error.log`,
      maxFiles: 30,
      watchLog: true,
      zippedArchive: true,
    }),
  ],
});

const loggerInfo = winston.createLogger({
  format: combine(
    timestamp({
      format: 'YYYY-MM-DD HH:mm:ss',
    }),
    logFormat,
  ),
  transports: [
    // info 레벨 로그를 저장할 파일 설정
    new winstonDaily({
      level: 'info',
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/info',
      filename: `%DATE%.info.log`,
      maxFiles: 30,  // 30일치 로그 파일 저장
      watchLog: true,
      zippedArchive: true, 
    }),
  ],
});

const loggerHttp = winston.createLogger({
  format: combine(
    timestamp({
      format: 'YYYY-MM-DD HH:mm:ss',
    }),
    logFormat,
  ),
  transports: [
    // http 레벨 로그를 저장할 파일 설정
    new winstonDaily({
      level: 'info',
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/http',
      filename: `%DATE%.http.log`,
      maxFiles: 30,  // 30일치 로그 파일 저장
      watchLog: true,
      zippedArchive: true, 
    }),
  ],
});

const loggerDebug = winston.createLogger({
  format: combine(
    timestamp({
      format: 'YYYY-MM-DD HH:mm:ss',
    }),
    logFormat,
  ),
  transports: [
    // debug 레벨 로그를 저장할 파일 설정
    new winstonDaily({
      level: 'info',
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/debug', 
      filename: `%DATE%.debug.log`,
      maxFiles: 30,
      watchLog: true,
      zippedArchive: true,
    }),
  ],
});


// Production 환경이 아닌 경우(dev 등) 
if (process.env.NODE_ENV !== 'production') {
  loggerInfo.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),  // 색깔 넣어서 출력
      winston.format.simple(),  // `${info.level}: ${info.message} JSON.stringify({ ...rest })` 포맷으로 출력
    )
  }));
}

// Production 환경이 아닌 경우(dev 등) 
if (process.env.NODE_ENV !== 'production') {
  loggerError.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),  // 색깔 넣어서 출력
      winston.format.simple(),  // `${info.level}: ${info.message} JSON.stringify({ ...rest })` 포맷으로 출력
    )
  }));
}

if (process.env.NODE_ENV !== 'production') {
  loggerHttp.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),  // 색깔 넣어서 출력
      winston.format.simple(),  // `${info.level}: ${info.message} JSON.stringify({ ...rest })` 포맷으로 출력
    )
  }));
}

if (process.env.NODE_ENV !== 'production') {
  loggerDebug.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),  // 색깔 넣어서 출력
      winston.format.simple(),  // `${info.level}: ${info.message} JSON.stringify({ ...rest })` 포맷으로 출력
    )
  }));
}

export { 
  loggerInfo,
  loggerError,
  loggerHttp,
  loggerDebug,
 };

 

/middlewares/index.ts

export * from "./http"

 

/middlewares/http.ts

import express, { Request, Response, NextFunction } from 'express';
import {   
    loggerInfo,
    loggerError,
    loggerHttp,
    loggerDebug,
 } from '../config/winston'

interface LOGSTR {
    url : string;
    method : string;
    query? : Object;
    body? : Object;
}

export const httpLoggingMiddleware = async (req: Request, res: Response, next: NextFunction) => {

    let logStr: LOGSTR = {
        url : "",
        method: "",
        query: "",
        body: "",
    }

    try {
        // 접속 경로
        logStr.url = req.originalUrl;

        // 메소드
        logStr.method = req.method;

        switch (req.method) {
            case 'GET':
                logStr.query = req.query;
                break;
            case 'POST':
                logStr.body = req.body;
                break;
            case 'PATCH':
                logStr.body = req.body;
                break;
            case 'DELETE':
                logStr.query = req.query;
                break;
        }

        loggerHttp.info(JSON.stringify(logStr))

        next();
    }
    catch (Err) {
        loggerError.info(Err)
        res.send({ success: false });
    }
}

 

/routers/one.ts

import express, { Request, Response, NextFunction } from 'express';
var router = express.Router();

// middlewares
import { httpLoggingMiddleware } from '../middlewares'

// logger
import {
    loggerHttp,
    loggerDebug,
    loggerError,
    loggerInfo 
    } from '../config/winston';

router.get('/',httpLoggingMiddleware, async function (req: Request, res: Response, next: NextFunction) {
    res.send('welcome!');
});

export default router;

 

 

16. /one 라우터에 접근해보고 로그 데이터가 잘 생성되는지 확인한다.

get 메소드는 브라우저 url 창에 입력해도 된다.

728x90
반응형

728x90
반응형

리눅스 서버로 실행할 때는 로컬에 vscode를 설치한 후 원격으로 접속해서 연결했지만 윈도우 서버에서는 인스턴스 안에 직접 vscode 를 설치하고 실행하였기에 포트포워딩을 로컬호스트로 해주지 않는다.

 

이를 위해서 ( 외부에서 접속하기 위해서 ) 해야될 것이 2가지 있는데,

 

1. 하나는 이미 해두었던 settings.py 파일에 아래 구문을 추가하는 것.

ALLOWED_HOSTS = ['*']

 

이 구문을 추가해주면 123.45.67.89:8000/ 이런식으로 외부에서 접속이 가능하다. 물론 나중에는 수정해야겠지만 지금은 테스트니까

 

2. 윈도우 서버에서는 방화벽을 따로 설정해주어야 한다. 탐색기에서 firewall을 검색하고 Windows Defender FireWall에 들어간다.

 

 

3. 인바운드에 기존에 있던 80에 관련된 규칙들에 Enabled를 활성화해주고 8000번 포트( django )를 열어준다. 

 

 

이 준비까지 다 되었으면 내 컴퓨터 로컬호스트에서 인터넷이나 postman 으로 응답을 확인할 수 있다.

 

4. 이제 셀레니움을 설치해서 사용해보자.

$ pip install selenium

 

5. 라우터에 다음 내용을 추가해준다. 여기서 chromedriver 는 전 포스트에서 설치했던 실행 파일의 위치이다.

/chwideukapp/views.py

from time import sleep
from rest_framework.views import APIView
from rest_framework.response import Response
from selenium import webdriver

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument("--single-process")
chrome_options.add_argument("--disable-dev-shm-usage")
path = "C:\\Users\\Administrator\\Downloads\\chromedriver_win32\\chromedriver.exe"
driver = webdriver.Chrome(path, chrome_options=chrome_options)


class ChwideukRouter(APIView):
    def get(self, request):
        driver.get("https://typo.tistory.com/")

        print("+" * 100)
        print(driver.title)   # 크롤링한 페이지의 title 정보
        print(driver.current_url)  # 현재 크롤링된 페이지의 url
        print("-" * 100)

        title = driver.title
        url = driver.current_url

        driver.close()
        return Response({'title': title, 'url': url})

    def post(self, request):
        driver.get("https://typo.tistory.com/")

        print("+" * 100)
        print(driver.title)   # 크롤링한 페이지의 title 정보
        print(driver.current_url)  # 현재 크롤링된 페이지의 url
        print("-" * 100)

        title = driver.title
        url = driver.current_url

        driver.close()
        return Response({'title': title, 'url': url})

 

6. 서버를 키고 postman으로 요청을 보내보자.

$ python mnage.py runserver 0.0.0.0:8000

정상적으로 뜨는 것을 확인할 수 있다.

728x90
반응형
728x90
반응형

전 포스트에서 프로젝트까지 만들었었다.

 

1. settings.py 파일에 rest_framework 를 추가해준다.

ALLOWED_HOSTS = ['*']

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework'
]

 

2. 새 마이그레이션을 생성하고 슈퍼유저를 생성해준다.

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser

 

3. 앱을 하나 만들어준다. ( 이름은 상관없습니다. )

$ python manage.py startapp chwideukapp

 

4. apiServer 폴더 안 settings.py 에 만든 앱을 추가해준다.

/apiServer/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'chwideukapp'
]

 

5. 새로 만든 앱의 models.py 파일을 수정해준다.

/chwideukapp/models.py

from django.db import models


class ChwideukModel(models.Model):
    title = models.CharField(max_length=70, default='')
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

 

6. 새 마이그레이션을 생성한다.

$ python manage.py makemigrations
$ python manage.py migrate

 

7. serializers.py 파일을 만든다.

/chwideukapp/serializers.py

from rest_framework import serializers
from .models import ChwideukModel


class ChwideukSerializer(serializers.ModelSerializer):
    class Meta:
        model = ChwideukModel
        fields = ['title']

 

8. views.py 파일을 수정해준다. 

from rest_framework.views import APIView
from rest_framework.response import Response


class ChwideukRouter(APIView):
    def get(self, request):
        return Response({'success': True})

    def post(self, request):
        return Response({'success': False})

 

9. apiServer 폴더의 urls.py에 방금 만든 라우터를 추가해준다.

from django.contrib import admin
from django.urls import include, path

from chwideukapp.views import ChwideukRouter


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/chwideuk/', ChwideukRouter.as_view())
]

 

728x90
반응형
728x90
반응형

 

1. aws에서 Windows 인스턴스를 만들고 고정 IP를 만들어주어 연결해준다.

 

AWS LightSail(1) - 인스턴스 생성하기

정말 빠르게 AWS LightSail로 인스턴스를 만들어보자. Lightsail에 관하여 https://lightsail.aws.amazon.com/ls/docs/ko_kr/all 1. aws 사이트에 회원가입, 로그인을 하고 Lightsail 서버를 찾아서 들어간다. 2...

typo.tistory.com

 

2. 맥북 환경이면 Microsoft Remote Desktop 앱을 깔아서 아이디와 비밀번호 연결 후 접속해준다.

인스턴스 초기 아이디 비밀번호는 aws 에서 확인하실 수 있습니다.

( aws 에서 RDP로 접속하니 좀 느렸습니다. )

 

3. aws 에서는 윈도우 서버를 만들고 파일을 다운받으려 할 때 에러가 뜬다. 아래처럼 해주자.

  1. EC2 Windows 인스턴스에 연결합니다.
  2. Windows 시작 메뉴를 열고 서버 관리자를 엽니다.
  3. EC2 Windows 인스턴스에서 실행 중인 Windows Server 버전에 대한 지침을 따릅니다.
    Windows Server 2012 R2, Windows Server 2016 또는 Windows Server 2019
    : 탐색 창에서 [Server Manager]를 선택합니다. [로컬 서버]를 선택합니다. [IE 보안 강화 구성]에서 [켜기]를 선택합니다.
    Windows Server 2008 R2: 탐색 창에서 [Server Manager]를 선택합니다. [서버 요약 - 보안 정보] 섹션에서 [IE ESC 구성]을 선택합니다.
  4. [관리자]에 대해 [끄기]를 선택합니다.
    [사용자]에 대해 [끄기]를 선택합니다.
    [확인]을 선택합니다.
  5. 서버 관리자를 닫습니다.

 

Internet Explorer를 사용하여 파일을 다운로드할 수 있도록 EC2 Windows 인스턴스 구성

인터넷에서 Amazon Elastic Compute Cloud(Amazon EC2) Windows 인스턴스에 타사 소프트웨어를 다운로드해야 합니다. Internet Explorer 보안 구성이 내 시도를 차단하고 있습니다. 다운로드를 활성화하려면 어떻

aws.amazon.com

 

4. Chrome 을 다운로드한다.

https://www.google.com/chrome/index.html

 

Chrome 웹브라우저

더욱 스마트해진 Google로 더 간편하고 안전하고 빠르게.

www.google.com

 

5. 아래 사진처럼 크롬 버전을 익혀둔다.

6. 크롬 버전을 확인했으면 앞자리 수에 맞는 크롬 드라이버를 설치한다.

 

ChromeDriver - WebDriver for Chrome - Downloads

Current Releases If you are using Chrome version 104, please download ChromeDriver 104.0.5112.29 If you are using Chrome version 103, please download ChromeDriver 103.0.5060.53 If you are using Chrome version 102, please download ChromeDriver 102.0.5005.61

chromedriver.chromium.org

 

7. 윈도우로 설치해준다. ( 버전은 다를 수 있습니다. )

 

8. 압축을 해제한 후 해당 파일 경로를 알아둔다. ( 나중에 셀레니움에서 사용 )

 

9. 개발환경을 위해 Vscode와 파이썬을 설치해준다.

 

 

Visual Studio Code - Code Editing. Redefined

Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications.  Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.

code.visualstudio.com

 

 

Download Python

The official home of the Python Programming Language

www.python.org

 

파이썬을 설치할 때 환경변수를 지정해두면 좋다.

 

10. 작업하길 원하는 위치에 폴더를 만들고 가상환경을 만들어준다.

$ python -m venv example

 

11. django와 djangorestframework를 설치해준다.

$ pip install django
$ pip install djangorestframework markdown
$ django-admin startproject apiServer

 

12. VSCode 에서 보면 파일 구조는 이럴 것이다.

 

 

 

참고로 아래 확장 설치하시면 편합니다 

728x90
반응형
728x90
반응형

1. 먼저 인스턴스에 접속해서 ssh 터미널을 키고 아래 명령어를 입력한다.

$ sudo apt update
$ sudo apt install ubuntu-desktop
$ sudo apt install tightvncserver
$ sudo apt install gnome-panel gnome-settings-daemon metacity nautilus gnome-terminal

 

2. vncserver 를 실행한다. ( 비밀번호를 기억해두어야함 )

$ vncserver

 

3. vnc config 파일을 수정한다.

$ sudo vi ~/.vnc/xstartup

~/.vnc/xstartup

#!/bin/sh
# Uncomment the following two lines for normal desktop:
# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc
[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey vncconfig -iconic &
x-terminal-emulator -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
x-window-manager &
gnome-panel &
gnome-settings-daemon &
metacity &
nautilus &
gnome-terminal &

 

4. vncserver를 재시작 해준다.

$ vncserver -kill :1
$ vncserver

 

5. 인바운드 규칙을 수정하여 5901 포트를 열어준다

 

6. Finder 에서 command + k 로 VNC 실행 후 ip 주소와 5901 포트까지 설정해서 켜준다.

 

- 성공화면

728x90
반응형
728x90
반응형

1. 셀레니움을 설치한다.

$ sudo pip3 install selenium

 

2. 라우터에 다음 내용을 추가해준다.

/chwideukapp/views.py

from time import sleep
from rest_framework.views import APIView
from rest_framework.response import Response
from selenium import webdriver

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument("--single-process")
chrome_options.add_argument("--disable-dev-shm-usage")
path = '/usr/bin/chromedriver'
driver = webdriver.Chrome(path, chrome_options=chrome_options)


class ChwideukRouter(APIView):
    def get(self, request):
        return Response({'success': True})

    def post(self, request):
        driver.get("https://typo.tistory.com/")

        print("+" * 100)
        print(driver.title)   # 크롤링한 페이지의 title 정보
        print(driver.current_url)  # 현재 크롤링된 페이지의 url
        print("-" * 100)

        title = driver.title
        url = driver.current_url

        driver.close()
        return Response({'title': title, 'url': url})

 

옵션을 추가한 이유는 아래와 같은 에러가 떠서 그렇습니다.

unknown error: DevToolsActivePort file doesn't exist

 

3. 서버를 실행하고 postman으로 요청을 보내보자.

postman

 

역시 개발이란 코딩도 힘든데 환경 세팅하는게 더 힘들다.. 수많은 삽질을 한 결과 드디어 기본 틀을 작성하게 되었다.

이 뒤부턴 driver로 원하는 페이지로 이동해서 원하는 데이터를 수집하는게 끝이라 여기서 django rest framework 기초 프로젝트는 종료하겠다. 아래는 selenium 사용에 있어 필요한 메소드를 잘 정리해놓은 블로그이다.

 

https://pythondocs.net/selenium/%EC%85%80%EB%A0%88%EB%8B%88%EC%9B%80-%ED%81%AC%EB%A1%A4%EB%9F%AC-%EA%B8%B0%EB%B3%B8-%EC%82%AC%EC%9A%A9%EB%B2%95/

 

셀레니움 크롤러 기본 사용법 - 뻥뚫리는 파이썬 코드 모음

셀레니움 전반에 관하여 간략하게 정리한다. 이 문서는 셀레니움 버전 3 기준이다. 최근 4버전이 출시되었으나 사용방법이 약간 다르니 이 부분을 확인하길 바란다. 사용 방법이나 예시는 따로

pythondocs.net

 

 

728x90
반응형

+ Recent posts