PhantomJS를 사용하여 웹 페이지 스크린샷을 만드는 것은 경제적이고 실용적이지만 API가 작아서 다른 기능을 수행하기가 더 어렵습니다. 예를 들어 자체 웹 서버 Mongoose는 동시에 최대 10개의 요청만 지원할 수 있으므로 독립적인 서비스가 되기를 기대하는 것은 실용적이지 않습니다. 따라서 서비스를 지원하려면 다른 언어가 필요하며 이를 완성하기 위해 NodeJS가 사용됩니다.
PhantomJS 설치
먼저 PhantomJS 공식 홈페이지에 접속해 플랫폼에 맞는 버전을 다운로드하거나, 소스코드를 다운로드해 직접 컴파일해 보세요. 그런 다음 환경 변수에 PhantomJS를 구성하고
$ phantomjs
응답이 있으면 다음 단계로 진행하시면 됩니다.
PhantomJS를 사용하여 간단한 스크린샷 찍기
page.viewportSize = { width: 1024, height: 800 };
명령줄 매개변수
phantomjs snapshot.js http://www.baidu.com
명령줄 매개변수는 PhantomJS가 열릴 때만 전달될 수 있으며, 실행 프로세스 중에는 아무것도 할 수 없습니다. 표준 출력
그러나 테스트에서 표준 출력은 이러한 방법 중 가장 빠른 전송 방법이므로 많은 양의 데이터를 전송할 때 고려해야 합니다. HTTP
이 방법은 간단하지만 PhantomJS에서만 요청을 할 수 있습니다. 웹소켓
테스트 중에 PhantomJS가 로컬 Websocket 서비스에 연결하는 데 실제로 약 1초가 걸리는 것으로 나타났습니다. 당분간 이 방법은 고려하지 않을 것입니다. phantomjs-node
PhantomJS가 지원하는 한 가지 기능이 있는데, 바로 웹페이지를 여는 것입니다. 사실, 웹 페이지를 여는 데는 정말 좋습니다. 따라서 ExpressJS 인스턴스를 가동하고, 하위 프로세스에서 Phantom을 열고, 소켓.io 메시지를 호출로 변환하는 특수 웹페이지를 가리켜 PhantomJS와 통신합니다.
전화를 Phantom이 수신하면 끝입니다!alert()
alert()
dnode 라이브러리를 통해 이루어집니다. 다행히도 이 라이브러리는 browserify와 결합하여 PhantomJS의 pidgin Javascript 환경에서 바로 실행할 수 있을 만큼 잘 작동합니다.
让我们开始吧
我们在第一版中选用HTTP进行实现。
首先利용 클러스터进行简单的进程守护(index.js):
if (cluster.isMaster) {
Cluster.fork();
Cluster.on('exit', function (worker) {
console.log('Worker' 작업자.id ' 사망 :(');
Cluster.fork();
});
})
} else {
require('./extract.js');
}
})();
然后利用connect做我们의 외부 API(extract.js):
var app = connect()
.use(connect.logger('dev'))
.use('/snapshot', connect.static(__dirname '/snapshot', { maxAge: pkg. maxAge }))
.use(connect.bodyParser())
.use('/bridge', bridge)
.use('/api', function (req, res, next) {
if (req.method !== "POST" || !req.body.campaignId) return next();
if (!req.body.urls || !req.body.urls.length) return jobMan.watch(req.body.campaignId, req, res, next);
var CampaignId = req.body.campaignId
,imagePath = './snapshot/'campaignId '/'
, urls = []
, url
, imagePath;
function _deal(id, url, imagePath) {
// URL 목록에 푸시합니다
urls.push({
id: id,
url,
이미지 경로: imagePath
});
}
for (var i = req.body.urls.length; i--;) {
url = req.body.urls[i];
imagePath = imagePath i '.png';
_deal(i, url, imagePath);
}
jobMan.register(campaignId, urls, req, res, next);
var snapshot = generate('phantomjs', ['snapshot.js', CampaignId]);
snapshot.stdout.on( 'data', 함수(데이터) {
console.log('stdout: ' data);
});
snapshot.stderr.on('data', 함수(데이터) {
console.log('stderr: ' data);
});
snapshot.on('close', function (code) {
console.log('스냅샷이 코드 ' code로 종료되었습니다);
});
})
.use(connect.static(__dirname '/html', { maxAge: pkg.maxAge }))
.listen(pkg.port, function () { console.log('listen : ' 'http://localhost:' pkg.port) });
})();
저희는 교량과 직업을 가지고 있습니다.
其中bridge是HTTP讯桥梁,jobMan是工work管理器。我们通过campaignId来对应一个job,然后将job와response委托给jobMan管리。然后启动PhantomJS进行处리。
일반적으로 讯桥梁负责接受或者返回job的关信息,并交给jobMan(bridge.js):
반환 함수(req, res, next) {
if (req.headers.secret !== pkg.secret) return next();
// 스냅샷 앱은 URL 정보를 게시할 수 있습니다
if (req.method === "POST") {
var body = JSON.parse(JSON.stringify(req.body));
jobMan.fire(body);
res.end(' ');
// 스냅샷 앱은 추출해야 하는 URL을 얻을 수 있습니다
} else {
var urls = jobMan.getUrls(req.url.match(/campaignId=([^&]*)(s |&|$)/)[1]);
res.writeHead(200, {'Content-Type': 'application/json'});
res.statuCode = 200;
res. end(JSON.stringify({ urls: urls }));
}
};
})();
요청 방법은 POST, 则我们认为PhantomJS正에서 我们推送job적상关信息。而为GET时,则认为其要获取job적信息。
jobMan负责管理job,并发送目前得到的job信息通过response返回给client(jobMan.js):
function _send(campaignId){
var job = _jobs[campaignId];
if (!job) return;
if (job.waiting) {
job.waiting = false; 완료됨: 완료됨
};
job.urls = [];
var res = job.res;
if (완료됨) {
_jobs[campaignId] = null;
delete _jobs[campaignId]
}
res.writeHead(200, {'Content-Type': 'application/json'});
res.statuCode = 200;
res.end( JSON.stringify(data));
}
}
함수 레지스터(campaignId, urls, req, res, next) {
_jobs[campaignId] = {
urlsNum: urls.length,
끝번호: 0,
urls: [],
캐시Urls: urls,
res: null,
대기: false,
시간 초과: null
} ;
watch(campaignId, req, res, next);
}
function watch(campaignId, req, res, next) {
_jobs[campaignId].res = res;
// 20초 제한 시간
_jobs[campaignId].timeout = setTimeout(function () {
}, 20000);
}
function fire(opts) {
var 캠페인Id = opts.campaignId
, job = _jobs[campaignId]
, fetchObj = fetch(opts.html);
if (직업) {
if ( opts.status && fetchObj.title) {
job.urls.push({
id: opts.id,
이미지: opts.image,
제목: fetchObj.title,
설명: fetchObj.description,
상태: opts.status
});
} 그 밖의 {
job.urls.push({
id: opts.id,
url: opts.url,
상태: opts.status
});
}
if (!job.waiting) {
job.waiting = true;
setTimeout(function () {
_send(campaignId);
}
job.finishNum ;
} else {
console.log('작업을 찾을 수 없습니다!');
}
}
function getUrls(campaignId) {
var job = _jobs[campaignId];
if (job) return job.cacheUrls;
}
return {
등록: 등록,
watch: watch,
fire: fire,
};
})();
반환 함수(html) {
if(!html) return { 제목: false, 설명: false };
var title = html.match(/
if (meta) {
for (var i = Meta.length; i--;) {
if(meta[i].indexOf('name="description"') > -1 || 메타[i].indexOf('name="Description"') > -1){
설명 = 메타[i].match(/content="(.*?)"/)[1] ;
}
}
}
(제목 && 제목[1] !== '') ? (제목 = 제목[1]) : (제목 = '제목 없음');
설명 || (설명 = '설명 없음');
return {
제목: 제목,
설명: 설명
};
};
})();
PhantomJS는 url就过HTTP返回给bridge(snapshotcommunications.js)를 사용하여 PhantomJS를 실행하고 있습니다.
function snapshot(id, url, imagePath) {
var page = webpage.create()
, send
, begin
, save
, end;
page.viewportSize = { width: 1024, height: 800 };
page.clipRect = { top: 0, left: 0, width: 1024, height: 800 };
page.settings = {
javascriptEnabled: false,
loadImages: true,
userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) PhantomJS/1.9.0'
};
page.open(url, function (status) {
var data;
if (status === 'fail') {
data = [
'campaignId=',
campaignId,
'&url=',
encodeURIComponent(url),
'&id=',
id,
'&status=',
].join('');
postPage.open('http://localhost:' + pkg.port + '/bridge', 'POST', data, function () {});
} else {
page.render(imagePath);
var html = page.content;
// callback NodeJS
data = [
'campaignId=',
campaignId,
'&html=',
encodeURIComponent(html),
'&url=',
encodeURIComponent(url),
'&image=',
encodeURIComponent(imagePath),
'&id=',
id,
'&status=',
].join('');
postMan.post(data);
}
// release the memory
page.close();
});
}
var postMan = {
postPage: null,
posting: false,
datas: [],
len: 0,
currentNum: 0,
init: function (snapshot) {
var postPage = webpage.create();
postPage.customHeaders = {
'secret': pkg.secret
};
postPage.open('http://localhost:' + pkg.port + '/bridge?campaignId=' + campaignId, function () {
var urls = JSON.parse(postPage.plainText).urls
, url;
this.len = urls.length;
if (this.len) {
for (var i = this.len; i--;) {
url = urls[i];
snapshot(url.id, url.url , url.imagePath);
}
}
});
this.postPage = postPage;
},
게시물: 함수(데이터) {
this.datas .push(data);
if (!this.posting) {
this.posting = true;
this.fire();
}
},
화재: 기능 () {
if (this.datas.length) {
var data = this.datas.shift()
, that = this;
this.postPage.open('http:// localhost:' pkg.port '/bridge', 'POST', data, function () {
that.fire();
// 하위 프로세스 종료
setTimeout(function () {
if ( this.currentNum === this.len) {
that.postPage.close();
phantom.exit();
}
}, 500);
}) ;
} else {
this.posting = false;
}
}
};
postMan.init(스냅샷);