2012-02-10 18 views
7

Tengo que eliminar un sitio web donde necesito buscar varias URL y luego procesarlas una por una. El proceso actual de alguna manera es así.Descarga de páginas en paralelo usando PHP

Obtengo una URL base y obtengo todas las URL secundarias de esta página, luego, para cada URL secundaria, obtengo esa URL, proceso la página encontrada, descargo algunas fotos (lo cual toma bastante tiempo) y almaceno esta información en la base de datos. luego busca la siguiente URL y repite el proceso.

En este proceso, creo que estoy perdiendo algo de tiempo en buscar URL secundaria al comienzo de cada iteración. Así que estoy tratando de obtener las próximas URL en paralelo mientras proceso la primera iteración.

La solución que tengo en mente es que, desde el proceso principal, llame a un script PHP, digamos downloader, que descargará toda la URL (con curl_multi o wget) y los almacenará en alguna base de datos.

Mis preguntas son

  • cómo llamar a tales downloder asíncrona, yo no quiero que mi script principal que esperar hasta downloder completa.
  • Cualquier ubicación para almacenar datos descargados, como la memoria compartida. Por supuesto, aparte de la base de datos.
  • Existe la posibilidad de que los datos se corrompan mientras se guardan y recuperan, ¿cómo evitar esto?
  • Además, por favor guíame si alguien tiene un plan mejor.
+2

PHP no está diseñado para iniciar múltiples procesos. ¿Por qué no mirar un lenguaje como Python para lograr esto? – afuzzyllama

+0

@afuzzyllama Es solo un submódulo, todo el proyecto está en PHP –

+0

[nodejs] (http://nodejs.org) sería perfecto para esto. – Xeoncross

Respuesta

6

Cuando escucho que alguien usa curl_multi_exec, por lo general resulta que solo lo cargan con, digamos, 100 URL, luego espera cuando todo se completa y luego los procesa todo, y luego vuelve a empezar con las siguientes 100 URL ... Culpa Yo también estaba haciéndolo, pero luego descubrí que es posible quitar/agregar identificadores a curl_multi mientras algo está todavía en progreso, y realmente ahorra mucho tiempo, especialmente si reutilizas las conexiones ya abiertas. Escribí una pequeña biblioteca para manejar la cola de solicitudes con devoluciones de llamadas; No estoy publicar la versión completa aquí, por supuesto ("pequeño" es todavía un poco de código), pero aquí es una versión simplificada de lo principal para darle la idea general:

public function launch() { 
    $channels = $freeChannels = array_fill(0, $this->maxConnections, NULL); 
    $activeJobs = array(); 
    $running = 0; 
    do { 
     // pick jobs for free channels: 
     while (!(empty($freeChannels) || empty($this->jobQueue))) { 
      // take free channel, (re)init curl handle and let 
      // queued object set options 
      $chId = key($freeChannels); 
      if (empty($channels[$chId])) { 
       $channels[$chId] = curl_init(); 
      } 
      $job = array_pop($this->jobQueue); 
      $job->init($channels[$chId]); 
      curl_multi_add_handle($this->master, $channels[$chId]); 
      $activeJobs[$chId] = $job; 
      unset($freeChannels[$chId]); 
     } 
     $pending = count($activeJobs); 

     // launch them: 
     if ($pending > 0) { 
      while(($mrc = curl_multi_exec($this->master, $running)) == CURLM_CALL_MULTI_PERFORM); 
       // poke it while it wants 
      curl_multi_select($this->master); 
       // wait for some activity, don't eat CPU 
      while ($running < $pending && ($info = curl_multi_info_read($this->master))) { 
       // some connection(s) finished, locate that job and run response handler: 
       $pending--; 
       $chId = array_search($info['handle'], $channels); 
       $content = curl_multi_getcontent($channels[$chId]); 
       curl_multi_remove_handle($this->master, $channels[$chId]); 
       $freeChannels[$chId] = NULL; 
        // free up this channel 
       if (!array_key_exists($chId, $activeJobs)) { 
        // impossible, but... 
        continue; 
       } 
       $activeJobs[$chId]->onComplete($content); 
       unset($activeJobs[$chId]); 
      } 
     } 
    } while (($running > 0 && $mrc == CURLM_OK) || !empty($this->jobQueue)); 
} 

En mi versión $ los trabajos son en realidad de clase separada, no instancias de controladores o modelos. Solo manejan la configuración de las opciones de cURL, analizan la respuesta y llaman a una devolución de llamada dada en Completo. Con esta estructura, las nuevas solicitudes comenzarán tan pronto como finalice algo fuera de la agrupación.

Por supuesto que no te ahorra mucho si no solo recuperar lleva tiempo sino que también procesa ... Y no es un verdadero manejo en paralelo. Pero todavía espero que ayude. :)

P.S. hizo un truco para mí :) Una vez el trabajo de 8 horas ahora se completa en 3-4 minutos usando un grupo de 50 conexiones. No puedo describir ese sentimiento. :) Realmente no esperaba que funcionara como estaba planeado, porque con PHP raramente funciona exactamente como se suponía ... Eso fue como "ok, espero que termine en al menos una hora ... Qué ... Espera ... .¡Ya!? 8-O "

+0

Gracias por compartir, también puede encontrar otras bibliotecas como esta si busca github. – Xeoncross

+0

Respuesta realmente útil, pero su comentario me ayudó mucho, gracias hombre ... –

+0

Bienvenido. :) Supongo que debo poner eso en la respuesta, entonces ... – Slava

2

Puede utilizar curl_multi: http://www.somacon.com/p537.php

También es posible que desee considerar la posibilidad de hacer este lado del cliente y el uso de Javascript.


Otra solución es escribir un cazador/recolector que presente un conjunto de direcciones URL para, a continuación, se hace el trabajo paralelo y devuelve una matriz JSON después de que se ha completado.

Dicho de otra manera: si tuviera 100 URL, podría POSTAR esa matriz (probablemente también como JSON) a mysite.tld/huntergatherer, hace lo que quiera en el idioma que desee y simplemente devuelve JSON.

+0

Sí, ya estoy usando curl_multi, javascript suena bien, ¿puedo obtener más detalles? –

0

Intente ejecutar desde PHP, scripts python-pycurl. Más fácil, más rápido que PHP curl.

2

Aparte de la solución múltiple de rizo, otra simplemente tiene un lote de gearman workers. Si sigues esta ruta, he encontrado que supervisord es una buena manera de iniciar una carga de trabajadores deamon.

+0

¡Gracias! Nunca he oído hablar de esta extensión antes. – Slava

1

cosas que usted debe mirar además de rizo múltiples:

  • corrientes no-bloqueo (ejemplo: PHP-MIO)
  • ZeroMQ de desove frente a muchos trabajadores que hacen peticiones de forma asíncrona

Mientras node.js, ruby ​​EventMachine o herramientas similares son geniales para hacer esto, las cosas que mencioné también lo hacen bastante fácil en PHP.

+0

Flujos sin bloqueo es realmente una buena lectura, lo siento, pero puedo seleccionar una sola respuesta como mejor. Gracias –

Cuestiones relacionadas