Ich habe ein PHP-Skript, dessen Fertigstellung lange dauert (5-30 Minuten). Nur für den Fall, dass es darauf ankommt, verwendet das Skript Curl, um Daten von einem anderen Server zu kratzen. Dies ist der Grund, warum es so lange dauert; Es muss warten, bis jede Seite geladen ist, bevor es verarbeitet und zur nächsten übergegangen wird.
Ich möchte in der Lage sein, das Skript zu initiieren und es so lange laufen zu lassen, bis es fertig ist, wodurch ein Flag in einer Datenbanktabelle gesetzt wird.
Ich muss wissen, wie ich die http-Anforderung beenden kann, bevor das Skript ausgeführt wird. Ist ein PHP-Skript auch der beste Weg, dies zu tun?
php
apache
curl
httprequest
kbanman
quelle
quelle
Goutte
undGuzzle
Parallelitätsthreads verwenden und implementieren. Sie können auch einen Blick darauf werfenGearman
, um parallele Anfragen in Form von Arbeitern zu starten.Antworten:
Natürlich kann dies mit PHP durchgeführt werden, Sie sollten dies jedoch NICHT als Hintergrundaufgabe tun - der neue Prozess muss aus der Prozessgruppe entfernt werden, in der er initiiert wird.
Da die Leute immer wieder die gleiche falsche Antwort auf diese FAQ geben, habe ich hier eine ausführlichere Antwort geschrieben:
http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html
Aus den Kommentaren:
quelle
shell_exec('echo /usr/bin/php -q longThing.php | at now');
aber die Gründe dafür sind hier etwas lang.Der schnelle und schmutzige Weg wäre, die
ignore_user_abort
Funktion in PHP zu verwenden. Dies sagt im Grunde: Es ist egal, was der Benutzer tut, führen Sie dieses Skript aus, bis es fertig ist. Dies ist etwas gefährlich, wenn es sich um eine öffentlich zugängliche Site handelt (da möglicherweise 20 ++ Versionen des Skripts gleichzeitig ausgeführt werden, wenn es 20 Mal gestartet wird).Die "saubere" Methode (zumindest IMHO) besteht darin, ein Flag zu setzen (z. B. in der Datenbank), wenn Sie den Prozess starten und jede Stunde (oder so) einen Cronjob ausführen möchten, um zu überprüfen, ob dieses Flag gesetzt ist. Wenn es gesetzt ist, startet das Skript mit langer Laufzeit. Wenn es NICHT gesetzt ist, passiert nichts.
quelle
header("Connection: close", true);
. Und vergessen Sie nicht zu spülen ()Sie können exec oder system verwenden , um einen Hintergrundjob zu starten und dann die Arbeit darin zu erledigen.
Es gibt auch bessere Ansätze zum Scraping des Webs als das, das Sie verwenden. Sie können einen Thread-Ansatz verwenden (mehrere Threads führen jeweils eine Seite aus) oder einen Eventloop (ein Thread führt mehrere Seiten gleichzeitig aus). Mein persönlicher Ansatz bei der Verwendung von Perl wäre die Verwendung von AnyEvent :: HTTP .
ETA: symcbean erklärte , wie der Hintergrundprozess richtig lösen hier .
quelle
Nein, PHP ist nicht die beste Lösung.
Ich bin mir bei Ruby oder Perl nicht sicher, aber mit Python könnten Sie Ihren Seitenschaber so umschreiben, dass er über mehrere Threads verfügt, und er würde wahrscheinlich mindestens 20-mal schneller laufen. Das Schreiben von Multithread-Apps kann eine Herausforderung sein, aber die allererste Python-App, die ich geschrieben habe, war ein Seitenschaber mit mehreren Threads. Und Sie können das Python-Skript einfach von Ihrer PHP-Seite aus aufrufen, indem Sie eine der Shell-Ausführungsfunktionen verwenden.
quelle
Ja, Sie können es in PHP tun. Zusätzlich zu PHP ist es jedoch ratsam, einen Warteschlangenmanager zu verwenden. Hier ist die Strategie:
Teilen Sie Ihre große Aufgabe in kleinere Aufgaben auf. In Ihrem Fall könnte jede Aufgabe das Laden einer einzelnen Seite sein.
Senden Sie jede kleine Aufgabe an die Warteschlange.
Führen Sie Ihre Warteschlangenarbeiter irgendwo aus.
Die Verwendung dieser Strategie hat folgende Vorteile:
Bei Aufgaben mit langer Laufzeit kann das Gerät wiederhergestellt werden, falls mitten im Lauf ein schwerwiegendes Problem auftritt. Sie müssen nicht von vorne beginnen.
Wenn Ihre Aufgaben nicht nacheinander ausgeführt werden müssen, können Sie mehrere Worker ausführen, um Aufgaben gleichzeitig auszuführen.
Sie haben verschiedene Möglichkeiten (dies sind nur einige):
quelle
PHP kann das beste Tool sein oder auch nicht, aber Sie wissen, wie man es verwendet, und der Rest Ihrer Anwendung wird damit geschrieben. Diese beiden Eigenschaften, kombiniert mit der Tatsache, dass PHP "gut genug" ist, sind ein ziemlich starkes Argument für die Verwendung anstelle von Perl, Ruby oder Python.
Wenn Sie eine andere Sprache lernen möchten, wählen Sie eine aus und verwenden Sie sie. Jede Sprache, die Sie erwähnt haben, erledigt den Job, kein Problem. Ich mag Perl, aber was du magst, kann anders sein.
Symcbean hat unter seinem Link einige gute Ratschläge zum Verwalten von Hintergrundprozessen.
Kurz gesagt, schreiben Sie ein CLI-PHP-Skript, um die langen Bits zu verarbeiten. Stellen Sie sicher, dass der Status auf irgendeine Weise gemeldet wird. Erstellen Sie eine PHP-Seite, um Statusaktualisierungen entweder mit AJAX oder mit herkömmlichen Methoden durchzuführen. Ihr Kickoff-Skript startet den Prozess, der in einer eigenen Sitzung ausgeführt wird, und gibt die Bestätigung zurück, dass der Prozess ausgeführt wird.
Viel Glück.
quelle
Ich stimme den Antworten zu, die besagen, dass dies in einem Hintergrundprozess ausgeführt werden sollte. Es ist aber auch wichtig, dass Sie über den Status berichten, damit der Benutzer weiß, dass die Arbeit erledigt wird.
Wenn Sie die PHP-Anforderung zum Starten des Prozesses erhalten, können Sie eine Darstellung der Aufgabe mit einer eindeutigen Kennung in einer Datenbank speichern. Starten Sie dann den Screen-Scraping-Prozess und übergeben Sie ihm die eindeutige Kennung. Melden Sie der iPhone-App, dass die Aufgabe gestartet wurde und dass eine angegebene URL mit der neuen Aufgaben-ID überprüft werden sollte, um den neuesten Status zu erhalten. Die iPhone-Anwendung kann diese URL jetzt abfragen (oder sogar "lange abrufen"). In der Zwischenzeit aktualisiert der Hintergrundprozess die Datenbankdarstellung der Aufgabe, da sie mit einem Abschlussprozentsatz, einem aktuellen Schritt oder anderen gewünschten Statusindikatoren funktioniert. Und wenn es fertig ist, würde es ein abgeschlossenes Flag setzen.
quelle
Sie können es als XHR-Anfrage (Ajax) senden. Clients haben im Gegensatz zu normalen HTTP-Anforderungen normalerweise keine Zeitüberschreitung für XHRs.
quelle
Mir ist klar, dass dies eine ziemlich alte Frage ist, aber ich würde sie gerne ausprobieren. Dieses Skript versucht, sowohl den ersten Kick-Off-Aufruf zu adressieren, um schnell fertig zu werden, als auch die schwere Last in kleinere Teile zu zerlegen. Ich habe diese Lösung nicht getestet.
<?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.'; }
quelle
Ich möchte eine Lösung vorschlagen, die sich ein wenig von der von symcbean unterscheidet, hauptsächlich weil ich zusätzlich die Anforderung habe, dass der lang laufende Prozess als ein anderer Benutzer und nicht als Apache / www-Datenbenutzer ausgeführt werden muss.
Erste Lösung mit cron zum Abrufen einer Hintergrundaufgabentabelle:
Zweite Lösung mit Linux inotify:
Einige zusätzliche Informationen finden Sie in meinem Beitrag: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html
quelle
Ich habe ähnliche Dinge mit Perl, double fork () und dem Trennen vom übergeordneten Prozess gemacht. Alle http-Abrufarbeiten sollten in einem gegabelten Prozess ausgeführt werden.
quelle
Verwenden Sie einen Proxy, um die Anforderung zu delegieren.
quelle
Was ich IMMER benutze, ist eine dieser Varianten (weil verschiedene Linux-Varianten unterschiedliche Regeln für den Umgang mit Ausgaben haben / einige Programme unterschiedlich ausgeben):
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 &');
Möglicherweise haben Sie "nohup" installiert. Als ich zum Beispiel FFMPEG-Videokonvertierungen automatisierte, wurde die Ausgabeschnittstelle durch die Umleitung der Ausgabestreams 1 und 2 nicht zu 100% behandelt, sodass ich nohup verwendet UND die Ausgabe umgeleitet habe.
quelle
Wenn Sie ein langes Skript haben, teilen Sie die Seitenarbeit mit Hilfe von Eingabeparametern für jede Aufgabe (dann verhält sich jede Seite wie ein Thread). Wenn die Seite eine lange Prozessschleife mit lac product_keywords hat, erstellen Sie anstelle der Schleife eine Logik für ein Schlüsselwort und übergeben Sie dieses Schlüsselwort von magic oder cornjobpage.php (im folgenden Beispiel)
und für Hintergrundarbeiter denke ich, dass Sie diese Technik ausprobieren sollten. Es wird hilfreich sein, so viele Seiten aufzurufen, wie Sie möchten. Alle Seiten werden unabhängig voneinander gleichzeitig ausgeführt, ohne auf jede Seitenantwort als asynchron zu warten.
cornjobpage.php // Hauptseite
<?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: Wenn Sie URL-Parameter als Schleife senden möchten, folgen Sie dieser Antwort: https://stackoverflow.com/a/41225209/6295712
quelle
Nicht der beste Ansatz, wie viele hier angegeben haben, aber dies könnte helfen:
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
quelle
Wenn die gewünschte Ausgabe Ihres Skripts eine Verarbeitung ist, keine Webseite, dann besteht meiner Meinung nach die gewünschte Lösung darin, Ihr Skript einfach wie folgt über die Shell auszuführen
php my_script.php
quelle