2012-02-10 27 views
7

Muszę złomować stronę internetową, gdzie muszę pobrać wiele adresów URL, a następnie przetwarzać je jeden po drugim. Obecny proces wygląda nieco inaczej.Pobieranie stron równolegle za pomocą PHP

Pobieram podstawowy adres URL i otrzymuję wszystkie dodatkowe adresy URL z tej strony, następnie dla każdego dodatkowego URL-a, który pobieram ten adres URL, przetwarzam znalezioną stronę, pobierane są niektóre zdjęcia (co zajmuje dość dużo czasu) i zapisuję te dane w bazie danych, następnie pobierz następny URL i powtórz proces.

Podczas tego procesu tracę trochę czasu na pobieranie dodatkowego adresu URL na początku każdej iteracji. Dlatego próbuję pobrać kolejne adresy URL równolegle podczas przetwarzania pierwszej iteracji.

Rozwiązaniem moim zdaniem jest, od głównego procesu wywoływania skryptu PHP, powiedzmy downloader, który pobierze cały adres URL (z curl_multi lub wget) i zapisze je w bazie danych.

Moje pytania są

  • Jak nazwać takie downloder asynchronicznie, nie chcę mój główny skrypt czekać aż downloder kończy.
  • Dowolna lokalizacja do przechowywania pobranych danych, takich jak pamięć współdzielona. Oczywiście, oprócz bazy danych.
  • Są jakieś szanse, że dane zostaną uszkodzone podczas przechowywania i pobierania, jak tego uniknąć?
  • Ponadto proszę mnie poinformować, czy ktoś ma lepszy plan.
+2

PHP nie jest tak naprawdę zaprojektowane do uruchamiania wielu procesów. Dlaczego nie spojrzeć na taki język jak Python, aby to osiągnąć? – afuzzyllama

+0

@afuzzyllama To tylko podmoduł, cały projekt jest w PHP –

+0

[nodejs] (http://nodejs.org) byłby idealny do tego. – Xeoncross

Odpowiedz

6

Kiedy słyszę, że ktoś używa curl_multi_exec, zwykle się po prostu ładuje, powiedzmy, 100 adresów URL, a następnie zaczekaj, aż wszystko będzie kompletne, a następnie przetwórz je wszystkie, a następnie zacznij od następnych 100 adresów URL ... ja też tak robiłem, ale potem dowiedziałem się, że możliwe jest usuwanie/dodawanie uchwytów do curl_multi, gdy coś jest nadal w toku, i to naprawdę oszczędza dużo czasu, szczególnie jeśli ponownie wykorzystujesz już otwarte połączenia. Napisałem małą bibliotekę do obsługi kolejki żądań z oddzwanianiem; Nie jestem opublikowania pełnej wersji tutaj oczywiście („mały” jest jeszcze trochę kodu), ale tutaj jest uproszczoną wersją Najważniejsze, aby dać Ci ogólne pojęcie:

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)); 
} 

W mojej wersji $ zadania są w rzeczywistości oddzielną klasą, a nie instancjami kontrolerów lub modeli. Po prostu obsługują ustawianie opcji cURL, analizowanie odpowiedzi i wywoływanie danego wywołania zwrotnego onComplete. Dzięki tej strukturze nowe wnioski zaczną się, gdy tylko coś z basenu się skończy.

Oczywiście to naprawdę nie oszczędza, jeśli nie tylko pobieranie zajmuje trochę czasu, ale również przetwarzanie ... I to nie jest prawdziwa obsługa równoległa. Ale wciąż mam nadzieję, że to pomaga. :)

P.S. zrobiłem dla mnie sztuczkę. :) Po 8-godzinnej pracy kończy się w 3-4 minutach przy użyciu puli 50 połączeń. Nie potrafię opisać tego uczucia. :) Nie spodziewałem się, że będzie działał zgodnie z planem, ponieważ z PHP rzadko działa dokładnie tak, jak powinno ... To było jak "ok, mam nadzieję, że skończy się co najmniej godzinę ... Co ... Poczekaj ... Już teraz ?! 8-O "

+0

Dzięki za udostępnienie, możesz również znaleźć inne biblioteki, takie jak ta, jeśli przeszukasz github. – Xeoncross

+0

Naprawdę przydatna odpowiedź, ale twoja uwaga bardzo mi pomogła, dzięki człowieku ... –

+0

Witamy. :) Zgaduję, że musiałem włożyć to do odpowiedzi ... – Slava

2

Można użyć curl_multi: http://www.somacon.com/p537.php

Można również rozważyć robi to po stronie klienta i przy użyciu JavaScript.


Innym rozwiązaniem jest napisanie hunter/zbieraczy że dołączania tablicę adresów URL, to działa równolegle i zwraca tablicę JSON po to zakończone.

Innymi słowy: gdybyś miał 100 adresów URL, mógłbyś POSTOWAĆ tę tablicę (prawdopodobnie również jako JSON) do mysite.tld/huntergatherer - robi to, co chce, w dowolnym języku i po prostu zwraca JSON.

+0

Yeh, już używam curl_multi, javascript brzmi dobrze, czy mogę uzyskać więcej szczegółów? –

0

Spróbuj wykonać z PHP, skrypty python-pycurl. Łatwiej, szybciej niż curl PHP.

2

Oprócz rozwiązania multi curl, kolejnym jest po prostu partia gearman workers. Jeśli pójdziesz tą trasą, znalazłem dobry sposób na załadowanie się demonicznych pracowników.

+0

Dzięki! Nigdy wcześniej nie słyszałem o tym rozszerzeniu. – Slava

1

rzeczy należy zwracać się dodatkowo do zwijania Multi:

  • non-blocking strumienie (Przykład: PHP-MIO)
  • ZeroMQ na tarło się wielu pracowników, które wykonują żądań asynchronicznie

Podczas node.js, ruby ​​EventMachine lub podobne narzędzia doskonale nadają się do robienia tych rzeczy, o czym wspomniałem również w PHP.

+0

Nie blokujące strumienie to naprawdę dobra lektura, przepraszam, ale mogę wybrać tylko jedną odpowiedź najlepiej. dzięki –

Powiązane problemy