web-dev-qa-db-fra.com

Comment gérer un «pool» d'instances PhantomJS

Je prévois un service Web pour mon propre usage en interne qui prend un argument, une URL et renvoie du code HTML représentant le DOM résolu de cette URL. Par résolu, je veux dire que le webservice obtiendra d'abord la page à cette URL, puis utilisera PhantomJS pour "rendre" la page, puis retournera la source résultante après que tous les DHTML, AJAX appels etc etc soient exécutés . Cependant, lancer phantom sur une base par demande (ce que je fais maintenant) est trop trop lent. Je préfère avoir un pool d'instances PhantomJS avec un toujours disponible pour servir le dernier appel à mon webservice.

A-t-on déjà travaillé sur ce genre de choses auparavant? Je préfère baser ce service Web sur le travail des autres que d'écrire un gestionnaire de pool/serveur proxy http pour moi à partir de zéro.

Plus de contexte: J'ai énuméré ci-dessous les 2 projets similaires que j'ai vus jusqu'ici et pourquoi j'ai évité chacun d'eux, ce qui pose cette question sur la gestion d'un pool d'instances PhantomJS à la place.

jsdom - d'après ce que j'ai vu, il a de grandes fonctionnalités pour exécuter des scripts sur une page, mais il ne tente pas de reproduire le comportement du navigateur, donc si je l'utilisais comme un "résolveur DOM" à usage général, il finirait par y avoir beaucoup de codage supplémentaire pour gérer toutes sortes de cas de bords, d'appels d'événements, etc. Le premier exemple que j'ai vu était d'avoir à appeler manuellement la fonction onload () de la balise body pour une application de test que j'ai configurée à l'aide de node. Cela semblait être le début d'un profond trou pour le lapin.

Sélénium - Il a juste tellement plus de pièces mobiles, donc configurer un pool pour gérer des instances de navigateur de longue durée sera juste plus compliqué que d'utiliser PhantomJS. Je n'ai besoin d'aucun de ses avantages d'enregistrement/script de macro. Je veux juste un service Web aussi performant pour obtenir une page Web et résoudre son DOM que si je naviguais vers cette URL avec un navigateur (ou encore plus rapidement si je peux lui faire ignorer les images, etc.)

66
Trindaz

J'ai configuré un service cloud PhantomJs, et il fait à peu près ce que vous demandez. Cela m'a pris environ 5 semaines de travail.

Le plus gros problème que vous rencontrerez est le problème connu de fuites de mémoire dans PhantomJs . La façon dont j'ai travaillé autour de cela est de faire un cycle mes instances tous les 50 appels.

Le deuxième plus gros problème que vous rencontrerez est le traitement par page est très gourmand en CPU et en mémoire, vous ne pourrez donc exécuter qu'environ 4 instances par CPU.

Le troisième plus gros problème que vous rencontrerez est que PhantomJs est assez farfelu avec les événements de fin de page et les redirections. Vous serez informé que le rendu de votre page est terminé avant qu'elle ne le soit réellement. Il existe un certain nombre de façons de gérer cela , mais rien de 'standard' malheureusement.

Le quatrième plus gros problème auquel vous devrez faire face est l'interopérabilité entre nodejs et phantomjs heureusement, il y a beaucoup de packages npm qui traitent de ce problème à choisir.

Je sais donc que je suis partial (comme j'ai écrit la solution que je vais suggérer) mais je vous suggère de vérifier PhantomJsCloud.com qui est gratuit pour une utilisation légère.

Mise à jour de janvier 2015: Un autre (5ème?) Gros problème que j'ai rencontré est de savoir comment envoyer la demande/réponse du gestionnaire/équilibreur de charge. À l'origine, j'utilisais le serveur HTTP intégré de PhantomJS, mais je continuais à rencontrer ses limites, en particulier en ce qui concerne la taille de réponse maximale. J'ai fini par écrire la demande/réponse au système de fichiers local comme lignes de communication. * Le temps total consacré à la mise en œuvre du service représente peut-être 20 problèmes de semaines-homme soit peut-être 1000 heures de travail. * et FYI je fais une réécriture complète pour la prochaine version .... (en cours)

62
JasonS

La bibliothèque JavaScript asynchrone fonctionne dans Node et a une fonction queue qui est assez pratique pour ce genre de chose:

queue(worker, concurrency)

Crée un objet de file d'attente avec la concurrence d'accès spécifiée. Les tâches ajoutées à la file d'attente seront traitées en parallèle (jusqu'à la limite de concurrence). Si tous les travailleurs sont en cours, la tâche est mise en file d'attente jusqu'à ce qu'un soit disponible. Une fois qu'un travailleur a terminé une tâche, le rappel de la tâche est appelé.

Quelques pseudocodes:

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);
  });
});

Consultez le documentation complète pour queue dans le fichier Lisez-moi du projet .

17
Michelle Tilley

Pour ma thèse de master, j'ai développé la bibliothèque phantomjs-pool qui fait exactement cela. Il permet de fournir des emplois qui sont ensuite mappés aux travailleurs PhantomJS. La bibliothèque gère la distribution des tâches, la communication, la gestion des erreurs, la journalisation, le redémarrage et d'autres choses. La bibliothèque a été utilisée avec succès pour explorer plus d'un million de pages.

Exemple:

Le code suivant exécute une recherche Google pour les nombres de 0 à 9 et enregistre une capture d'écran de la page sous googleX.png. Quatre sites Web sont explorés en parallèle (en raison de la création de quatre travailleurs). Le script est démarré via node master.js.

master.js (s'exécute dans l'environnement 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 (s'exécute dans l'environnement 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

Comme alternative à la grande réponse @JasonS, vous pouvez essayer PhearJS , que j'ai construit. PhearJS est un superviseur écrit en NodeJS pour les instances PhantomJS et fournit une API via HTTP. Il est disponible en open source à partir de Github .

5
TTT

si vous utilisez nodejs pourquoi ne pas utiliser Selenium-webdriver

  1. exécuter une instance de phantomjs en tant que pilote Web phantomjs --webdriver=port_number
  2. pour chaque instance de phantomjs, créez 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;
    }
    

    et mettez-les tous dans un seul tableau [phantomInstance1, phantomInstance2]

  3. créer dispather.js qui obtient gratuitement phantomInstance à partir du tableau et

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

Si vous utilisez nodejs, vous pouvez utiliser https://github.com/sgentle/phantomjs-node , ce qui vous permettra de connecter un nombre arbitraire de processus phantomjs à votre processus NodeJS principal, donc, la possibilité d'utiliser async.js et de nombreux goodies de nœuds.

0
Fred B