Загрузка страниц параллельно с использованием PHP
Мне нужно удалить веб-сайт, на котором мне нужно получить несколько URL-адресов, а затем обработать их один за другим. Текущий процесс в какой-то степени идет так.
Я выбираю базовый URL-адрес и получаю все вторичные URL-адреса с этой страницы, затем для каждого вторичного URL-адреса я выбираю этот URL-адрес, обрабатываю найденную страницу, загружаю несколько фотографий (что занимает довольно много времени) и сохраняю эти данные в базе данных, затем извлекаю следующий URL-адрес и повторяю процесс.
В этом процессе я думаю, что трачу некоторое время на извлечение дополнительный URL-адрес в начале каждой итерации. Поэтому я пытаюсь параллельно получать следующие URL-адреса при обработке первой итерации.
Решение, на мой взгляд, состоит в том, чтобы из основного процесса вызвать PHP-скрипт, скажем, загрузчик, который загрузит все URL-адреса (с curl_multi
или wget
) и сохранит их в какой-либо базе данных.
Мои вопросы
- Как вызвать такой даунлодер асинхронно, я не хочу, чтобы мой основной скрипт ждал завершения даунлодера.
- Любой расположение для хранения загруженных данных, таких как общая память. Конечно, кроме базы данных.
- Есть ли вероятность того, что данные будут повреждены при хранении и извлечении, как этого избежать?
- Кроме того, пожалуйста, сообщите мне, есть ли у кого-нибудь план получше.
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-О"
Вы можете использовать curl_multi: http://www.somacon.com/p537.php
Вы также можете рассмотреть возможность выполнения этого на стороне клиента и использования Javascript.
Другое решение - написать охотника/собирателя, которому вы отправляете массив URL-адресов, затем он выполняет параллельную работу и возвращает массив JSON после его завершения.
Другими словами: если бы у вас было 100 URL-адресов, вы могли бы ОПУБЛИКОВАТЬ этот массив (возможно, также в формате JSON) в mysite.tld/huntergatherer - он делает все, что угодно хочет на любом языке, который вы хотите, и просто возвращает JSON.
Помимо решения curl multi, у другого просто есть группа рабочих-редукторов. Если вы пойдете по этому пути, я нашел supervisord
хороший способ запустить загрузку работников deamon.
Вещи, на которые вам следует обратить внимание в дополнение к CURL multi:
- Неблокирующие потоки (пример: PHP-MIO)
- ZeroMQ для создания множества рабочих, которые выполняют запросы асинхронно
В то время как node.js , ruby EventMachine или аналогичные инструменты отлично подходят для этого, вещи, о которых я упоминал, также делают это довольно простым в PHP.
Попробуйте выполнить из скриптов PHP, python-pycurl. Проще, быстрее, чем PHP curl.