NodeJS와 비슷하면서도 V8엔진을 사용하지 않기 때문에 없거나 다르게 동작하는 기능이 많아 PhantomJS 기반으로 작업할 때 유난히 삽질을 많이 했다. 또다른 누군가가 동일한 삽질을 하지 않도록 몇 가지 알게 된 사실을 정리해서 포스팅해보았다.
1. NPM INSTALL 로 설치한 NodeJS의 모듈을 PhantomJS에서도 사용할 수 있다.
var Promise = require('bluebird');
위와 같이 동일하게 require 해서 사용할 수 있다. 따라서 npm을 활용해서 어느정도는 패키지 관리를 해줄 수 있다.
하지만 NodeJS 고유의 기능을 사용하는 모듈들은 실행시 에러가 발생하므로 Pure JavaScript인 경우에만 의미가 있다.
2. PhantomJS도 NodeJS와 동일하게 CommonJS 방식의 module을 지원한다.
복잡한 작업을 하지 않는다면 module화에 관심을 갖지 않아도 되지만, 조금만 규모가 커져도 모듈화는 필수적이다. PhantomJS도 NodeJS와 동일하게 CommonJS 방식의 module을 지원한다.
NodeJS에서 사용자 모듈을 호출할 때 사용하는 방식 그대로 PhantomJS에서 사용자 모듈을 호출해서 사용할 수 있다.
3. PhantomJS의 Page 내에서 evaluate로 실행한 코드는 오류가 날 경우 굉장히 찾기 어렵다.
오류를 PhantomJS가 먹는 경우가 많은데, evaluate 하는 경우 console.log 로도 보기 쉽지 않기 때문에 자칫 잘못하면 삽질의 서막이 열리게 될 수 있다. evaluate하는 코드의 경우 거의 대부분 브라우저 상에서 실행하는 코드이므로 로컬에서 작업한 후 검증된 코드로 evaluate 하는 것이 삽집을 조금이라도 줄일 수 있다.
4. PhantomJS 상의 Scope과 page 내부의 scope은 서로 독립적이다.
다른 말로 표현하면 evaluate 할 때, page 내부에 PhantomJS 상의 객체를 공유하고 싶다면, evaluate의 두번째 파라메터에 해당 객체를 넣고, 첫번째 파라메터의 콜백의 파라메터로 받으면 된다.
var page = require('webpage').create(); var toInjectVars = "PhantomJS 컨텍스트에서 PhantomJS의 Page 컨텍스트로 공유하고 싶은 변수"; page.evaluate(function (injetedVars) { //page 내부 실행 코드 }, toInjectVars);
5. Cross Origin 문제는 보안 설정을 통해 해결할 수 있다.
iframe 안에 iframe이 존재하고 하위 iframe의 origin이 달라 접근이 어려운 문제가 있었는데, 아래와 같이 옵션을 통해 보안 설정을 끄면 Cross Origin 이어도 iframe의 요소에 접근할 수 있게 되어 문제를 쉽게 해결할 수 있었다.
var page = require('webpage').create(); page.settings.webSecurityEnabled = false;
마치며
PhantomJS 이외에 선택할만한 Headless Browser는 현시점에서는 많지 않은 것 같다. PhantomJS를 Wrapping해서 보다 사용하기 편리하도록 만들어주는 도구들도 많이 있지만, 세세한 조정이 필요할 때엔 결국 Wrapping한 도구들을 쓰기보다는 직접 PhantomJS에 손을 댈 수 밖에 없다. NodeJS와 비슷하면서도 다르거나 모자르고, 내장된 브라우저와 바깥의 PhantomJS 사이의 scope을 넘나들다보면 작은 실수가 작업을 미궁으로 빠뜨릴 때가 많았다.
이 짧은 포스팅이 PhantomJS에 대한 특별한 insight를 줄 수는 없겠지만 누군가 동일한 어려움에 빠지는 것은 막아줄 수 있기를 기대해본다.