Question

J'ai un script PHP qui prend beaucoup de temps (5-30 minutes) pour terminer. Juste au cas où il importe, le script utilise une boucle pour gratter les données d'un autre serveur. Ceci est la raison pour laquelle il est si long; il doit attendre pour chaque page à charger avant de le traiter et de passer à la suivante.

Je veux être en mesure de lancer le script et que ce soit jusqu'à ce qu'il soit fait, qui établira un drapeau dans une table de base de données.

Ce que je dois savoir est comment être en mesure de mettre fin à la demande http avant que le script est terminé en cours d'exécution. En outre, est un script php la meilleure façon de le faire?

Était-ce utile?

La solution

Certes, il peut être fait avec PHP, mais vous ne devriez pas faire cela comme une tâche d'arrière-plan - le nouveau processus doit être dissocated du groupe de processus où il est initié

.

Étant donné que les gens continuent de donner la même mauvaise réponse à cette FAQ, j'ai écrit une réponse plus complète ici:

http://symcbean.blogspot.com/ 2010/02 / php-et-longue durée-processes.html

D'après les commentaires:

  

La version courte est shell_exec('echo /usr/bin/php -q longThing.php | at now'); mais les raisons pour lesquelles sont un peu long à inclure ici.

Autres conseils

La façon rapide et sale serait d'utiliser la fonction ignore_user_abort en php. Cela dit essentiellement: Ne pas en ce que l'utilisateur ne, exécutez ce script jusqu'à ce qu'il soit terminé. Ceci est quelque peu dangereuse si elle est un site destiné au public (car il est possible que vous finissez par avoir 20 ++ versions du script en cours d'exécution en même temps si elle est déclenchée 20 fois).

La façon « propre » (au moins à mon humble avis) est de définir un indicateur (dans le db par exemple) lorsque vous souhaitez lancer le processus et exécuter une tâche cron toutes les heures (environ) pour vérifier si le drapeau est réglé. Si elle est définie, le script en cours d'exécution à long démarre, si elle est pas définie, rien ne se passe.

Vous pouvez utiliser exec ou AnyEvent :: HTTP .

ETA: symcbean a expliqué comment pour détacher le processus d'arrière-plan correctement .

Non, PHP est pas la meilleure solution.

Je ne suis pas sûr de Ruby ou Perl, mais avec Python vous pouvez réécrire votre scraper page pour être multi-thread et il serait probablement courir au moins 20x plus rapide. L'écriture d'applications multi-thread peut être un peu un défi, mais la première application Python je l'ai écrit était grattoir page mutlti-thread. Et vous pouvez simplement appeler le script Python à partir de votre page PHP en utilisant l'une des fonctions d'exécution du shell.

Oui, vous pouvez le faire en PHP. Mais en plus de PHP, il serait sage d'utiliser un gestionnaire de file d'attente. Voici la stratégie:

  1. Cassez votre grande tâche en tâches plus petites. Dans votre cas, chaque tâche pourrait être charger une seule page.

  2. Envoyer chaque petite tâche à la file d'attente.

  3. Exécuter quelque part vos travailleurs de la file d'attente.

En utilisant cette stratégie présente les avantages suivants:

  1. Pour de longues tâches en cours d'exécution, il a la capacité de récupérer au cas où un problème fatal se produit au milieu de la course - pas besoin de démarrer à partir du début

  2. .
  3. Si vos tâches ne doivent pas être exécutés de manière séquentielle, vous pouvez exécuter plusieurs travailleurs pour exécuter des tâches en même temps.

Vous avez une variété d'options (ce qui est juste quelques-uns):

  1. RabbitMQ ( https://www.rabbitmq.com/tutorials/tutorial- un php.html )
  2. ZeroMQ ( http://zeromq.org/bindings:php )
  3. Si vous utilisez le cadre Laravel, les files d'attente sont intégrés ( https://laravel.com /docs/5.4/queues ), avec des pilotes pour AWS SES, Redis, beanstalkd

PHP peut ou peut ne pas être le meilleur outil, mais vous savez comment l'utiliser, et est écrit le reste de votre application qui l'utilise. Ces deux qualités, combinées au fait que PHP est de faire un cas assez fort « assez bon » pour l'utiliser, au lieu de Perl, Ruby ou Python.

Si votre objectif est d'apprendre une autre langue, puis choisir un et de l'utiliser. Toute langue que vous avez mentionné fera le travail, pas de problème. Je suis comme Perl, mais ce que vous aimez peut-être différent.

Symcbean a quelques bons conseils sur la façon de gérer les processus d'arrière-plan à son lien.

En bref, écrire un script PHP CLI pour gérer les longs morceaux. Assurez-vous que les rapports d'état d'une certaine façon. Faire une page php pour gérer les mises à jour de statut, soit en utilisant AJAX ou les méthodes traditionnelles. Votre script coup d'envoi sera le début du processus en cours d'exécution dans sa propre session, et le retour confirmation que le processus va.

Bonne chance.

Je suis d'accord avec les réponses qui disent que cela devrait être exécuté dans un processus d'arrière-plan. Mais il est également important que vous rapport sur l'état afin que l'utilisateur sait que le travail est fait.

Lors de la réception de la demande de PHP pour lancer le processus, vous pouvez stocker dans une base de données d'une représentation de la tâche avec un identifiant unique. Ensuite, démarrez le processus de grattage écran, il passe l'identifiant unique. Rendre compte à l'application iPhone que la tâche a été démarré et qu'il doit vérifier une URL spécifiée, contenant le nouvel ID de tâche, pour obtenir le dernier statut. L'application iPhone peut maintenant surveiller (ou même « sondage long ») cette URL. Entre-temps, le processus d'arrière-plan mettrait à jour la représentation de la base de données de la tâche qu'il a travaillé avec un pourcentage d'achèvement, étape en cours, ou quel que soit d'autres indicateurs d'état que vous souhaitez. Et quand il a fini, il fixerait un drapeau rempli.

Vous pouvez l'envoyer comme une demande XHR (Ajax). Les clients ne sont généralement pas délai d'attente pour les requêtes XHR, contrairement à des requêtes HTTP normales.

Je sais que c'est une question tout à fait vieux mais voudrait donner un coup de feu. Ce script tente de répondre à la fois le coup initial de l'appel pour terminer rapidement et abattre la lourde charge en petits morceaux. Je ne l'ai pas testé cette solution.

<?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.';
}

Je voudrais proposer une solution qui est un peu différente de celle des symcbean, principalement parce que j'ai exigence supplémentaire que le processus long besoin d'être exécuté comme un autre utilisateur, et non en tant qu'utilisateur apache / www-data.

La première solution à l'aide Cron au sondage une table de tâches de fond:

  • inserts page web PHP dans une table de tâches de fond, état 'PRESENTEE'
  • Cron exécute une fois toutes les 3 minutes, en utilisant un autre utilisateur, en cours d'exécution script PHP CLI qui vérifie la table des tâches d'arrière-plan pour les lignes 'soumis'
  • CLI PHP mettra à jour la colonne d'état de la ligne en « traitement » et de commencer le traitement, après achèvement, il sera mis à jour sur « REMPLIR »

Deuxième solution utilisant Linux inotify installation:

  • page Web PHP met à jour un fichier de contrôle avec les paramètres définis par l'utilisateur, et donnant également un identifiant de tâche
  • script shell (en tant qu'utilisateur non-www) en cours d'exécution inotifywait attendra le fichier de contrôle à écrire
  • après le fichier de contrôle est écrit, un événement close_write sera soulevé un script shell va continuer
  • script shell exécute PHP CLI pour faire le processus long
  • PHP CLI écrit la sortie dans un fichier journal identifié par ID de tâche, ou encore des mises à jour des progrès dans un tableau d'état
  • page Web PHP pourrait interroger le fichier journal (basé sur l'ID de tâche) pour montrer les progrès du processus long, ou il pourrait également le tableau d'état requête

Quelques informations supplémentaires pourraient être trouvées dans mon post: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html

Je l'ai fait des choses similaires avec Perl, double fork () et détacher du processus parent. Tous les travaux aller chercher http doit être fait dans le processus fourchue.

Utilisez un proxy pour déléguer la demande.

ce que j'utilise toujours est l'une de ces variantes (parce que les différentes saveurs de Linux ont des règles différentes sur la sortie de manutention / une sortie de programmes différemment):

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 &');

Vous pouvez installer Havet "nohup". Mais par exemple, quand je converstions vidéo AUTOMATISATION DES FFMPEG, l'interface de sortie est en quelque sorte pas 100% manipulé en redirigeant les flux de sortie 1 & 2, donc j'utilisé nohup et réorienté la sortie.

si vous avez long script puis diviser le travail de la page avec l'aide du paramètre d'entrée pour chaque tâche. (Alors chaque acte de la page comme un fil) i.e. si la page a 1 lac boucle de processus à long product_keywords alors au lieu de la logique de faire de boucle pour un mot-clé et passez ce mot-clé de la magie ou cornjobpage.php (dans l'exemple suivant)

et pour les travailleurs de fond je pense que vous devriez essayer cette technique, il vous aidera à appeler autant que les pages que vous aimez toutes les pages fonctionneront à la fois de manière indépendante, sans attendre chaque réponse de page asynchrone.

cornjobpage.php // mainpage

    <?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
    ?>

PS: si vous voulez envoyer des paramètres d'URL en boucle puis suivre cette réponse: https://stackoverflow.com/a/41225209 / 6295712

Pas la meilleure approche, comme beaucoup dit, mais cela pourrait aider:

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

Si la sortie désirée de votre script est un certain traitement, pas une page Web, alors je crois que la solution désirée est d'exécuter votre script shell, simplement comme

php my_script.php

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top