Tengo un script PHP que tarda mucho (5-30 minutos) en completarse. Por si acaso importa, el script utiliza curl para extraer datos de otro servidor. Ésta es la razón por la que está tardando tanto; tiene que esperar a que se cargue cada página antes de procesarla y pasar a la siguiente.
Quiero poder iniciar el script y dejarlo estar hasta que esté listo, lo que establecerá una bandera en una tabla de base de datos.
Lo que necesito saber es cómo poder finalizar la solicitud http antes de que termine de ejecutarse el script. Además, ¿es un script php la mejor manera de hacer esto?
php
apache
curl
httprequest
kbanman
fuente
fuente
Goutte
eGuzzle
implementaría subprocesos de concurrencia. También puede consultar cómoGearman
lanzar solicitudes paralelas en forma de trabajadores.Respuestas:
Ciertamente, se puede hacer con PHP, sin embargo, NO debe hacer esto como una tarea en segundo plano: el nuevo proceso debe disociarse del grupo de procesos donde se inició.
Dado que la gente sigue dando la misma respuesta incorrecta a estas preguntas frecuentes, he escrito una respuesta más completa aquí:
http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html
De los comentarios:
fuente
shell_exec('echo /usr/bin/php -q longThing.php | at now');
pero las razones por las que son un poco largas para incluirlas aquí.La forma rápida y sucia sería usar la
ignore_user_abort
función en php. Esto básicamente dice: No importa lo que haga el usuario, ejecute este script hasta que esté terminado. Esto es algo peligroso si se trata de un sitio público (porque es posible que termine teniendo 20 ++ versiones del script ejecutándose al mismo tiempo si se inicia 20 veces).La forma "limpia" (al menos en mi humilde opinión) es establecer una bandera (en la base de datos, por ejemplo) cuando desea iniciar el proceso y ejecutar un cronjob cada hora (más o menos) para comprobar si esa bandera está activada. Si está configurado, se inicia el script de larga ejecución, si NO está configurado, no ocurre nada.
fuente
header("Connection: close", true);
. Y no te olvides de flush ()Puede usar exec o system para comenzar un trabajo en segundo plano y luego hacer el trabajo en eso.
Además, existen mejores enfoques para raspar la web que el que está utilizando. Puede usar un enfoque de subprocesos (varios subprocesos en una página a la vez), o uno que usa un bucle de eventos (un subproceso que hace varias páginas a la vez). Mi enfoque personal usando Perl sería usar AnyEvent :: HTTP .
ETA: symcbean explicó cómo separar correctamente el proceso en segundo plano aquí .
fuente
No, PHP no es la mejor solución.
No estoy seguro acerca de Ruby o Perl, pero con Python podría reescribir su raspador de página para que sea multiproceso y probablemente se ejecute al menos 20 veces más rápido. Escribir aplicaciones de múltiples subprocesos puede ser un desafío, pero la primera aplicación de Python que escribí fue un raspador de páginas de múltiples subprocesos. Y puede simplemente llamar al script Python desde su página PHP usando una de las funciones de ejecución de shell.
fuente
Sí, puedes hacerlo en PHP. Pero además de PHP, sería aconsejable utilizar un Administrador de colas. Esta es la estrategia:
Divida su gran tarea en tareas más pequeñas. En su caso, cada tarea podría cargar una sola página.
Envía cada pequeña tarea a la cola.
Ejecute sus trabajadores de cola en algún lugar.
El uso de esta estrategia tiene las siguientes ventajas:
Para tareas de ejecución prolongada, tiene la capacidad de recuperarse en caso de que ocurra un problema fatal en el medio de la ejecución, no es necesario comenzar desde el principio.
Si sus tareas no tienen que ejecutarse secuencialmente, puede ejecutar varios trabajadores para ejecutar tareas simultáneamente.
Tiene una variedad de opciones (estas son solo algunas):
fuente
PHP puede ser o no la mejor herramienta, pero usted sabe cómo usarlo, y el resto de su aplicación está escrito usándolo. Estas dos cualidades, combinadas con el hecho de que PHP es "suficientemente bueno", hacen un caso bastante sólido para usarlo, en lugar de Perl, Ruby o Python.
Si su objetivo es aprender otro idioma, elija uno y utilícelo. Cualquier idioma que mencione funcionará, no hay problema. Me gusta Perl, pero lo que a ti te gusta puede ser diferente.
Symcbean tiene buenos consejos sobre cómo gestionar procesos en segundo plano en su enlace.
En resumen, escriba un script PHP CLI para manejar los bits largos. Asegúrese de que informe el estado de alguna manera. Cree una página php para manejar las actualizaciones de estado, ya sea usando AJAX o métodos tradicionales. Su script de inicio iniciará el proceso en ejecución en su propia sesión y devolverá la confirmación de que el proceso está en marcha.
Buena suerte.
fuente
Estoy de acuerdo con las respuestas que dicen que esto debería ejecutarse en un proceso en segundo plano. Pero también es importante que informe sobre el estado para que el usuario sepa que se está realizando el trabajo.
Al recibir la solicitud de PHP para iniciar el proceso, puede almacenar en una base de datos una representación de la tarea con un identificador único. Luego, inicie el proceso de eliminación de pantalla, pasándole el identificador único. Informe a la aplicación de iPhone que la tarea se ha iniciado y que debe verificar una URL específica, que contiene la nueva identificación de la tarea, para obtener el estado más reciente. La aplicación de iPhone ahora puede sondear (o incluso "sondear largamente") esta URL. Mientras tanto, el proceso en segundo plano actualizaría la representación de la base de datos de la tarea a medida que funcionaba con un porcentaje de finalización, paso actual o cualquier otro indicador de estado que desee. Y cuando haya terminado, establecerá una bandera completa.
fuente
Puede enviarlo como una solicitud XHR (Ajax). Los clientes no suelen tener tiempo de espera para las XHR, a diferencia de las solicitudes HTTP normales.
fuente
Me doy cuenta de que esta es una pregunta bastante antigua, pero me gustaría intentarlo. Este script intenta abordar tanto la llamada de inicio inicial para terminar rápidamente como para dividir la carga pesada en partes más pequeñas. No he probado esta solución.
<?php /** * crawler.php located at http://mysite.com/crawler.php */ // Make sure this script will keep on runing after we close the connection with // it. ignore_user_abort(TRUE); function get_remote_sources_to_crawl() { // Do a database or a log file query here. $query_result = array ( 1 => 'http://exemple.com', 2 => 'http://exemple1.com', 3 => 'http://exemple2.com', 4 => 'http://exemple3.com', // ... and so on. ); // Returns the first one on the list. foreach ($query_result as $id => $url) { return $url; } return FALSE; } function update_remote_sources_to_crawl($id) { // Update my database or log file list so the $id record wont show up // on my next call to get_remote_sources_to_crawl() } $crawling_source = get_remote_sources_to_crawl(); if ($crawling_source) { // Run your scraping code on $crawling_source here. if ($your_scraping_has_finished) { // Update you database or log file. update_remote_sources_to_crawl($id); $ctx = stream_context_create(array( 'http' => array( // I am not quite sure but I reckon the timeout set here actually // starts rolling after the connection to the remote server is made // limiting only how long the downloading of the remote content should take. // So as we are only interested to trigger this script again, 5 seconds // should be plenty of time. 'timeout' => 5, ) )); // Open a new connection to this script and close it after 5 seconds in. file_get_contents('http://' . $_SERVER['HTTP_HOST'] . '/crawler.php', FALSE, $ctx); print 'The cronjob kick off has been initiated.'; } } else { print 'Yay! The whole thing is done.'; }
fuente
Me gustaría proponer una solución que sea un poco diferente de la de symcbean, principalmente porque tengo el requisito adicional de que el proceso de larga ejecución debe ejecutarse como otro usuario, y no como un usuario de apache / www-data.
Primera solución usando cron para sondear una tabla de tareas en segundo plano:
Segunda solución usando la instalación inotify de Linux:
Se puede encontrar información adicional en mi publicación: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html
fuente
He hecho cosas similares con Perl, double fork () y separándome del proceso principal. Todo el trabajo de búsqueda de http debe realizarse en un proceso bifurcado.
fuente
Utilice un proxy para delegar la solicitud.
fuente
lo que SIEMPRE uso es una de estas variantes (porque los diferentes sabores de Linux tienen diferentes reglas sobre el manejo de la salida / algunos programas tienen una salida diferente):
Variante I @exec ('./ myscript.php \ 1> / dev / null \ 2> / dev / null &');
Variante II @exec ('php -f myscript.php \ 1> / dev / null \ 2> / dev / null &');
Variante III @exec ('nohup myscript.php \ 1> / dev / null \ 2> / dev / null &');
Es posible que no tenga que instalar "nohup". Pero, por ejemplo, cuando estaba automatizando las conversaciones de video FFMPEG, la interfaz de salida de alguna manera no se manejó al 100% al redirigir los flujos de salida 1 y 2, así que usé nohup Y redirigí la salida.
fuente
si tiene un script largo, divida el trabajo de la página con la ayuda del parámetro de entrada para cada tarea. (luego cada página actúa como hilo) es decir, si la página tiene 1 lac product_keywords ciclo de proceso largo, entonces en lugar del ciclo haga lógica para una palabra clave y pase esta palabra clave de magic o cornjobpage.php (en el siguiente ejemplo)
y para el trabajador en segundo plano, creo que debería probar esta técnica; le ayudará a llamar a tantas páginas como desee, todas las páginas se ejecutarán a la vez de forma independiente sin esperar la respuesta de cada página como asincrónica.
cornjobpage.php // página principal
<?php post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue"); //post_async("http://localhost/projectname/testpage.php", "Keywordname=testValue2"); //post_async("http://localhost/projectname/otherpage.php", "Keywordname=anyValue"); //call as many as pages you like all pages will run at once independently without waiting for each page response as asynchronous. ?> <?php /* * Executes a PHP page asynchronously so the current page does not have to wait for it to finish running. * */ function post_async($url,$params) { $post_string = $params; $parts=parse_url($url); $fp = fsockopen($parts['host'], isset($parts['port'])?$parts['port']:80, $errno, $errstr, 30); $out = "GET ".$parts['path']."?$post_string"." HTTP/1.1\r\n";//you can use POST instead of GET if you like $out.= "Host: ".$parts['host']."\r\n"; $out.= "Content-Type: application/x-www-form-urlencoded\r\n"; $out.= "Content-Length: ".strlen($post_string)."\r\n"; $out.= "Connection: Close\r\n\r\n"; fwrite($fp, $out); fclose($fp); } ?>
testpage.php
<? echo $_REQUEST["Keywordname"];//case1 Output > testValue ?>
PD: si desea enviar parámetros de URL como bucle, siga esta respuesta: https://stackoverflow.com/a/41225209/6295712
fuente
No es el mejor enfoque, como muchos afirman aquí, pero esto podría ayudar:
ignore_user_abort(1); // run script in background even if user closes browser set_time_limit(1800); // run it for 30 minutes // Long running script here
fuente
Si la salida deseada de su script es algún procesamiento, no una página web, entonces creo que la solución deseada es ejecutar su script desde shell, simplemente como
php my_script.php
fuente