web-dev-qa-db-ja.com

PhantomJSインスタンスの「プール」を管理する方法

1つの引数、URLを受け取り、そのURLからresolvedDOMを表すhtmlを返すWebサービスを内部で独自に使用することを計画しています。解決するということは、Webサービスが最初にそのURLでページを取得し、PhantomJSを使用してページを「レンダリング」し、すべてのDHTML、AJAX呼び出しなどが実行された後に結果ソースを返すことを意味します。しかし、リクエストごとにファントムを起動することは(今私がやっている)、wayが遅すぎる。むしろ、PhantomJSインスタンスのプールがあるだろう私のウェブサービスへの最新の呼び出しを提供するために常に利用可能なもので。

これまでにこの種の作業は行われましたか?このWebサービスは、プールマネージャー/ httpプロキシサーバーを最初から自分で作成するよりも、他の人の作業に基づいて作成したいです。

詳細なコンテキスト:これまでに見た2つの同様のプロジェクトと、それぞれを回避した理由をリストしました。その結果、PhantomJSインスタンスのプールの管理に関するこの質問になりました。

jsdom-私が見たところ、ページ上でスクリプトを実行するための優れた機能を備えていますが、ブラウザの動作を再現しようとしないため、汎用の「DOMリゾルバ」として使用すると最終的にはあらゆる種類のエッジケース、イベント呼び出しなどを処理するための多くの余分なコーディング。最初に目にした例は、nodeを使用して設定したテストアプリのbodyタグのonload()関数を手動で呼び出す必要がありました。深いウサギの穴の始まりのように見えた。

Selenium-あまりにも多くの可動部分があるため、長寿命のブラウザインスタンスを管理するためのプールのセットアップは、PhantomJSを使用するよりも複雑になります。マクロの記録/スクリプト作成の利点は必要ありません。私はちょうどブラウザでそのURLを閲覧しているかのようにWebページを取得してDOMを解決するパフォーマンスの高いWebサービスが必要です(または画像などを無視できるようにすればさらに速くなります)

66
Trindaz

私はPhantomJsクラウドサービスをセットアップしましたが、それはあなたが求めていることとほぼ同じです。約5週間の作業器具が必要でした。

遭遇する最大の問題は、既知の問題である PhantomJsのメモリリーク です。これを回避する方法は、50回の呼び出しごとにインスタンスを循環させることです。

2番目に大きな問題は、ページごとの処理が非常にCPUとメモリを集中的に使用するため、CPUごとに4つ程度のインスタンスしか実行できないことです。

あなたが遭遇する3番目の最大の問題は、PhantomJsがページフィニッシュイベントとリダイレクトでかなり奇抜であることです。ページが実際にレンダリングされる前にレンダリングが完了したことが通知されます。 これに対処する方法はたくさんあります 、しかし残念ながら「標準」はありません。

対処しなければならない4番目の最大の問題は、ありがたいことに、nodejsとphantomjsの相互運用性です この問題を処理する多くのnpmパッケージ から選択できます。

だから私はバイアスをかけていることを知っています(提案するソリューションを書いたので)が、チェックアウトすることをお勧めします PhantomJsCloud.com これは軽い使用には無料です。

2015年1月の更新:別の(5番目?)大きな問題は、マネージャー/ロードバランサーからリクエスト/レスポンスを送信する方法です。もともと私はPhantomJSの組み込みHTTPサーバーを使用していましたが、特に最大応答サイズに関しては、その制限に直面し続けていました。最終的に、通信の行としてローカルファイルシステムに要求/応答を書き込みました。 *サービスの実装に費やされる合計時間は、おそらく20人週の問題であり、おそらく1000時間の作業です。 *およびFYI私は次のバージョンの完全な書き換えを行っています....(進行中)

62
JasonS

async JavaScript library は、Nodeで動作し、この種のものに非常に便利なqueue関数を持っています:

queue(worker, concurrency)

指定された並行性でキューオブジェクトを作成します。キューに追加されたタスクは、並行処理されます(同時実行制限まで)。すべてのワーカーが進行中の場合、タスクは使用可能になるまでキューに入れられます。ワーカーがタスクを完了すると、タスクのコールバックが呼び出されます。

いくつかの擬似コード:

function getSourceViaPhantomJs(url, callback) {
  var resultingHtml = someMagicPhantomJsStuff(url);
  callback(null, resultingHtml);
}

var q = async.queue(function (task, callback) {
  // delegate to a function that should call callback when it's done
  // with (err, resultingHtml) as parameters
  getSourceViaPhantomJs(task.url, callback);
}, 5); // up to 5 PhantomJS calls at a time

app.get('/some/url', function(req, res) {
  q.Push({url: params['url_to_scrape']}, function (err, results) {
    res.end(results);
  });
});

プロジェクトのreadmeでqueueのドキュメント全体 を確認してください。

17
Michelle Tilley

私の修士論文では、まさにこれを行うライブラリ phantomjs-pool を開発しました。 PhantomJSワーカーにマッピングされるジョブを提供できます。ライブラリは、ジョブの配布、通信、エラー処理、ログ、再起動などを処理します。ライブラリは、100万ページを超えるクロールに使用されました。

例:

次のコードは、0〜9の数字のGoogle検索を実行し、ページのスクリーンショットをgoogleX.pngとして保存します。 4つのWebサイトが並行してクロールされます(4人のワーカーが作成されたため)。スクリプトはnode master.jsを介して開始されます。

master.js(Node.js環境で実行)

var Pool = require('phantomjs-pool').Pool;

var pool = new Pool({ // create a pool
    numWorkers : 4,   // with 4 workers
    jobCallback : jobCallback,
    workerFile : __dirname + '/worker.js', // location of the worker file
    phantomjsBinary : __dirname + '/path/to/phantomjs_binary' // either provide the location of the binary or install phantomjs or phantomjs2 (via npm)
});
pool.start();

function jobCallback(job, worker, index) { // called to create a single job
    if (index < 10) { // index is count up for each job automatically
        job(index, function(err) { // create the job with index as data
            console.log('DONE: ' + index); // log that the job was done
        });
    } else {
        job(null); // no more jobs
    }
}

worker.js(PhantomJS環境で実行)

var webpage = require('webpage');

module.exports = function(data, done, worker) { // data provided by the master
    var page = webpage.create();

    // search for the given data (which contains the index number) and save a screenshot
    page.open('https://www.google.com/search?q=' + data, function() {
        page.render('google' + data + '.png');
        done(); // signal that the job was executed
    });

};
14
Thomas Dondorf

@JasonSの優れた答えの代わりとして、私が作成した PhearJS を試すことができます。 PhearJSは、PhantomJSインスタンス用にNodeJSで記述されたスーパーバイザーであり、HTTP経由でAPIを提供します。 Github からオープンソースで入手できます。

5
TTT

nodejsを使用している場合、Selenium-webdriverを使用しない理由

  1. いくつかのphantomjsインスタンスをwebdriverとして実行しますphantomjs --webdriver=port_number
  2. 各phantomjsインスタンスに対してPhantomInstanceを作成します

    function PhantomInstance(port) {
        this.port = port;
    }
    
    PhantomInstance.prototype.getDriver = function() {
        var self = this;
        var driver = new webdriver.Builder()
            .forBrowser('phantomjs')
            .usingServer('http://localhost:'+self.port)
            .build();
        return driver;
    }
    

    そして、それらすべてを1つの配列[phantomInstance1、phantomInstance2]に入れます

  3. 配列から無料のphantomInstanceを取得するdispather.jsを作成し、

    var driver = phantomInstance.getDriver();
    
1
Shawn Liu

Nodejsを使用している場合、 https://github.com/sgentle/phantomjs-node を使用できます。これにより、任意の数のphantomjsプロセスをメインNodeJSプロセスに接続できます。したがって、 async.jsと多くのノードの利点を使用する機能。

0
Fred B