curl multi exec php

Многопоточное скачивание в cURL на PHP

В данном топике представлена на мой взгляд удобная и функциональная реализация многопоточного скачивания на cURL для PHP. Возможно кому-то она будет полезна, а мне принесёт инвайт 😉

Скачиванием через cURL не пользовался пусть даже из интереса только ленивый. Будь-то из консоли, либо реализуя код на каком-либо ЯП. Решения блокирующего скачивания одной ссылки валяются на каждом углу сети, к примеру на php.net. Однако, если рассматривать реализации на PHP, то такой подход подчас не подходит ввиду высоких временных затрат на вспомогательные операции ( dns lookup, request waiting и подобные ). Для скачивания большого числа страниц последовательный вариант не приемлем. Если устраивает — дальше можно не читать 🙂

В Perl, к примеру, можно применять fork() либо нити (use threads) для распараллеливания однопоточных скачиваний. Это не считая богатых возможностей библиотек данного языка. Я лично применял нити и LWP. Однако, речь идёт о PHP, и тут с распараллеливанием большие проблемы ввиду отсутствия данной возможности в принципе. Если кто знает, как создавать нити, сообщите, но я не нашел пока достойных решений. Да, в cURL есть функции curl_multi_*, но вот примеры реализаций на их основе меня не устроили. И, в итоге, решил собрать свой велосипед.

Первоначально отсылаю к простейшему примеру из офф. справочника. Позволю себе привести его тут 🙂
// create both cURL resources
$ch1 = curl_init ();
$ch2 = curl_init ();

// set URL and other appropriate options

//create the multiple cURL handle
$mh = curl_multi_init ();

// тут какая-то обработка текста страницы

Вот такая функция. Еще можно было бы написать про работу с куками и POST-запросами, но это уж если получу инвайт. И так понаписал много, многие ли осилили? 😉

Источник

PHP cURL multi_exec delay between requests

If I run a standard cURL_multi_exec function (example below), I get all cURL handles requested at once. I would like to put a delay of 100ms between each request, is there a way to do that? (nothing found on Google & StackOverflow search)

I’ve tried usleep() before curl_multi_exec() which slows down the script but does not postpone each request.

I’m working on this all day, any help would be greatly appreciated! Thank you.

EDIT: Any other non-cUrl method? That would also answer my question.

4 Answers 4

Don’t think you can. If you run this from the cli, you could instead fork your script into 10 processes and then fire regular curl requests from each. That would allow you fine grained control over the timing.

curl multi exec php

PHP is not solution for that. Forking the script won’t help too. In the beginnings yes but once you have a little bit more websites you need to grab like that you will find yourself as your sever very, very red. In terms of costs and in terms of script stability you should reconsider using some other idea.

You can do that with Python easily and in case of non-blocking real time calls to API endpoints you should use stuff like Socket.IO + Node.JS or just Node.JS or well, huh. lol

In case that you do not have time nor will you can use stuff like this:

It actually all depends on what are you trying to achieve.

Источник

Curl multi exec php

API библиотеки libcurl на С

По большей части здесь представлен перевод страниц сайта curl.haxx.se с моими соображениями в дополнение. Многие из приведенных здесь функций окажутся неизвестными для PHP-кодера. Cвязь двух языков рассмотрена ниже.

Основная часть

Примерный порядок работы такой: создать простые дескрипторы, описать все нужные для них опции (для каждого дескриптора могут быть свои опции). Далее создать мульти-дескриптор функцией curl_multi_init(), затем добавить в него простые дескрипторы вызовом curl_multi_add_handle(). Замечание: во избежание глюков простой дескриптор нельзя использовать, пока он находится в стеке. Когда добавлены все дескрипторы на текущий момент (можно добавить еще в любое время), запустить трансферы вызовом curl_multi_perform().

CURLMcode curl_multi_perform (CURLM *multi_handle, int *running_handles)

До версии 7.20.0: если в функция возвращает CURLM_CALL_MULTI_PERFORM, обычно это означает, что вам нужно вызвать curl_multi_perform() снова, до select() или других действий (об этом чуть позже). Не обязательно делать это немедленно, но этот код возврата означает, что у libcurl возможно есть еще данные для возврата или отправки перед тем, как она будет «удовлетворена».

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

Как я не извращался, не смог получить другие CURLM_* коды, кроме CURLM_CALL_MULTI_PERFORM и CURLM_OK. Устойчивая система 🙂

В своем втором параметре эта функция хранит количество еще работающих трансферов (а не флаг, как сказано в PHP-справке), т.е. тех, кто еще не закончил прием/передачу данных. Трансферы могут завершаться ошибкой передачи, это тоже вариант. Когда какой-то трансфер завершится, счетчик уменьшится. Когда он станет равен 0, значит все транферы завершились.

Любопытный момент, который я обнаружил экспериментально: похоже, curl_multi_add_handle() кидает в стек копию простого дескриптора. К примеру, если закрыть дескриптор сразу после добавления, multi_curl_perform() все равно его запустит и обработает. Даже передача по ссылке, типа curl_multi_add_handle($mh, &$ch2), не меняет этот факт.

Функцию curl_multi_info_read() можно использовать для получения информации по завершенным трансферам.

CURLMsg *curl_multi_info_read ( CURLM *multi_handle, int *msgs_in_queue)

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

Если нужно остановить передачу по одному простому дескриптору в стеке, используйте curl_multi_remove_handle. После curl_multi_remove_handle() все еще можно использовать простой дескриптор для одиночных функций cURL, типа curl_exec() и т.п. Поэтому исключения дескриптора из стека не достаточно для корректного завершения работы. Нужно вызывать еще curl_easy_cleanup() для каждого дескриптора. После всей работы нужно закрыть мульти-дескриптор вызовом curl_multi_cleanup(). PHP-аналоги: curl_close и curl_multi_close.

Высший пилотаж

Сказанного выше уже достаточно, чтобы осознано использовать функции curl_multi_* в PHP. Однако, чтобы скрипт по минимуму использовал ресурсы сервера, а код выглядел более профессионально, нужно знать еще пару вещей.

Небольшой экскурс в язык программирования C. int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);

Если ни один из заданных дескрипторов не готов к назначенным действиям, то select() блокирует скрипт, пока хотя бы один дескриптор не будет готов, или пока не наступит таймаут, или выполнение не прервется сигналом. Параметр timeout определяет, как долго функция select() должна ждать. Если время вышло, функция должна завершиться.

int poll (struct pollfd fds[], nfds_t nfds, int timeout);

Теперь самое интересное. Ваше приложение может узнать у libcurl, когда библиотека готова к вызову для передачи данных. Поэтому не нужно дергать в цикле curl_multi_perform() до безумия. Функция curl_multi_fdset() предлагает интерфейс, которым вы можете извлекать наборы дескрипторов типа fd_set из libcurl, чтобы использовать вызовы select() или poll() для получения информации, когда трансферам в стеке возможно нужно внимание.

CURLMcode curl_multi_fdset (CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd)

Большинство приложений использует curl_multi_fdset() для получения файловых дескрипторов из мульти-дескриптора libcurl. Затем приложение ждет каких-либо действий используя select(), и когда хотя бы один дескриптор готов, вызывает curl_multi_perform().

5 мин (на форумах вычитал). Еще одно предостережение: обязательно вызвайте curl_multi_fdset() сразу перед вызовом select(), т.к. текущий набор файловых дескрипторов мог измениться с прошлого вызова функций libcurl.

Лирическое отступление. Вопрос с правильным таймаутом оказался практически не разрешимым 🙁 На том же сайте нашел следующее: «когда выполняете select(), вам следует использовать curl_multi_timeout() для выяснения нужного времени ожидания действия. Вызовите curl_multi_perform() даже если нет активности на указанных файловых дескрипторах по истечении таймаута«. Так вот, функция curl_multi_timeout() выясняет значение либо через вызов curl_multi_socket_action() задав второму параметру значение CURL_SOCKET_TIMEOUT, либо через вызов той же curl_multi_perform()! В пером случае без бутылки не разобраться, но все-таки можно докопаться до истины. Но каким образом вызов curl_multi_perform установит таймаут для select, я не понял 🙁 Более того, покопавшись в исходниках PHP, я выяснил, что там таймаут расчитывается математикой от переданного параметра $timeout (double), затем задается в select(). Т.е. ни каких дополнительных вызовов curl_multi_* в PHP не делается.

Реализация на PHP

Рассматриваем PHP 5.3.8, cURL 7.20.0. Ниже приведен список некоторых функций cURL в PHP и их связь с «настоящими» функциями библиотеки. В исходниках PHP помимо вызова «настоящих» функций libcurl есть еще обращения к zend_* и много того, что мне непостижимо. Но главную суть я уловил и подтвердил свои догадки. Что обо всем этом написано в справке PHP, можно узнать здесь.

cURL на PHPИнкапсуляция (код С)
int curl_multi_exec (resource $mh,
int &$still_running)
curl_multi_perform (mh->multi, &still_running)
RETURN_LONG
В текущей версии PHP не используется curl_multi_socket()
int curl_multi_select ( resource $mh
[, float $timeout = 1.0 ] )
_make_timeval_struct (&to, timeout); расчет таймаута (внутренняя функция)
curl_multi_fdset (mh->multi, &readfds, &writefds, &exceptfds, &maxfd);
select (maxfd+1, &readfds, &writefds, &exceptfds, &to);
RETURN_LONG
void curl_multi_close (resource $mh)curl_multi_cleanup (mh->multi);
bool curl_setopt (resource $ch,
int $option, mixed $value)
curl_easy_setopt (ch->cp, option, value);
return 1;
void curl_close (resource $ch)curl_easy_cleanup (ch->cp);
Практика

Теперь зная, откуда «ноги растут», можно создать правильный код на PHP. Приведенный мной пример не претендует на единственно возможный, это один из многих рабочих вариантов использования мульти-cURL. Весь код здесь не привожу, см. в этом архиве. Код в архиве немного другой, больше служебной информации выдает в браузер.

В строке 25 стоит пауза. Это не «по учебнику», я придумал такую хитрость, чтобы дожидаться окончания одновременных запросов. Они обычно отстают друг от друга где-то на 0.5 секунды и такая пауза уменьшает количество итераций основного цикла. На практике она может оказаться бесполезной, можно смело ее убрать.

Функцию curl_multi_info_read() засунул в цикл (строка 31), т.к. есть вероятность, что несколько дескрипторов закончатся одновременно (особенно с паузой выше), тогда их нужно обработать в одном шаге основного цикла.

В примере я ставил таймаут select-а (строка 24) на несколько секунд только потому, что ответ предполагается ждать долго. На практике можно явно не указывать таймаут, тогда он будет равен 1сек. Все зависит от области применения. Одно могу сказать точно, curl_multi_select() нужен в коде. Он заметно разгружает скрипт и ресурсы сервера.

Проблема этого примера в том, что приходится «ловить лису за хвост». Сервер хостера работает в разы шустрее, чем моя машина. Поэтому чтобы понять смысл кода, придется экспериментировать с таймаутом select-а, GET-запросом и паузой после curl_multi_select(). Если вам все это до лампочки, то на практике можно просто использовать код, как есть.

[UPD] Прошло время, и сам не понял ход своих мыслей =) Строки 19-20. Сначала перечитываем «Очень важный момент», там два абзаца. Теперь поясняю: пауза нужна, чтобы пропустить переходное состояние libcurl между статусами *_PERFORM и *_OK, когда библиотека не вернет ни одного дескриптора и значит нечего разбирать в последующем цикле. Вот в чем была идея 🙂 Кроме того, без этой паузы получим зависание на первом шаге цикла в строке 24, пока таймаут, заданный в *_select() не кончится. Все это наблюдалось на медленной машине. Теперь ОЗУ прибавилось, не получается подтвердить слова.

Однако это не все странности 🙁 Специально погонял этот код с отладочными var_dump(). Смотрите строки 17-20 (первый цикл и повторный вызов curl_multi_exec()). Знаете, что будет возвращать curl_multi_exec() на последних шагах цикла? CURLM_CALL_MULTI_PERFORM, затем FALSE (а не CURLM_OK, как ожидалось), что приводит к выходу из цикла. Далее вызов в 20-й строке вернет CURLM_OK, на быстрой машине даже пауза не нужна.

Есть более короткий пример использования мульти-cURL, основанный на первой части статьи. Забьем на всякие ожидания и т.п. и получим следующее (источник + мои доработки):

Источник

curl_multi_exec

curl_multi_exec — Запускает подсоединения текущего дескриптора cURL

Описание

Обрабатывает каждый дескриптор в стеке. Этот метод может быть вызван вне зависимости от необходимости дескриптора читать или записывать данные.

Список параметров

Ссылка на флаг, указывающий, идут ли ещё какие-либо действия.

Возвращаемые значения

Список изменений

ВерсияОписание
8.0.0multi_handle теперь ожидает экземпляр; раньше, ожидался ресурс ( resource ).

Примеры

Пример #1 Пример использования curl_multi_exec()

Этот пример создаст два дескриптора cURL, добавит их в набор дескрипторов, а затем запустит их асинхронно.

// создаём оба ресурса cURL
$ch1 = curl_init ();
$ch2 = curl_init ();

//создаём набор дескрипторов cURL
$mh = curl_multi_init ();

Смотрите также

User Contributed Notes 16 notes

Solve CPU 100% usage, a more simple and right way:

Probably you also want to be able to download the HTML content into buffers/variables, for parsing the HTML or for other processing in your program.

The example code on this page only outputs everything on the screen, without giving you the possibility to save the downloaded pages in string variables. Because downloading multiple pages is what I wanted to do (not a big surprise, huh? that’s the reason for using multi-page parallel Curl) I was initially baffled, because this page doesn’t give pointers to a guide how to do that.

Fortunately, there’s a way to download content with parallel Curl requests (just like you would do for a single download with the regular curl_exec). You need to use: http://php.net/manual/en/function.curl-multi-getcontent.php

The function curl_multi_getcontent should definitely be mentioned in the «See Also» section of curl_multi_exec. Probably most people who find their way to the docs page of curl_multi_exec, actually want to download the multiple HTML pages (or other content from the multiple parallel Curl connections) into buffers, one page per one buffer.

/!\ ATTENTION
/!\ Several of the non-downvoted notes on this page are using outdated info.

The CURLM_CALL_MULTI_PERFORM return code has been defunct since circa 2012, at least seven years ago.

> CURLM_CALL_MULTI_PERFORM is deprecated and will never be returned, as documented.

> During the first decade or so of libcurl’s multi interface, I never saw a single proper use of that feature. I did however see numerous mistakes and misunderstandings. That made me decide that the feature wasn’t important or good enough, so since 7.20.0 CURLM_CALL_MULTI_PERFORM is no more.

Discovered all of this thanks to https://stackoverflow.com/q/19490837/3229684, which suggested the following replacement while loop:

«When you’ve added the handles you have for the moment (you can still add new ones at any time), you start the transfers by call curl_multi_perform(3).

curl_multi_perform(3) is asynchronous. It will only execute as little as possible and then return back control to your program. It is designed to never block. If it returns CURLM_CALL_MULTI_PERFORM you better call it again soon, as that is a signal that it still has local data to send or remote data to receive.»

So it seems the loop in sample script should look this way:

select error

» ;
break;
>
> while ( true );
?>

This worked fine (PHP 5.2.5 @ FBSD 6.2) without running non-blocked loop and wasting CPU time.

However this seems to be the only use of curl_multi_select, coz there’s no simple way to bind it with other PHP wrappers for select syscall.

Just for people struggling to get this to work, here is my approach.
No infinite loops, no CPU 100%, speed can be tweaked.

Источник

curl_multi_init

curl_multi_init — Создаёт набор cURL-дескрипторов

Описание

Позволяет асинхронную обработку множества cURL-дескрипторов.

Список параметров

У этой функции нет параметров.

Возвращаемые значения

Возвращает набор cURL-дескрипторов в случае успешного выполнения или false в случае возникновения ошибки.

Список изменений

ВерсияОписание
8.0.0В случае успешного выполнения возвращает экземпляр CurlMultiHandle ; раньше, возвращался ресурс ( resource ).

Примеры

Пример #1 Пример использования curl_multi_init()

Этот пример создаст два дескриптора cURL, добавит их в набор дескрипторов, а затем запустит их асинхронно.

// создаём оба ресурса cURL
$ch1 = curl_init ();
$ch2 = curl_init ();

//создаём набор дескрипторов cURL
$mh = curl_multi_init ();

Смотрите также

User Contributed Notes 5 notes

// build the individual requests, but do not execute them
$ch_1 = curl_init(‘http://webservice.one.com/’);
$ch_2 = curl_init(‘http://webservice.two.com/’);
curl_setopt($ch_1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch_2, CURLOPT_RETURNTRANSFER, true);

Therefore, in order to work correctly on Windows for PHP 5.3.10+, the second loop in the example code should look something like the following:

One of the URLs used in the first example on this page (lxr.php.net) now gives a proxy error.

If you’re using this first script example, replace with a different URL.

If you fire off 10 curl requests in parallel you don’t have to wait for all of them to be finished before accessing one which is already finished.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *