Загрузка страниц параллельно с использованием PHP


Мне нужно удалить веб-сайт, на котором мне нужно получить несколько URL-адресов, а затем обработать их один за другим. Текущий процесс в какой-то степени идет так.

Я выбираю базовый URL-адрес и получаю все вторичные URL-адреса с этой страницы, затем для каждого вторичного URL-адреса я выбираю этот URL-адрес, обрабатываю найденную страницу, загружаю несколько фотографий (что занимает довольно много времени) и сохраняю эти данные в базе данных, затем извлекаю следующий URL-адрес и повторяю процесс.

В этом процессе я думаю, что трачу некоторое время на извлечение дополнительный URL-адрес в начале каждой итерации. Поэтому я пытаюсь параллельно получать следующие URL-адреса при обработке первой итерации.

Решение, на мой взгляд, состоит в том, чтобы из основного процесса вызвать PHP-скрипт, скажем, загрузчик, который загрузит все URL-адреса (с curl_multi или wget) и сохранит их в какой-либо базе данных.

Мои вопросы

  • Как вызвать такой даунлодер асинхронно, я не хочу, чтобы мой основной скрипт ждал завершения даунлодера.
  • Любой расположение для хранения загруженных данных, таких как общая память. Конечно, кроме базы данных.
  • Есть ли вероятность того, что данные будут повреждены при хранении и извлечении, как этого избежать?
  • Кроме того, пожалуйста, сообщите мне, есть ли у кого-нибудь план получше.
Author: Yi Jiang, 2012-02-10

5 answers

Когда я слышу, что кто-то использует curl_multi_exec, обычно оказывается, что они просто загружают его, скажем, с 100 URL-адресами, затем ждут, когда все завершится, а затем обрабатывают их все, а затем начинают со следующих 100 URL-адресов... Вините меня, я тоже так делал, но потом я узнал, что можно удалить/добавить дескрипторы в curl_multi, пока что-то еще продолжается, и это действительно экономит много времени, особенно если вы повторно используете уже открытые соединения. Я написал небольшую библиотеку для обработки очереди запросов с обратными вызовами; Я, конечно, не публикую здесь полную версию ("маленький" - это все еще довольно много кода), но вот упрощенная версия главного, чтобы дать вам общее представление:

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

В моей версии $задания на самом деле относятся к отдельному классу, а не экземплярам контроллеров или моделей. Они просто обрабатывают параметры настройки CURL, анализируют ответ и вызывают данный обратный вызов в полном объеме. С помощью этой структуры новые запросы будут запускаться, как только что-то из пула завершится.

Конечно, это на самом деле это не спасет вас, если не только извлечение требует времени, но и обработка... И это не настоящая параллельная обработка. Но я все еще надеюсь, что это поможет. :)

P.S. сделал для меня трюк. :) Однажды 8-часовая работа теперь выполняется за 3-4 минуты с использованием пула из 50 подключений. Не могу описать это чувство. :) Я действительно не ожидал, что он будет работать так, как планировалось, потому что с PHP он редко работает точно так, как предполагалось... Это было похоже на "хорошо, надеюсь, это закончится по крайней мере через час... Что?.. Ждать... Уже?! 8-О"

 6
Author: Slava, 2015-03-13 23:51:53

Вы можете использовать curl_multi: http://www.somacon.com/p537.php

Вы также можете рассмотреть возможность выполнения этого на стороне клиента и использования Javascript.


Другое решение - написать охотника/собирателя, которому вы отправляете массив URL-адресов, затем он выполняет параллельную работу и возвращает массив JSON после его завершения.

Другими словами: если бы у вас было 100 URL-адресов, вы могли бы ОПУБЛИКОВАТЬ этот массив (возможно, также в формате JSON) в mysite.tld/huntergatherer - он делает все, что угодно хочет на любом языке, который вы хотите, и просто возвращает JSON.

 2
Author: Grok, 2012-02-10 19:18:39

Помимо решения curl multi, у другого просто есть группа рабочих-редукторов. Если вы пойдете по этому пути, я нашел supervisord хороший способ запустить загрузку работников deamon.

 2
Author: Wrikken, 2012-02-10 21:10:43

Вещи, на которые вам следует обратить внимание в дополнение к CURL multi:

  • Неблокирующие потоки (пример: PHP-MIO)
  • ZeroMQ для создания множества рабочих, которые выполняют запросы асинхронно

В то время как node.js , ruby EventMachine или аналогичные инструменты отлично подходят для этого, вещи, о которых я упоминал, также делают это довольно простым в PHP.

 1
Author: igorw, 2012-02-13 09:01:18

Попробуйте выполнить из скриптов PHP, python-pycurl. Проще, быстрее, чем PHP curl.

 0
Author: danielpopa, 2012-02-10 19:35:26