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

Je planifie un service Web pour mon propre usage en interne qui prend un argument, une URL et renvoie html représentant le DOM résolu de cette URL. Par résolu, je veux dire que le service Web obtiendra d’abord la page sur cette URL, puis utilisera PhantomJS pour “rendre” la page, puis renverra la source résultante après que tous les appels DHTML, AJAX, etc. Cependant, lancer un fantôme sur demande (ce que je fais maintenant) est beaucoup trop lent. Je préférerais avoir un pool d’instances PhantomJS dont une toujours disponible pour servir le dernier appel à mon service Web.

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

Plus de contexte : J’ai énuméré les deux projets similaires que j’ai vus ci-dessous et pourquoi j’ai évité chacun d’eux, ce qui a entraîné cette question sur la gestion d’un pool d’instances PhantomJS à la place.

jsdom – d’après ce que j’ai vu, il a d’excellentes fonctionnalités pour exécuter des scripts sur une page, mais il ne tente pas de répliquer le comportement du navigateur, donc si je l’utilisais comme un “résolveur DOM” à usage général, cela finirait par être beaucoup de codage supplémentaire pour traiter toutes sortes de cas d’arêtes, appel d’événements, etc. Le premier exemple que j’ai vu consistait à appeler manuellement la fonction onload () de la balise body pour une application de test que j’ai configurée avec node. Cela semblait être le début d’un profond trou de lapin.

Sélénium – Il y a juste beaucoup plus de pièces mobiles, donc configurer un pool pour gérer des instances de navigateur de longue durée sera plus compliqué que d’utiliser PhantomJS. Je n’ai besoin d’aucun de ses avantages en matière d’enregistrement de macro et de script. Je veux juste un webservice qui soit aussi performant pour obtenir une page Web et résoudre son DOM que si je naviguais vers cette URL avec un navigateur (ou même plus rapidement si je pouvais ignorer les images, etc.)

Je mets en place 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 des memory leaks dans PhantomJs . La façon dont j’ai travaillé autour de cela est de cycle mes instances tous les 50 appels.

Le deuxième problème le plus important que vous rencontrerez est que le traitement par page demande beaucoup de ressources CPU et de mémoire, vous ne pourrez donc exécuter que quelques instances par processeur.

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 votre page est terminée avant son rendu. Il y a plusieurs façons de gérer cela , mais malheureusement, rien de «standard».

Le quasortingème problème le plus important auquel vous devrez faire face est l’interopérabilité entre nodejs et phantomjs. Heureusement, de nombreux paquets npm permettent de choisir parmi ce problème .

Donc je sais 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 problème majeur (5ème?) Auquel j’ai été confronté concerne la manière d’envoyer la demande / réponse du gestionnaire / équilibreur de charge. À l’origine, j’utilisais le serveur HTTP intégré de PhantomJS, mais je continuais à en rencontrer les limites, notamment en ce qui concerne la taille maximale des réponses. J’ai fini par écrire la requête / réponse sur le système de fichiers local en tant que lignes de communication. * Le temps total consacré à la mise en œuvre du service représente peut-être 20 semaines-homme, ce qui représente peut-être 1000 heures de travail. * et FYI je fais une réécriture complète pour la prochaine version …. (en cours)

La bibliothèque JavaScript asynchrone fonctionne dans Node et possède une fonction de queue très pratique pour ce genre de chose:

queue(worker, concurrency)

Crée un object de queue avec la simultanéité spécifiée. Les tâches ajoutées à la queue seront traitées en parallèle (jusqu’à la limite de simultanéité). Si tous les travailleurs sont en cours, la tâche est mise en queue jusqu’à ce que l’un d’entre eux soit disponible. Une fois qu’un travailleur a terminé une tâche, le rappel de la tâche est appelé.

Certains 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 la documentation complète pour la queue d’ queue au readme du projet .

Pour ma thèse, j’ai développé la bibliothèque phantomjs-pool qui fait exactement cela. Cela permet de fournir des emplois qui sont ensuite associés aux employés de PhantomJS. La bibliothèque gère la dissortingbution des tâches, la communication, la gestion des erreurs, la journalisation, le redémarrage et d’autres fonctions. La bibliothèque a été utilisée avec succès pour parsingr plus d’un million de pages.

Exemple:

Le code suivant exécute une recherche Google pour les numéros 0 à 9 et enregistre une capture d’écran de la page en tant que 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 le 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('http://soffr.miximages.com/node.js/search'); done(); // signal that the job was executed }); }; 

En alternative à @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 .

si vous utilisez nodejs pourquoi ne pas utiliser selenium-webdriver

  1. exécuter une instance de phantomjs en tant que webdriver 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 les mettre tous dans un tableau [phantomInstance1, phantomInstance2]

  3. créer dispather.js qui obtient phantomInstance libre du tableau et

     var driver = phantomInstance.getDriver(); 

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, ce qui vous permettra d’utiliser async.js et beaucoup de goodies de noeud.