2012-04-01 12 views
65

Estoy planeando un servicio web interno para mi propio uso que toma un argumento, una URL y devuelve html que representa el DOM resuelto de esa URL. Resuelto, quiero decir que el servicio web primero obtendrá la página en esa URL, luego usará PhantomJS para 'renderizar' la página, y luego devolverá la fuente resultante después de que se ejecuten todas las llamadas DHTML, AJAX, etc. Sin embargo, el lanzamiento de fantasmas por solicitud (lo que estoy haciendo ahora) es manera demasiado lento. Preferiría tener un conjunto de instancias PhantomJS con una siempre disponible para atender la última llamada a mi servicio web.Cómo administrar un 'grupo' de instancias de PhantomJS

¿Se ha hecho algún trabajo en este tipo de cosas con anterioridad? Prefiero basar este servicio web en el trabajo de los demás que escribir un administrador de grupo/servidor proxy http desde cero.

Más contexto: He enumerado los 2 proyectos similares que he visto hasta ahora y por qué he evitado cada uno, dando lugar a esta pregunta sobre la gestión de un conjunto de instancias PhantomJS en su lugar.

jsdom - por lo que he visto, tiene una gran funcionalidad para ejecutar scripts en una página, pero no intenta replicar el comportamiento del navegador, por lo que si lo utilizo como un propósito general "DOM resolver" allí ' d termina siendo una gran cantidad de codificación adicional para manejar todo tipo de casos de bordes, llamadas a eventos, etc. El primer ejemplo que vi fue tener que llamar manualmente a la función onload() de la etiqueta del cuerpo para una aplicación de prueba que configuré usando un nodo . Parecía el comienzo de un profundo agujero de conejo.

Selenium - Simplemente tiene muchas más partes móviles, por lo que configurar un grupo para administrar instancias de navegador de larga duración será más complicado que usar PhantomJS. No necesito ninguno de sus beneficios de macro grabación/scripting. Solo quiero un servicio web que sea tan eficiente para obtener una página web y resolver su DOM como si estuviera navegando a esa URL con un navegador (o incluso más rápido si puedo ignorar imágenes, etc.)

Respuesta

17

El async JavaScript library funciona en nodo y tiene una función queue que es muy útil para este tipo de cosas:

queue(worker, concurrency)

Creates a queue object with the specified concurrency. Tasks added to the queue will be processed in parallel (up to the concurrency limit). If all workers are in progress, the task is queued until one is available. Once a worker has completed a task, the task's callback is called.

Algunos pseudocódigo:

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

Mira la entire documentation for queue at the project's readme.

+0

¿Conoce cómo funciona la gestión de colas ¿en detalle? Estoy pensando que está llamando a múltiples solicitudes XHR en la cola ¿verdad?Estoy buscando una solución que realmente mantenga los procesos phantomjs ejecutándose como daemon, en lugar de hacer girar uno cada vez que entre una tarea. – CMCDragonkai

+0

@CMCDragonkai La pregunta menciona que "un conjunto de instancias PhantomJS con una siempre disponible para servir al última llamada a mi servicio web, "lo que implica ejecutar constantemente daemons PhantomJS, pero esta respuesta funcionaría con cualquier caso". Todo lo que hace la función 'async.queue' es asegurarse de que no haya más de un cierto número de llamadas a la función pendientes en un momento dado; lo que haces dentro de esa función depende de ti. –

+2

Eres mi amigo, casi 4 años después, me salvó bastante el dolor de cabeza. – mgmcdermott

0

Si está utilizando nodejs, puede usar https://github.com/sgentle/phantomjs-node, que le permitirá conectar un número arbitrario de proceso phantomjs a su proceso NodeJS principal, por lo tanto, la capacidad de utilizar async.js y muchos elementos del nodo.

+0

Esto no es verdad. Si crea más de una instancia de JS fantasma y las ejecuta al mismo tiempo, obtiene 'Error: escuche EADDRINUSE'. Actualmente estoy buscando una forma de poner las instancias fantasmas en diferentes puertos o lo que está causando el EADDRINUSE. – RachelC

+1

Por supuesto, es su responsabilidad iniciar las instancias fantasmas para que escuchen en un puerto diferente. –

61

Configuré un servicio en la nube PhantomJs, y prácticamente hace lo que me pide. Me tomó alrededor de 5 semanas de implementación del trabajo.

El problema más grande con el que se encontrará es el problema conocido de memory leaks in PhantomJs. La forma en que trabajé en esto es hacer un ciclo de mis instancias cada 50 llamadas.

El segundo problema más grande que se encontrará es que el procesamiento por página consume mucha memoria y memoria, por lo que solo podrá ejecutar 4 o más instancias por CPU.

El tercer problema más grande que se encontrará es que PhantomJs es bastante loco con eventos y redirecciones de página completa. Se le informará que su página ha finalizado la renderización antes de que realmente sea. There are a number of ways to deal with this, pero desafortunadamente no hay nada "estándar".

El cuarto problema con el que tendrás que lidiar es la interoperacion entre nodejs y phantomjs. Afortunadamente hay a lot of npm packages that deal with this issue para elegir.

Así que sé que soy parcial (ya que escribí la solución que voy a sugerir), pero le sugiero que consulte PhantomJsCloud.com que es gratuito para el uso de la luz.

Enero de 2015 actualización: Otro (¿5?) Gran problema que encontré es cómo enviar la solicitud/respuesta del administrador/equilibrador de carga. Originalmente estaba usando el servidor HTTP incorporado de PhantomJS, pero seguí corriendo en sus limitaciones, especialmente con respecto al tamaño máximo de respuesta. Terminé escribiendo la solicitud/respuesta al sistema de archivos local como líneas de comunicación. * El tiempo total invertido en la implementación del servicio representa tal vez 20 problemas de hombre-semana es quizás 1000 horas de trabajo. * y, para su información, estoy haciendo una reescritura completa para la próxima versión .... (en curso)

+0

Gran respuesta Jason. Sería realmente bueno si pudieras seguir adelante y contarnos más sobre los detalles de la implementación. ¿Cómo manejas todas las instancias, por ejemplo? Además, ¿cómo se inician instancias de Phantom desde el nodo en sí? ¿Alguna recomendación del módulo para hacerlo? ¿O engendras los procesos? – Nobita

+1

Hago toda la administración desde una aplicación nodejs 'enrutador' en el servidor. lanza múltiples instancias de phantomjs.exe a través de los comandos de proceso de generación de nodejs normales. nada especial en ese sentido en realidad. Probé todas las envolturas de Phantomjs que se encuentran en NPM, pero francamente son muy apetecibles. Terminó utilizando el servidor http incorporado de phantomjs para comunicarse con/desde la aplicación del enrutador nodejs. – JasonS

+0

¿qué hay de crear varios objetos de página web dentro de una instancia phantomJS? Hay algo malo con eso ? – Xsmael

5

Como alternativa a la excelente respuesta de @JasonS, puede intentar PhearJS, que construí. PhearJS es un supervisor escrito en NodeJS para instancias de PhantomJS y proporciona una API a través de HTTP. Está disponible en código abierto desde Github.

1

si está utilizando nodejs por qué no usar el selenio-WebDriver

  1. ejecutar algunas PhantomJS ejemplo como WebDriver phantomjs --webdriver=port_number
  2. para cada PhantomJS ejemplo crear 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; 
    } 
    

    y poner todos a una matriz [phantomInstance1, phantomInstance2]

  3. crear dispather.js que se interponen phantomInstance libre de la matriz y

    var driver = phantomInstance.getDriver(); 
    
+0

Esta no es una buena manera. Confía en mí ... en mi programa usé selenium-webdriver pero finalmente lo abandoné. –

14

Para mi tesis de maestría, he desarrollado la biblioteca phantomjs-pool que hace exactamente esto. Permite proporcionar trabajos que luego se asignan a los trabajadores de PhantomJS. La biblioteca maneja la distribución del trabajo, la comunicación, el manejo de errores, el inicio de sesión, el reinicio y algunas cosas más. La biblioteca se utilizó con éxito para rastrear más de un millón de páginas.

Ejemplo:

El siguiente código ejecuta una búsqueda en Google de los números del 0 al 9 y guarda una captura de pantalla de la página tal como googleX.png. Cuatro sitios web se rastrean en paralelo (debido a la creación de cuatro trabajadores). La secuencia de comandos se inicia a través de node master.js.

master.js (corre en un entorno 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 (corre en un entorno 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 
    }); 

}; 
+1

Esta es una gran biblioteca. Me pregunto, ¿hay alguna manera de detectar cuándo no se generan más procesos? Como en, esperando, a través de asincrónico o una promesa, después de 'pool.start()' para hacer algo una vez que una serie de procesos se ha completado? – afithings

+0

Gracias. Actualmente no hay forma de hacerlo tan simple como con async. Sin embargo, puede usar la devolución de llamada para cada trabajo individual (que se dispara cuando se realiza un trabajo) y aumentar un contador de esa manera. Por lo tanto, aún puede detectar cuándo terminan todos los trabajos. –

Cuestiones relacionadas