Mam skrypt PHP, którego wykonanie zajmuje dużo czasu (5-30 minut). Na wszelki wypadek, skrypt używa curl do pobierania danych z innego serwera. To jest powód, dla którego trwa to tak długo; musi czekać na załadowanie każdej strony, zanim ją przetworzy i przejdzie do następnej.
Chcę mieć możliwość zainicjowania skryptu i pozostawienia go, dopóki nie zostanie ukończony, co spowoduje ustawienie flagi w tabeli bazy danych.
Muszę wiedzieć, jak zakończyć żądanie http przed zakończeniem działania skryptu. Czy skrypt PHP jest najlepszym sposobem na to?
php
apache
curl
httprequest
kbanman
źródło
źródło
Goutte
iGuzzle
implementował wątki współbieżności. Możesz również spojrzeć naGearman
uruchamianie równoległych żądań w postaci pracowników.Odpowiedzi:
Z pewnością można to zrobić za pomocą PHP, jednak NIE należy tego robić w tle - nowy proces należy oddzielić od grupy procesów, w której jest inicjowany.
Ponieważ ludzie ciągle udzielają tej samej złej odpowiedzi na to często zadawane pytania, tutaj napisałem pełniejszą odpowiedź:
http://symcbean.blogspot.com/2010/02/php-and-long-running-processes.html
Z komentarzy:
źródło
shell_exec('echo /usr/bin/php -q longThing.php | at now');
tylko powody, dla których umieszczenie tutaj jest trochę długie.Szybkim i nieczystym sposobem byłoby użycie
ignore_user_abort
funkcji w php. To w zasadzie mówi: Nie obchodzi mnie, co robi użytkownik, uruchamiaj ten skrypt, dopóki nie zostanie zakończony. Jest to nieco niebezpieczne, jeśli jest to witryna publiczna (ponieważ jest możliwe, że w końcu uruchomisz 20 ++ wersji skryptu w tym samym czasie, jeśli zostanie zainicjowany 20 razy)."Czystym" sposobem (przynajmniej IMHO) jest ustawienie flagi (na przykład w bazie danych), gdy chcesz zainicjować proces i uruchamiać cronjob co godzinę (lub tak), aby sprawdzić, czy ta flaga jest ustawiona. Jeśli jest ustawiony, uruchamia się długo działający skrypt, jeśli NIE jest ustawiony, nic się nie dzieje.
źródło
header("Connection: close", true);
. I nie zapomnij o flush ()Możesz użyć exec lub system, aby rozpocząć zadanie w tle, a następnie wykonać tę pracę.
Istnieją również lepsze metody skrobania sieci niż ta, której używasz. Możesz zastosować podejście wielowątkowe (wiele wątków wykonujących jedną stronę na raz) lub jedno wykorzystujące pętlę zdarzeń (jeden wątek wykonujący wiele stron jednocześnie). Moim osobistym podejściem do używania Perla byłoby użycie AnyEvent :: HTTP .
ETA: symcbean wyjaśnił, jak prawidłowo odłączyć proces w tle tutaj .
źródło
Nie, PHP nie jest najlepszym rozwiązaniem.
Nie jestem pewien co do Rubiego czy Perla, ale w Pythonie mógłbyś przepisać skrobak strony, aby był wielowątkowy i prawdopodobnie działałby co najmniej 20x szybciej. Pisanie aplikacji wielowątkowych może być pewnym wyzwaniem, ale pierwsza napisana przeze mnie aplikacja Pythona była wielowątkowym skrobakiem stron. Możesz po prostu wywołać skrypt Pythona ze strony PHP, używając jednej z funkcji wykonywania powłoki.
źródło
Tak, możesz to zrobić w PHP. Ale oprócz PHP rozsądnie byłoby użyć menedżera kolejek. Oto strategia:
Podziel duże zadanie na mniejsze zadania. W twoim przypadku każde zadanie może ładować jedną stronę.
Wyślij każde małe zadanie do kolejki.
Uruchom gdzieś swoich pracowników w kolejce.
Korzystanie z tej strategii ma następujące zalety:
W przypadku długotrwałych zadań ma możliwość regeneracji w przypadku wystąpienia poważnego problemu w połowie biegu - nie trzeba zaczynać od początku.
Jeśli zadania nie muszą być uruchamiane sekwencyjnie, możesz uruchomić wielu pracowników, aby wykonywać zadania jednocześnie.
Masz wiele opcji (to tylko kilka):
źródło
PHP może być najlepszym narzędziem lub nie, ale wiesz, jak go używać, a reszta aplikacji jest napisana przy jego użyciu. Te dwie cechy, w połączeniu z faktem, że PHP jest „wystarczająco dobry”, stanowią całkiem mocne argumenty przemawiające za jego użyciem, zamiast Perla, Ruby czy Pythona.
Jeśli Twoim celem jest nauczenie się innego języka, wybierz jeden i używaj go. Każdy język, o którym wspomniałeś, spełni swoje zadanie, nie ma problemu. Tak się składa, że lubię Perla, ale to, co lubisz, może być inne.
Symcbean ma pod swoim łączem kilka dobrych rad dotyczących zarządzania procesami w tle.
Krótko mówiąc, napisz skrypt CLI PHP do obsługi długich bitów. Upewnij się, że w jakiś sposób zgłasza stan. Utwórz stronę php do obsługi aktualizacji statusu, używając AJAX lub tradycyjnych metod. Twój skrypt kickoff uruchomi proces działający we własnej sesji i zwróci potwierdzenie, że proces trwa.
Powodzenia.
źródło
Zgadzam się z odpowiedziami, które mówią, że powinno to być uruchamiane w tle. Ale ważne jest również, aby zgłaszać stan, aby użytkownik wiedział, że praca jest wykonywana.
Po otrzymaniu żądania PHP, aby rozpocząć proces, możesz przechowywać w bazie danych reprezentację zadania z unikalnym identyfikatorem. Następnie rozpocznij proces skrobania ekranu, przekazując mu unikalny identyfikator. Zgłoś ponownie aplikacji na iPhone'a, że zadanie zostało uruchomione i że powinna sprawdzić określony adres URL zawierający nowy identyfikator zadania, aby uzyskać najnowszy stan. Aplikacja na iPhone może teraz sondować (lub nawet „długo sondować”) ten adres URL. W międzyczasie proces w tle zaktualizowałby reprezentację zadania w bazie danych, tak jak działało, z procentem ukończenia, bieżącym krokiem lub innymi wskaźnikami stanu, które chcesz. A kiedy skończy, ustawia flagę ukończenia.
źródło
Możesz wysłać go jako żądanie XHR (Ajax). Klienci zwykle nie mają limitu czasu dla XHR, w przeciwieństwie do zwykłych żądań HTTP.
źródło
Zdaję sobie sprawę, że jest to dość stare pytanie, ale chciałbym spróbować. Ten skrypt próbuje zająć się zarówno początkowym wezwaniem, aby szybko zakończyć i pociąć duże obciążenie na mniejsze kawałki. Nie testowałem tego rozwiązania.
<?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.'; }
źródło
Chciałbym zaproponować rozwiązanie, które trochę różni się od Symcbean, głównie dlatego, że mam dodatkowe wymaganie, aby długo działający proces musiał być uruchamiany jako inny użytkownik, a nie jako użytkownik apache / www-data.
Pierwsze rozwiązanie wykorzystujące cron do sondowania tabeli zadań w tle:
Drugie rozwiązanie wykorzystujące funkcję inotify w systemie Linux:
Dodatkowe informacje można znaleźć w moim poście: http://inventorsparadox.blogspot.co.id/2016/01/long-running-process-in-linux-using-php.html
źródło
Zrobiłem podobne rzeczy z Perlem, podwójnym widelcem () i odłączeniem od procesu rodzica. Wszystkie prace związane z pobieraniem http powinny być wykonywane w procesie rozwidlonym.
źródło
Użyj serwera proxy do delegowania żądania.
źródło
ZAWSZE używam jednego z tych wariantów (ponieważ różne wersje Linuksa mają różne zasady dotyczące obsługi danych wyjściowych / niektóre programy działają inaczej):
Wariant I @exec ('./ myscript.php \ 1> / dev / null \ 2> / dev / null &');
Wariant II @exec ('php -f myscript.php \ 1> / dev / null \ 2> / dev / null &');
Wariant III @exec ('nohup myscript.php \ 1> / dev / null \ 2> / dev / null &');
Być może będziesz musiał zainstalować "nohup". Ale na przykład, kiedy automatyzowałem konwersje wideo FFMPEG, interfejs wyjściowy nie był w 100% obsługiwany przez przekierowywanie strumieni wyjściowych 1 i 2, więc użyłem nohup I przekierowałem wyjście.
źródło
jeśli masz długi skrypt, podziel pracę strony za pomocą parametru wejściowego dla każdego zadania. (wtedy każda strona działa jak wątek) tj. jeśli strona ma 1 lac product_keywords długa pętla procesu, to zamiast pętli stwórz logikę dla jednego słowa kluczowego i przekaż to słowo kluczowe from magic lub cornjobpage.php (w poniższym przykładzie)
a dla pracownika pracującego w tle myślę, że powinieneś wypróbować tę technikę, pomoże ona wywołać dowolną liczbę stron, wszystkie strony będą działać jednocześnie niezależnie, bez oczekiwania na każdą odpowiedź strony jako asynchroniczną.
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: jeśli chcesz wysłać parametry adresu URL jako pętlę, postępuj zgodnie z tą odpowiedzią: https://stackoverflow.com/a/41225209/6295712
źródło
Nie jest to najlepsze podejście, jak wielu tutaj stwierdziło, ale może to pomóc:
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
źródło
Jeśli pożądanym wyjściem twojego skryptu jest przetwarzanie, a nie strona internetowa, to uważam, że pożądanym rozwiązaniem jest uruchomienie skryptu z powłoki, tak jak
php my_script.php
źródło