MacOS 기준과 PhantomJS 공식 문서 기반으로 작성하였습니다.

PhantomJS 란?

  • 자바스크립트 API를 Webkit 기반에 브라우저없이 스크립팅 할 수 있게 해주는 것입니다.
  • DOM 처리, CSS 선택기, JSON, Canvas 및 SVG와 같은 다양한 웹 표준을 기본으로 지원합니다.

특징

사용성

  • Jasmine, QUnit, Mocha, Capybara, WebDriver 및 기타 여러 프레임 워크로 기능 테스트 가능
  • SVG, Canvas등과 같은 웹컨텐츠를 캡쳐 할 수 있습니다.
  • 표준 DOM API나 jQuery와 같은 라이브러리를 이용하여 DOM에 접근 하여 조작 할 수 있습니다.
  • YSlow나 Jenkins등으로 네트워크 성능 분석 자동화를 할 수 있습니다.

장점

  • Windows, Mac OS X, Linux 등 메이저한 OS를 지원(멀티플랫폼)
  • 에뮬레이터 없이 빠르고 네이티브한 웹 표준 준수 : DOM, CSS, Javascript, Canvas
  • 리눅스에서 순수한 headless 구동이되며, CI시스템에 이상적이고, 또한 Amazon EC2, Heroku, Iron.io에서도 작동

아키텍처

아키텍쳐
< 아키텍쳐 >
PhantomJS는 WIndows, OS X, Linux와 같은 x86 운영체제용 C++ 응용프로그램입니다.
웹페이지를 만들고 구성 하기 위해 오픈소스 웹 렌더링 엔진인 Webkit 을 사용합니다. 자바스크립트를 실행하기 위해 WebkitJavascript Core 라는 자바스크립트 엔진을 사용합니다. 보다 구체적으로, PhantomJS는 오픈 소스 멀티 플랫폼 C ++ 프레임워크 인 Qt (qt.io)를 사용하는 QtWebKit이라는 유명한 Webkit의 특정 변형을 사용합니다. Qt는 다양한 운영 체제 간의 차이를 표준화하고 이벤트 루프, 스레드, HTTP (s), 2-D 그래픽, 글꼴 처리, 파일 시스템, I / O 작업 등을 사용하는 네트워크 연결을 위한 통합 된 API 세트를 제공합니다.
PhantomJS는 PhantomJS 기능을 사용하는 WebDriver Wire Protocol의 순수 JavaScript 구현 인 Ghost 드라이버 (github.com/detro/ghostdriver)를 내장하고 있기 때문에 Selenium과 함께 사용할 수 있습니다.

설치

Mac

static build 설치

brew 로 설치

$ brew install phantomjs

ubuntu 16.04

fonts issue

phantomjs 는 리눅스에서 시스템 폰트가 없으면 한자같은걸 출력 못하기 때문에, 리눅스상에 폰트를 설치해야합니다.

  • sudo apt-get install tex-common
  • https://packages.debian.org/sid/all/latex-cjk-chinese-arphic-bsmi00lp/download 에서 다운받아야합니다.
  • sudo apt-get install texlive-*

실전 돌입

PhantomJS 실행법

예제를 해보기 앞서 작성한 소스를 phantomJS로 실행하는 법입니다. Command Line으로 실행을 해야하고, phantomjs가 전역으로 설정되어있다는 전제하에, 코딩한 소스파일을 phantomjs 라는 커맨드로 실행하면 됩니다.(node 와 같다.)

$ phantomjs test.js

여기에 arguments를 붙여서 실행을 하고 소스파일에서 arguments를 불러올 수 있습니다.

$ phantomjs test.js arg1 arg2 arg2
// test.js 파일
var system = require('system');

console.log(system.args[0]); // test.js
console.log(system.args[1]); // arg1
console.log(system.args[2]); // arg2
console.log(system.args[3]); // arg3

웹 페이지 캡쳐

PhantomJS를 이용하면 원하는 웹페이지에 원하는 부분을 캡쳐를 할 수 가 있습니다. 캡쳐를 한 것으로 미리보기를 사용한다던가, 썸네일을 생성하는 등의 작업에 응용을 할 수 있습니다.
기본적인 예제를 통해 웹페이지를 캡쳐 해보도록 하겠습니다.

var page = require('webpage').create();

var t0 = performance.now();
var t1 = 0;

page.viewportSize = { width: 1525, height: 1240 };
page.open('https://blog.tyle.io', function () {
        page.render('test.png');
    t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.")
       phantom.exit();
});

위 코드를 실행하면 test.png 라는 이미지 파일이 생성되고, 이미지 파일은 다음과 같이 되어있습니다.(너무 길어서 이미지를 잘랐습니다...)

capture
< capture >

PhantomJS에서 제공하는 webpage(API문서) 모듈을 생성하여 캡쳐하기 위한 웹페이지의 viewportSize를 지정하고 open 메서드로 웹 페이지를 로드 하고 render 메서드로 캡쳐후, exit 를 통해 닫음으로써 모든 캡쳐 프로세스는 완료됩니다. (안하게 되면 윈도우 사이즈를 어떻게 지정할지 알 수가 없습니다.)

open 메서드는 실제 ajax 호출 하듯이 body를 설정하거나, HTTP PROTOCOL을 설정할 수 도 있습니다.

// 몸체
open(url, callback){void} 
open(url, method, callback){void} 
open(url, method, data, callback) {void} 
open(url, settings, callback) {void}

// header 세팅
var settings = {
  operation: "POST",
  encoding: "utf8",
  headers: {
    "Content-Type": "application/json"
  },
  data: JSON.stringify({
    some: "data",
    another: ["custom", "data"]
  })
};

page.open('https://blog.tyle.io', settings, function(status) {
  console.log('Status: ' + status);
  // Do other things here...
});

자바스크립트 함수 실행하여 원하는 부분만 캡쳐하기

웹페이지가 로드된 이후 에 자바스크립트를 실행 할 수 있게 PhantomJS가 제공하는 page.evaluate 함수가 있습니다. 이걸 통해 원하는 DOM에 접근하거나, 내부 스크립팅을 실행 하는등의 조작이 가능합니다.

var page = require('webpage').create();

page.viewportSize = { width: 1525, height: 1240 };
page.open('https://blog.tyle.io/', function (status) {
    if(status !== 'success'){
        phantom.exit();
    }
    var option = page.evaluate(function(){
        var $post = document.getElementsByClassName('public')[1].getBoundingClientRect();
        var option = {
                top:    $post.top,
                left:   $post.left,
                width:  $post.width,
                height: $post.height
            };
        return option;
       });

    page.clipRect = {
        top :   option.top,
        left:   option.left,
        width:  option.width,
        height: option.height
    };

    page.render('test.png');
    phantom.exit();
});

웹페이지가 로드에 성공하면 status 파라미터로 success 값이 들어오게 되며, 그 이후 page.evaluate 에 콜백 함수로 원하는 자바스크립트 코드를 삽입하면 됩니다.
이 예제에서는 원하는 DOM 부분만 캡쳐하기 위한 Element Position 을 얻어와 반환해주도록 하였습니다. 반환된 Position 값을 page.clipRect(api문서) 에 세팅하여주면 원하는 DOM에 캡쳐를 진행 하게 됩니다.

호스트 웹으로 데이터 받아보기

호출하는 웹사이트와 phantomJS와 데이터를 교환 할 수 있게 phantomJS는 지원하고 있습니다. 저희는 PhantomJS가 호스트 웹이 전달하는 데이터를 받아보도록 하겠습니다.
이 기능을 이용하면 원하는 시점에 캡쳐 타이밍을 잡는 다거나, 종료시점을 제어 할 수 있는 좋은 기능입니다.
page.onCallback 에서 호스트 웹에서 전달하는 데이터를 받아올 수 있으며, 호스트 웹에서는 callPhantom 객체가 전역으로 생성되지고 이를 호출 하여 데이터를 전달하는 방식입니다.

client-side (host web)

if (typeof window.callPhantom === 'function') {
  window.callPhantom("start");
}

server-side( phantomjs)

page.onCallback = function(data) {
  console.log('CALLBACK: ' + data); //  'CALLBACK: start'
};

영상 만들기

영상 만들기 전

이제 필요한 건 다 해보았습니다. 대단한 프로그램이지만, 특별하게 사용하는데 어려움없는 정말 쉽습니다. 이제 영상을 만들기 위한 이미지 프레임을 캡쳐를 해보고자 합니다. 수많은 이미지들을 한데 모아 빠르게 재생하면 영상이 되는 방식으로 영상을 만들어 보도록하겠습니다.

var page = require('webpage').create();
var system = require('system');

var args = {
    URL         : system.args[1]
}

page.onLoadFinished = function(status) {
    if(status === 'success'){
        var option = page.evaluate(function(){
          var $post = document.getElementsByClassName('public')[4].childNodes[3].childNodes[1].getBoundingClientRect() // 임의로 작성한것 입니다.
          var option = {
                  top:    $post.top,
                    left:   $post.left,
                    width:  $post.width,
                    height: $post.height
                };
         return option;
        });
        page.clipRect = {
            top:  option.top,
            left:   option.left,
            width:  option.width,
            height: option.height
        };
    }
};

page.onCallback = function(data) {
    if(data === 'start'){
        var frame = 0;
        setInterval(function() {
            var dir = 'frames/test-'+(frame++)+'.png';
            page.render(dir, { format: "png" });
        }, 60);
    }else if(data === 'end'){
        phantom.exit();
    }
};

page.open(args.URL, function () {});
$ phantomjs test.js https://blog.tyle.io

요청 페이지 로드가 끝나면, page.onLoadFinished 함수가 호출 됩니다.(dom.ready와 같습니다.) 원하는 부분을 지정하여 rect를 설정합니다. 그리고 요청 웹에서 캡쳐를 시작하라는 메세지를 전달하면, 60ms에 한번씩 캡쳐를 시작하여 /frames 폴더에 저장을 합니다. 그후 캡쳐를 끝내라는 메세지를 받게 되면 프로그램은 종료됩니다.

ffmpeg으로 이미지 합치기

ffmpeg 설치 필수

아래 명령어를 실행하면 합쳐줘서 mp4 영상으로 추출된 것을 확인 할 수 있습니다.

ffmpeg -i frames/test-%d.png -c:v libx264 -preset slow -b:v 1000k -vf scale=-1:720 -threads 0 -pix_fmt yuv420p -r 60 out.mp4

간단 옵션 설명

  • -i : 파일 위치 지정 ( %d 와같은 연산자 사용가능 )
  • -c:v : 비디오 코덱 스트림 지정
  • -preset : 압축 시간 조정
  • -b:v : 비디오 스트림 bitrate 조절
  • -vf : 비디오 필터 조정 (문서)
  • -threads : CPU 코어 갯수설정 (0은 자동 조절)
  • -pix_fmt : 픽셀 포맷 조절 (pixel format list)
  • -r : fps

결론

거대한 PhantomJS와 FFMPEG가 간단한 캡쳐와 이미지 압축을 할 때는 매우 쉽고 간편하게 사용이 가능합니다. 설치도 쉽고, 명령어도 어렵지 않으며, API 또한 간소화 되어 있어서 보기 편합니다. 실시간 으로 캡쳐를 하여 영상을 렌더링 하는 작업을 진행하면, 다양한 기능들을 제공 할 수 있을 거 같습니다. 끝