ホームページ > 記事 > ウェブフロントエンド > NodeJS と PhantomJS を使用して Web サイトのページ情報と Web サイトのスクリーンショットをキャプチャする_JavaScript スキル
PhantomJS を使用して Web ページのスクリーンショットを作成するのは経済的で実用的ですが、API が小さいため、他の機能の実行が難しくなります。たとえば、独自の Web サーバー Mongoose は同時に最大 10 個のリクエストしかサポートできませんが、独立したサービスになることを期待するのは現実的ではありません。したがって、サービスをサポートするには別の言語が必要であり、それを完成させるために NodeJS が使用されます。
PhantomJS をインストールします
まず、PhantomJS 公式 Web サイトにアクセスしてプラットフォームに対応するバージョンをダウンロードするか、ソースコードをダウンロードして自分でコンパイルします。次に、環境変数に PhantomJS を設定し、
$ phantomjs
応答がある場合は、次のステップに進むことができます。
PhantomJS を使用して簡単なスクリーンショットを撮ります
page.viewportSize = { width: 1024, height: 800 };
NodeJS と PhantomJS の通信
まず、PhantomJS でどのような通信ができるかを見てみましょう。 コマンドラインパラメータphantomjs snapshot.js http://www.baidu.com
コマンド ライン パラメーターは、PhantomJS を開いたときにのみ渡すことができ、プロセスの実行中にできることは何もありません。
標準出力
ただし、テストでは標準出力がこれらの方法の中で最も速い送信方法であり、大量のデータを送信する場合は考慮する必要があります。
HTTP
この方法は簡単ですが、リクエストは PhantomJS からのみ行うことができます。
Websocket
テスト中に、PhantomJS がローカル Websocket サービスに接続するのに実際には約 1 秒かかることがわかりました。当面はこの方法を検討しません。
phantomjs-node
phantomjs-nodePhantomJS を NodeJS の 1 つのモデルとして使用することに成功しましたが、作成者の理解:
その質問には質問でお答えします。共有メモリ、ソケット、FIFO、または標準入力をサポートしていないプロセスとどのように通信しますか?
そうですね、PhantomJS がサポートしていることが 1 つあります。それは Web ページを開くことです。実際、Web ページを開くのが非常に得意です。そのため、ExpressJS のインスタンスを起動し、サブプロセスで Phantom を開き、socket.io メッセージを alert()
呼び出しに変換する特別な Web ページを指定することで、PhantomJS と通信します。 alert()
の通話は Phantom によって応答され、完了です。
通信自体は、James Halliday の素晴らしい dnode ライブラリを介して行われます。幸いなことに、このライブラリは browserify と組み合わせることで十分に機能し、PhantomJS の pidgin Javascript 環境から直接実行できます。
実際に phantomjs-node で使用されるのは、HTTP または Websocket を使用して通過することですが、その依存度に関係なく、私たちは単一のサービスを考えています。
设计图
はじめに
最初のバージョンでは HTTP を使用して実行することを選択しました。
最初にクラスターを利用して简单の進行守护(index.js):
if(!fs.existsSync('./snapshot')) {
fs.mkdirSync('./snapshot');
}
if (cluster.isMaster) {
cluster.fork();
cluster.on('exit', function (worker) {
console.log('Worker' worker.id ' が死亡しました :(');
process.nextTick(function () {
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 '/'
、url = []
、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 = ImagesPath i '.png';
_deal(i, url, imagePath);
}
jobMan.register(campaignId, urls, req, res, next);
var snapshot = spawn('phantomjs', ['snapshot.js', CampaignId]);
snapshot.stdout.on( 'data', function (data) {
console.log('stdout: ' data);
});
snapshot.stderr.on('data', function (data) {
console.log('stderr: ' data);
});
snapshot.on('close', function (code) {
console.log('snapshot exited with code ' code);
});
})
.use(connect.static(__dirname '/html', { maxAge: pkg.maxAge }))
.listen(pkg.port, function () { console.log('listen : ' http://localhost:' pkg.port); });
})();
ここでは 2 つのモジュールブリッジとジョブマンを参照しています。
ここで、ブリッジは HTTP 通過ビーム、jobMan はオペレーション管理者です。私たちはキャンペーン ID を介してジョブを処理し、ジョブと応答を jobMan 管理に委任します。その後、PhantomJS を起動して処理します。
通讯桥梁の受領またはジョブを返す関連情報,并交给jobMan(bridge.js):
return function (req, res, next) {
if (req.headers.secret !== pkg.secret) return next();
// スナップショット APP は URL 情報を投稿できます
if (req.method === "POST") {
var body = JSON.parse(JSON.stringify(req.body));
jobMan.fire(body);
res.end(' ');
// スナップショット APP は抽出する 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 が現在送信しているジョブの関連情報と見なされます。GET の場合は、取得ジョブの情報と見なされます。
jobMan 管理ジョブ、応答を介してクライアントに返されるジョブ情報を送信します(jobMan.js):
function _send(campaignId){
var job = _jobs[campaignId];
if (!job) return;
if (job.waiting) {
job.waiting = false;
clearTimeout(job.timeout);
var completed = (job.urlsNum === job.finishNum)
, data = {
CampaignId: CampaignId,
urls: job.urls,
completed: completed
};
job.urls = [];
var res = job.res;
if (finished) {
_jobs[campaignId] = null;
delete _jobs[campaignId]
}
res.writeHead(200, {'Content-Type': 'application/json'});
res.statuCode = 200;
res.end( JSON.stringify(data));
}
}
function register(campaignId, urls, req, res, next) {
_jobs[campaignId] = {
urlsNum: urls.length,
finishNum: 0,
urls: [],
cacheUrls: 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 () {
_send(campaignId);
}, 20000);
}
function fire(opts) {
var CampaignId = opts.campaignId
, job = _jobs[campaignId]
, fetchObj = fetch(opts.html);
if (ジョブ) {
if ( opts.status && fetchObj.title) {
job.urls.push({
id: opts.id,
url: opts.url,
image: opts.image,
title: fetchObj.title,
description: fetchObj.description,
status: opts.status
});
} else {
job.urls.push({
id: opts.id,
url: opts.url,
status: opts.status
});
}
if (!job.waiting) {
job.waiting = true;
setTimeout(function () {
_send(campaignId);
}, 500);
}
job.finishNum ;
} else {
console.log('ジョブが見つかりません!');
}
}
function getUrls(campaignId) {
var job = _jobs[campaignId];
if (job) return job.cacheUrls;
}
return {
register: register,
watch: watch,
fire: fire,
getUrls: getUrls
};
})();
ここで私は fetch html を実行してそのタイトルと説明を取得します,fetch 实现比较简单(fetch.js):
return function (html) {
if (!html) return { title: false, description: false };
var title = html.match(/
if (meta) {
for (var i = meta.length; i--;) {
if(meta[i].indexOf('name="description"') > -1 || メタ[i].indexOf('name="説明"') > -1){
説明 = メタ[i].match(/content="(.*?)"/)[1] ;
}
}
}
(タイトル && タイトル[1] !== '') ? (title = title[1]) : (title = 'タイトルなし');
説明 || (説明 = '説明なし');
return {
title: タイトル,
description: description
};
};
})();
最後は PhantomJS が実行するソースコードであり、起動後に HTTP 経由でジョブ情報を取得し、ジョブが完了すると、その中の 1 つの URL が HTTP 経由でブリッジに返されます (snapshot.js):
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;
},
post: function (data) {
this.datas .push(data);
if (!this.posting) {
this.posting = true;
this.fire();
}
},
fire: function () {
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(snapshot);