본문 바로가기
IT/Serverless

네이버 블로그 파워링크 클릭

by DOSGamer 2019. 7. 30.
반응형

네이버 블로그의 파워링크를 클릭하는 프로그램 만들기

네이버 블로그를 운영하면 광고 수익을 위해서 애드포스트를 추가한다

애드포스트를 추가하면 아래와 같이 파워링크 광고 영역이 생긴다

(티스토리는 구글 애드센스를 광고 배너로 사용)

파워링크를 클릭하는 프로그램을 만드려고 한다

네이버 블로그에 나타나는 파워링크 광고 영역

개발환경

Serverless Framework : serverless 로 AWS Lambda 배포할 수 있게

Nodejs : javascript 로 구현할 꺼라서

puppeteer : 크롬브라우저를 컨트롤 하려고 사용

chrome-aws-lambda : AWS Lambda 에 크롬브라우저를 용량을 줄여서 올리기 위해서 사용

 

개발환경설정

Step 1. 프로그램 설치

 

nvm 으로 nodejs 를 설치

nvm install 10.16.0
nvm use 10.16.0

serverless framework 설치

npm i -g serverless

Step 2. 라이브러리 설치

개발폴더 만들고

mkdir monitoring-sns-blog

nodejs init 해주고

npm init -y

폴더에 독립적으로 설치

 npm i chrome-aws-lambda
 npm i puppeteer

package.json 파일

{
  "name": "monitoring-sns-blog",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "skyksit@gmail.com",
  "license": "ISC",
  "engines": {
    "node": ">= 8.10.0"
  },
  "dependencies": {
    "chrome-aws-lambda": "^1.18.1",
    "puppeteer": "^1.18.1"
  },
  "devDependencies": {}
}

Step 3. serverless framework 설정

sls create -t aws-nodejs

소스개발

serverless.yml 파일

service: monitoring-sns-blog

custom:
  defaultStage: dev
  defaultRegion: ap-northeast-2
  deploy_bucket_name: ${self:provider.stage}-source-bucket

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, self:custom.defaultStage}
  region: ${opt:region, self:custom.defaultRegion}
  logRetentionInDays: 14
  deploymentBucket:
    name: ${self:custom.deploy_bucket_name}
    serverSideEncryption: AES256
  deploymentPrefix: serverless
      
package:
  exclude:
    - node_modules/puppeteer/.local-chromium/**
    - node_modules/serverless/**

functions:
  clickBlog:
    handler: src/index.clickBlog
    environment:
      STAGE: ${self:provider.stage}
    memorySize: 1536
    timeout: 30
    events:
      - schedule: rate(15 minutes)
    tags:
      Application: monitoring
      Service: sns
      Environment: ${self:provider.stage}
      Owner: 김병철
      Role: service
      Team: platform

custom 에서 환경 / 리전 / serverless 소스를 넣을 버킷을 지정

provider 에서는

기본설정값은 option 으로 가져오고 없으면 custom 정보를 가져온다

로그 보관기간은 14일로 설정하고 소스를 버킷에 넣을 때 AES256 보안적용하고 serverless 라는 prefix 만들어서 거기에 넣는다

package 에서는 puppeteer 설치시에 깔리는 크롬브라우저 (140MB) 를 제외시켜서 람다에 올리고

serverless framework 이 깔려 있어도 올라가지 않도록 exclude 로 제외해서 소스를 package 하게 한다

functions 가 진정한 람다 관련 설정으로 

src 폴더 밑의 index.js 파일내에 clickBlog 라는 함수를 람다 함수로 만들고

환경변수는 STAGE 라고 환경 설정해주고 (그냥 습관처럼)

memorySize 는 크롬브라우저가 실행해야 하니 1.5GB 로 설정

timeout 도 30초 로 해준다

events 를 걸어서 15분마다 실행되도록 AWS Cloudwatch 의 cron 서비스를 엮어준다

tag 를 걸어서 비용 분석을 할 수 있게 한다

 

폴더구조는 엄청단순하다

 

src/index.js 파일내용

'use strict'
const chromium = require('chrome-aws-lambda');

if(!process.env.STAGE) process.env.STAGE = 'local';

const blogUrl = "https://blog.naver.com/PostView.nhn?blogId=블로그URL";

module.exports.clickBlog = async (event) => {
    let browser = null;
    let page = null;
    let powerlinkpage = null;
    try {
        browser = await chromium.puppeteer.launch(await getChromeBrowserArgument());
        page = await browser.newPage();
        let blogTitle = await gotoBlog(page);
        console.log(`blogTitle = ${blogTitle}`);
        let dummyTitle = await powerlinkClick(page);
        console.log(`dummyTitle = ${dummyTitle}`);
    } catch (err) {
        console.error(err);
    } finally {
        if (page !== null) {
            for (let page of await browser.pages()) {
                await page.close({
                    "runBeforeUnload": true
                });
            }
            await browser.disconnect();
        }
    }
}

const getChromeBrowserArgument = async () => {
    let puppeteerArgument = null;
    if (process.env.STAGE === "local") {
        puppeteerArgument = {
            headless: false,
            defaultViewport: {
                width: 1280,
                height: 800
            },
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox',
            ],
            ignoreDefaultArgs: ['--disable-extensions'],
        };
    } else {
        puppeteerArgument = {
            args: chromium.args,
            defaultViewport: {
                width: 1280,
                height: 800
            },
            executablePath: await chromium.executablePath,
            headless: chromium.headless,
        };
    }
    return puppeteerArgument;
}

const gotoBlog = async (page) => {
    try {
        await page.setExtraHTTPHeaders({ 'Accept-Language': 'ko' });
        await page.goto(blogUrl, { waitUntil: "networkidle2" });
        return await page.title();
    } catch (err) {
        console.error(`gotoBlog ${err}`);
    }
}

const powerlinkClick = async (page) => {
    try {
        await page.evaluate(dis => {
            window.scrollBy(0, dis);
        }, 2000);
        await page.mouse.move(410,217);
        await page.mouse.click(410,217);
        await page.mouse.move(410,277);
        await page.mouse.click(410,277);
        await page.mouse.move(410,337);
        await page.mouse.click(410,337);
        await page.waitFor(2000);
    } catch (err) {
        console.error(`powerlinkClick ${err}`);
    }
    return await page.title();
}

 

메인함수는 간단하다

module.exports.clickBlog = async (event) => {
    let browser = null;
    let page = null;
    let powerlinkpage = null;
    try {
        browser = await chromium.puppeteer.launch(await getChromeBrowserArgument());
        page = await browser.newPage();
        let blogTitle = await gotoBlog(page);
        console.log(`blogTitle = ${blogTitle}`);
        let dummyTitle = await powerlinkClick(page);
        console.log(`dummyTitle = ${dummyTitle}`);
    } catch (err) {
        console.error(err);
    } finally {
        if (page !== null) {
            for (let page of await browser.pages()) {
                await page.close({
                    "runBeforeUnload": true
                });
            }
            await browser.disconnect();
        }
    }
}

 

1. 사용자 PC 이나 AWS 람다 환경이냐에 따라서 크롬브라우저를 다르게 실행한다

2. 크롬브라우저에서 new tab을 만들고

3. blogUrl 경로로 이동해서

4. powerlink 3개를 클릭해준다

5. 완료되면 page 를 전부 종료하고 크롬브라우저를 닫는다

 

const getChromeBrowserArgument = async () => {
    let puppeteerArgument = null;
    if (process.env.STAGE === "local") {
        puppeteerArgument = {
            headless: false,
            defaultViewport: {
                width: 1280,
                height: 800
            },
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox',
            ],
            ignoreDefaultArgs: ['--disable-extensions'],
        };
    } else {
        puppeteerArgument = {
            args: chromium.args,
            defaultViewport: {
                width: 1280,
                height: 800
            },
            executablePath: await chromium.executablePath,
            headless: chromium.headless,
        };
    }
    return puppeteerArgument;
}

getChromeBrowserArgument 는 로컬PC 에서는 크롬브라우저를 실행해주고

AWS Lambda 에서는 Lambda 기준의 크롬브라우저를 Headless 모드로 실행해준다 (AWS Lambda 는 linux OS 이다)

const powerlinkClick = async (page) => {
    try {
        await page.evaluate(dis => {
            window.scrollBy(0, dis);
        }, 2000);
        await page.mouse.move(410,217);
        await page.mouse.click(410,217);
        await page.mouse.move(410,277);
        await page.mouse.click(410,277);
        await page.mouse.move(410,337);
        await page.mouse.click(410,337);
        await page.waitFor(2000);
    } catch (err) {
        console.error(`powerlinkClick ${err}`);
    }
    return await page.title();
}

파워링크를 마우스 위치로 클릭하는 이유는

파워링크 주소를 크롤링하는 것을 방지하기 위해서 몇단계로 꼬아놨기에 읽어오는 것 까지는 되는데

데이터로 new tab 을 띄우는 것보다는 마우스 클릭으로 띄우는 게 집계를 속일 수 있지 않을까 해서 이렇게 해놨다

 

로컬테스트

테스트용 파일 /localtest/index.test.js 파일을 만들기

'use strict';
const lambda = require("../src/index");

let event = {}

lambda.clickBlog(event);

 

테스트 실행

node localtest/index.test.js

 

테스트를 실행하면 내 블로그 내용이 첫번째 tab 으로 뜨고 두번째 tab 부터 파워링크 사이트들이 떠요

종료되면 콘솔로그로 dummyTitle 하나 찍게 해놨습니다

 

배포

sls deploy -v --aws-profile AWS계정프로파일명

deploy 해주면

배포되는 로그

cloudformation 에서 리소스들이 만들어진걸 확인할 수 있고

 

람다에서 만들어진 것을 확인할 수 있다

 

테스트 이벤트 구성을 해서 테스트 해볼 수 있다

 

15분 마다 스케쥴로 실행될 것 입니다

 

자원삭제

sls remove --aws-profile AWS계정프로파일명

remove 한방이면 다 지워져요

소스 저장한 bucket 의 소스파일

로그그룹

스케쥴 정보

람다 서비스

실행용 Role 정보들

반응형