관리가 간편한 자동 글등록 봇 만들기 -디씨봇- (2)

By | 2019년 2월 26일

실제예제

실전예제로 대표적으로 자동등록 방지가 복잡하게 되어있는 커뮤니티 디씨인사이드에 등록을 해보자.

bot.js

"use strict"

const phantom = require('phantom');
const debug = console.log;

const timeout = ms => new Promise(res => setTimeout(res, ms))

var account = '[userid]';
var password = '[password]';

// 셀렉터 대기
async function waitForSelector(page, selector, timeOutMillis) {
    return new Promise((res)=>{
        var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000;
        var start = new Date().getTime();

        var timer = setInterval(async ()=>{
            //console.log('wait..');

            if (new Date().getTime() - start >= maxtimeOutMillis) {
                debug("'waitFor()' timeout");
                clearTimeout(timer);
                res(false);
            }

            var ret = await page.evaluate(function (selector) {
                return document.querySelectorAll(selector).length;
            }, selector);
            if (ret) {
                clearTimeout(timer);
                res(true);
            }
        }, 250);
    });
}

// 로그인
async function dc_login(page) {
    debug('--dc_login');
    var url = 'http://m.dcinside.com/auth/login?r_url=http%3A%2F%2Fm.dcinside.com%2Faside';
    const status = await page.open(url);
    var ret = false;

    ret = await waitForSelector(page, '#user_id');

    if (!ret) return false;

    await page.evaluate(function (account, password) {
        var form = document.getElementById('login_process');
        form.user_id.value = account;
        form.user_pw.value = password;
        document.getElementById('login_ok').click();
    }, account, password);

    ret = await waitForSelector(page, '.login-box');

    if (!ret) {
        debug('timeout!');
    }
    debug(ret);

    //await page.render('login.png')

    if (!ret) return false;

    return true;
}

// 글작성
async function dc_writer(page, gall, subject, memo) {
    debug('--dc_writer');
    var url = 'http://m.dcinside.com/write/'+gall;
    const status = await page.open(url);
    var ret = false;

    ret = await waitForSelector(page, '.gall-tit');
    if (!ret) {
        return 'timeout';
    }

    ret = await page.evaluate(function (selector) {
        return document.querySelectorAll(selector).length;
    }, '#name');
    if (ret) {
        return 'logout';
    }

    await page.evaluate(function (subject, memo) {
        var form = document.getElementById('writeForm');

        document.getElementById('subject').value = subject;
        document.getElementById('textBox').innerHTML = memo;
        document.getElementById('memo').value = memo;

        write_submit();
    }, subject, memo);

    await timeout(3000);

    //await page.render('write.png')

    return 'write';
}

// 메인루프
(async ()=>{
    const instance = await phantom.create();

    try {
        var ret = false;

        var page = await instance.createPage();
        ret = await dc_login(page);
        if (!ret) {
            debug('login fail');
            throw new Error('login fail');
        }
        await page.close();

        var running = true;
        while (running) {
            debug('--running');
            page = await instance.createPage();

            var gallid = '[갤러리ID]';
            var subject = '제목입니다.';
            var memo = '내용입니다.<br/>test content<br/>test content';

            var ret = await dc_writer(page, gallid, subject, memo);
            debug(ret);
            if (ret=='write') {
                // 성공
            }

            await page.close();
            await timeout(60 * 60 * 1000); //한시간 대기
        }

        debug('--end');
    } catch(e) {
        debug(e);
    }

    await instance.exit();
})();

아래의 변수에는 실제 사용하는 아이디/패스워드/갤러리ID를 입력한다.

var account = '[userid]';
var password = '[password]';
var gallid = '[갤러리ID]';

주요 함수 설명

timeout Pomise를 이용한 setTimeout이다. ms만큼 대기한다.

waitForSelector phantomjs page에 selector에 해당하는 DOM객체가 나타날때까지 대기한다. 주로 페이지 이동, UI액션후에 대기할때 사용하기위해 만들었다. timeOutMillis이 지나면 대기시간 초과로 실패한다.

page.evaluate phantomjs 에서 제공하는 함수로 webpage내에서 javascript를 실행한다. 코드상으로는 연결되서 동작하는 것처럼보이지만.. 사실상 함수를 텍스트로 전달하는식이다. (closure로 변수 접근불가) 따라서 변수를 별도로 넘겨줘야한다.

await page.evaluate(function (account, password) {
    var form = document.getElementById('login_process');
    form.user_id.value = account;
    form.user_pw.value = password;
    document.getElementById('login_ok').click();
}, account, password);

변수 account, password를 전달하기위해 evaluate의 2번째, 3번째 파라메터에 넣고 evaluate로 전달하는 자바스크립트 함수에서 넘겨받는다. 여기서 자바스크립트 함수인

function (account, password) {
    var form = document.getElementById('login_process');
    form.user_id.value = account;
    form.user_pw.value = password;
    document.getElementById('login_ok').click();
}

이 부분은 텍스트로 봐야한다. 브라우저내에 스크립트로 실행된다. dcinside.com에서 jquery를 사용하고 있으므로

function (account, password) {
    var form = $('#login_process')[0];
    form.user_id.value = account;
    form.user_pw.value = password;
    $('#login_ok').click();
}

이런식으로도 jquery도 사용가능하다.

동작함수

dc_login 로그인을 한다. 로그인 페이지로 이동하고 page.evaluate를 통해 자바스크립트로 아이디/패스워드를 입력하고 로그인 버튼을 클릭한다.

dc_writer 글을 작성한다. 글쓰기 페이지로 이동하고 page.evaluate를 통해 자바스크립트로 제목/내용을 입력하고 글쓰기버튼을 눌렀을때 실행되는 write_submit()를 호출하여 글을 등록한다.

메인루프

글을 작성하고 한시간을 대기하는 무한 loop구조다.

실행

node bot.js

node.js로 짜여진만큼 기타 모듈을 이용하여 (request,cheerio등) 정상적으로 등록되었는지 목록페이지를 읽어와서 확인을 하거나 다른 페이지의 캡쳐화면을 첨부파일로 추가하거나하는 기능을 쉽게 덧붙일 수 있다.

raspberry pi 이용

라즈베리파이를 이용하면 적은 비용으로 이용할 수 있다. 저전력으로 24시간 켜놔도 전기세 부담이 없다.
고맙게도 phantomjs를 라즈베리파이용으로 미리 컴파일해서 올려둔 프로젝트가 있으니 바로 clone받아 이용할 수 있다.

라즈베리파이로 포팅된 phantomjs
https://github.com/piksel/phantomjs-raspberrypi.git

1) stretch

git clone https://github.com/piksel/phantomjs-raspberrypi.git

2) jessie

git clone -b jessie https://github.com/piksel/phantomjs-raspberrypi.git

실행파일복사

ln -s phantomjs-raspberrypi/bin/phantomjs /usr/bin/phantomjs

or

mv phantomjs-raspberrypi /usr/local/
ln -s /usr/local/phantomjs-raspberrypi/bin/phantomjs /usr/bin/
phantomjs -v

한글폰트 설치

apt-get install fonts-nanum

node.js 설치

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - sudo apt-get install -y nodejs

or 

wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-armv6l.tar.xz
tar xzvf node-v10.15.1-linux-armv6l.tar.xz
cd node-v10.15.1-linux-armv6l
sudo cp -R * /usr/local/

12 thoughts on “관리가 간편한 자동 글등록 봇 만들기 -디씨봇- (2)

  1. 비밀이야ㅑㅏㅏ

    제목에 숫자를 자동으로 계속 바뀌게하려면 함수처리를 어떻게해야하나요

    1일차 2일차 이런식으로 추가하고싶어요

    Reply
    1. cook Post author

      날짜를 계산해서 일수를 구하면 되겠죠~ momment 같은거 이용하시면 좀 더 수월하겠고요

      2019-05-22 기준으로 현재 날짜를 빼면 일차를 구할수 있겠네요

      Reply
      1. 박경준

        –dc_writer에서
        write 문장만 출력되고 그 이상 진행되지 않습니다. ㅠㅠ

        Reply
        1. ㅁㄴㅇㄹ

          또한 글을 작성했음에도 불구하고 글자가 깨져 보입니다.

          Reply
          1. cook Post author

            글자가 깨지는건.. 소스 인코딩이 utf-8이 맞는지 확인을 해보아요~

        2. cook Post author

          어떤 상황에서 진행이 안되는건지 await page.render(‘screenshot.png’) 같은걸로 화면을 캡쳐해서 확인해보아요~

          Reply
  2. 디시

    안녕하세요 글등록프로그램 구매의향이 있어서 댓글남겨요~ 가능하시다면 연락부탁드리겠습니다

    Reply
  3. 행인1

    안녕하세요 이글을 보고 자동글쓰기 봇 을 만들고있습니다.
    자동으로 글을 등록하는것까지는 성공했는데

    글의 내용에 링클를 달아주고싶은데 어떤 방법을해도 링크가 연결이 되지 않는군요 ㅠㅠ..

    모바일 버전이라그런지 html테그를 쓰면 다 무시되고,
    모바일에서 주소를직접넣으면 링크가 달리는기능도 작동이 되지않습니다.
    이부분에 대한 해결법 힌트를 얻을 수 있을까해서 문의드립니다.

    Reply
  4. 김ㅇㅇ

    자꾸 로그인실패뜨는데 이건 디씨측에서 못하게 막아둔건가요?

    Reply
  5. 디시봇

    안녕하세요. write_submit() 함수를 찾을 수 없는데 누락된건가요?

    Reply

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다