Многопоточное скачивание в 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-запросами, но это уж если получу инвайт. И так понаписал много, многие ли осилили? 😉
curl_multi_exec
curl_multi_exec — Запускает подсоединения текущего дескриптора cURL
Описание
Обрабатывает каждый дескриптор в стеке. Этот метод может быть вызван вне зависимости от необходимости дескриптора читать или записывать данные.
Список параметров
Ссылка на флаг, указывающий, идут ли ещё какие-либо действия.
Возвращаемые значения
Список изменений
| Версия | Описание |
|---|---|
| 8.0.0 | multi_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.
Клиентская библиотека работы с URL
User Contributed Notes 27 notes
I wrote the following to see if a submitted URL has a valid http response code and also if it responds quickly.
Use the code like this:
Hey I modified script for php 5. Also I add support server auth. and fixed some little bugs on the script.
[EDIT BY danbrown AT php DOT net: Original was written by (unlcuky13 AT gmail DOT com) on 19-APR-09. The following note was included:
Below is the my way of using through PHP 5 objecte oriented encapsulation to make thing easier.]
I needed to use cURL in a php script to download data using not only SSL for the server authentication but also for client authentication.
On a default install of Fedora, setting up the proper cURL parameters, I would get an error:
$ php curl.php
Peer certificate cannot be authenticated with known CA certificates
The data on http://curl.haxx.se/docs/sslcerts.html was most useful. Indeed, toward to bottom it tells you to add a missing link inside /etc/pki/nssdb to use the ca-bundle.crt file. You do it so:
Now you can do client authentication, provided you have your certificate handy with:
In this example: http://php.net/manual/en/book.curl.php#102885 by «frank at interactinet dot com»
There’s a small bug in
?>
The code will immediately leave the function at the `return`, and pcntl_wait() will NEVER be executed, under any circumstances.
I can’t see any other issues with this function however.
Sharing is caring, handles included.
$user_agent = ‘Mozilla HotFox 1.0’ ;
CURL failed with PHP5.3 and Apache2.2.X on my Windows 7 machine.
It turns out that it’s not enough to copy the two dll’s mentioned (libeay32 and sslea32) from the php folder into your system32 folder. You HAVE TO UNBLOCK THESE TWO FILES.
Right click the file, select unblock, for each one. Then restart Apache.
Another very handy security feature added into Windows.
Here you have a function that I use to get the content of a URL using cURL:
You can use this class for fast entry
[EDIT BY danbrown AT php DOT net: Includes a bugfix provided by (manuel AT rankone DOT ch) on 24-NOV-09 to properly reference cURL initialization.]
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.
curl_multi_select
curl_multi_select — Ждёт активности на любом curl_multi соединении
Описание
Блокирует выполнение скрипта, пока какое-либо из соединений curl_multi не станет активным.
Список параметров
Время в секундах для ожидания ответа.
Возвращаемые значения
Список изменений
| Версия | Описание |
|---|---|
| 8.0.0 | multi_handle теперь ожидает экземпляр; раньше, ожидался ресурс ( resource ). |
Смотрите также
User Contributed Notes 8 notes
In my application a very small interval like usleep(1) worked. For example:
When doing select(), you should use curl_multi_timeout to figure out how long to wait for action.»
Untill PHP implements curl_multi_timeout() we have to play with our application and determine the «wait».
For that reason, curl_multi_exec() should always be wrapped:
This function blocks the calling process until there is activity on any of the connections opened by the curl_multi interface, or until the timeout period has expired.
In other words, it waits for data to be received in the opened connections.
Internally it fetches socket pointers with «curl_multi_fdset()» and runs the «select()» C function.
It returns in 3 cases:
1. Activity is detected on any socket;
2. Timeout has ended (second parameter);
3. Process received any signal (#man kill).
Thanks for attention, hope this helps.
Since the docs are still lacking, here’s an example of how to use the function. The following code will keep checking all active threads until one of them returns the HTTP 200 Ok status code, or simply end. On success, it will return the URL that worked.
As stated by someone else, it also doesn’t seem to return the overall count of threads in the handle, but only that of the currently active ones.