<rmcreative>

RSS

rolling-curl

13 мая 2010

rolling-curl — PHP-класс для параллельного выполнения большого количества асинхронных HTTP-запросов при помощи curl, написанная Josh Fraser и поддерживаемая на данный момент мной.

Правильно чистит память, не простаивает зря, выполняя одновременно заданное число запросов. Обрабатывает каждый ответ сразу после выполнения запроса.

Пример:

// URL, которые будем обрабатывать
$urls = array(
  "http://www.google.com",
  "http://www.facebook.com",
  "http://www.yahoo.com",
);
 
// функция для обработки ответа
function request_callback($response, $info) {
  // получаем title страницы
  if (preg_match("~<title>(.*?)</title>~i", $response, $out)) {
    $title = $out[1];
  }
  echo "<b>$title</b><br />";
  print_r($info);
  echo "<hr>";
}
 
$rc = new RollingCurl("request_callback");
// одновременно позволим не более 20 запросов
$rc->window_size = 20;
foreach ($urls as $url) {
    // добавляем запросы в очередь
    $request = new RollingCurlRequest($url);
    $rc->add($request);
}
// запускаем
$rc->execute();

Пользуемся

Комментарии RSS

  1. №2528
    mktums
    mktums 13 мая 2010 г., 14:41:17

    Спасибо :) Возьму на заметку :)

  2. №2529
    Ewg
    Ewg 13 мая 2010 г., 14:43:13

    Как скачать?

  3. №2530
    Максим
    Максим 13 мая 2010 г., 17:38:24

    Удобная вешь+ еще и на curl. А по асинхроным серверам на php ничего не завалялось случайно ?

  4. №2531
    Andron
    Andron 13 мая 2010 г., 17:56:25

    Спасибо, возможно пригодится :)

  5. №2532
    Sam
    Sam 13 мая 2010 г., 18:53:30

    Ewg

    Можно забрать из SVN Google Code.

  6. №2533
    Sam
    Sam 13 мая 2010 г., 19:10:35

    Максим

    У меня лично по асинхронным нет, но есть http://github.com/kakserpom/phpdaemon.

  7. №2534
    Сергей
    Сергей 13 мая 2010 г., 23:16:25

    Зачем

    $request = new Request($url);

    если можно

    $rc->request($url);

    Вообще довольно странная библиотека...

  8. №2535
    Сергей
    Сергей 14 мая 2010 г., 0:07:54

    И правда что-то не так

    Почему то при запуске example.php функции curl_multi_exec и curl_multi_info_read у меня запускались по 94000 с лишним раз.

    Что то там явно не так. Будто цикл какой-то крутится без таймаутов.

    Там вот эта последовательность

      160,3418     527568         -> curl_multi_exec() /home/seriy/rolling-curl-read-only/RollingCurl.php:255
      160,3418     527568         -> curl_multi_info_read() /home/seriy/rolling-curl-read-only/RollingCurl.php:259
      160,3419     527568         -> curl_multi_exec() /home/seriy/rolling-curl-read-only/RollingCurl.php:255
      160,3419     527568         -> curl_multi_info_read() /home/seriy/rolling-curl-read-only/RollingCurl.php:259
      160,3419     527568         -> curl_multi_exec() /home/seriy/rolling-curl-read-only/RollingCurl.php:255
      160,3420     527568         -> curl_multi_info_read() /home/seriy/rolling-curl-read-only/RollingCurl.php:259
      160,3420     527568         -> curl_multi_exec() /home/seriy/rolling-curl-read-only/RollingCurl.php:255
    

    Очень много раз повторяется (по 20-100 тыс раз)

    Я бы не советовал этот класс использовать..

  9. №2536
    Сергей
    Сергей 14 мая 2010 г., 1:01:31

    Да и ничуть оно ни асинхронное/неблокирующее.

    Если в example.php в самом конце после $rc->execute(); вставить например print "Hello world"; то если все действительно асинхронно, то сперва напечатается "Hello world" а потом уже все остальное а тут сперва отрабатывает полностью $rc->execute(); (т.е. оно заблокировало все операции) и только потом print

    Так что асинхронностью тут и не пахнет. Многопоточность разве что...

  10. №2537
    Psih
    Psih 14 мая 2010 г., 1:40:05

    http://github.com/kakserpom/phpdaemon - ыыы, Данко! Я этого психа знаю лично - он на демонах на PHP собаку съел, целую стаю, стаж у него с этим лет 6-7 точно. Так что это реально хорошее решение будет, от опытного спеца :)

  11. №2538
    Sam
    Sam 14 мая 2010 г., 3:02:47

    Сергей

    Слово «асинхронные» тут относится именно к запросам и их обработке, а не к библиотеке в целом.

    Советую почитать про curl_multi_exec и curl_multi_info_read.

  12. №2543
    Сергей
    Сергей 14 мая 2010 г., 22:44:17

    Sam

    Ок, насчет асинхронности в целом согласен, хотя к библиотеке в целом это не очень подходит.

    Но то, что там в какой-то момент начинает крутиться цикл по 20-90 тыс итераций это явно проблема.

  13. №2545
    Sam
    Sam 15 мая 2010 г., 0:40:20

    Сергей

    Для curl_multi это совершенно нормально и указано в описаниях к методам.

  14. №2547
    Константин
    Константин 15 мая 2010 г., 1:25:26

    Спасибо, но не пойму вот чего – я хочу сделать 5 миллионов запросов в 5 потоков. Все 5 миллионов я в $requests, понятное дело, не засуну – память не резиновая. Хотелось бы добавлять запросы в коллбэке по мере их выполнения. Однако приходится дождаться выполнения всех 5 запросов в первом потоке, и затем запускать следующий поток.

    Либо делать множество более коротких очередей – скажем, 5 тысяч раз по 1000 урлов. Что уже лучше, но усложняет логику. Есть выход?

  15. №2553
    Sam
    Sam 16 мая 2010 г., 13:49:21

    В самой библиотеке выхода, думаю, нет. Занесите в тикет, надо будет подумать.

  16. №2609
    alexf2000
    alexf2000 02 июня 2010 г., 17:01:48

    Может ли один из урлов завесить выполнение всех запросов? Если скажем в списке урлов не яхо, а вася.пупкин.цюрюпинск.херсон.уа и на этом сервере днс сначала 3 минуты думает, а потом не отвечает и до хттп соотсветственно дело даже не доходит или доходит, но сервер выдаёт мегабайтный файл по 1 байту в секунду? :)

  17. №2613
    Sam
    Sam 03 июня 2010 г., 12:27:17

    Нет, не может.

  18. №2661
    Jei
    Jei 29 июня 2010 г., 17:16:23

    Возникла пара вопросов:

    1) при работе скрипта (параллельно логинишься на два сайта; после этого параллельно забирается контент одной нужной страницы с каждого сайта) процес апача грузит проц на 50%. Это нормально или проблема конкретно у меня? ОС - виндовсХР, пакет - денвер+расширения, $rc->execute() - чтобы залогиниться и куки получить, второй $rc->execute()- чтобы получить контент.

    2) Хотелось бы, чтобы после того как залогинишься на сайте, сразу начинал работать другой запрос для получения контента страницы, и после получения парситься, независимо от запросов к другим сайтам. Такое возможно с данным функционалом?

  19. №2935
    Turbonist
    Turbonist 27 авг. 2010 г., 0:40:17

    Привет!

    Спасибо за либу. Очень пригодилось.

    Есть вопрос.

    С учетом того, что:

    запросов допустим 1000, а

    $rc->window_size = 20;
    for($i=0;$i<1000;$i++){
    $request = new Request($url . $i);
    $request->options = array(CURLOPT_TIMEOUT => 3);
    $rc->add($request);
    }

    , то либа должна отрабатывать каждые 3 сек минимум 20 запросов. Независимо от скорости получения контента (ибо CURLOPT_TIMEOUT => 3). Значит на всю операцию должно уйти не более 150 сек. Добавим 150 сек для прочих операций. Итого 5 мин. Однако, на деле может и час и два работать. Такое впечатление, что после первых 20 потоков переходит в режим однопоточности + не работает CURLOPT_TIMEOUT.

    С чем может быть связано?

  20. №2937
    Turbonist
    Turbonist 27 авг. 2010 г., 2:34:21

    в догонку к предыдущему посту

    [total_time] => 35.851
        [namelookup_time] => 0.06
        [connect_time] => 18.847
        [pretransfer_time] => 18.847
        [starttransfer_time] => 35.781

    как-то эти цифры не совместимы с

    $rc->options = array(CURLOPT_TIMEOUT => 5,CURLOPT_CONNECTTIMEOUT => 5);

    версия curl libcurl/7.19.6

    может дело в самой либе curl?

  21. №2910
    Sam
    Sam 27 авг. 2010 г., 13:02:45

    Jei

    1) Не уверен. У меня не наблюдается.

    2) Да, в коллбэках писать надо.

    Turbonist

    Скорее всего в curl. Параметры либе вроде передаются.

  22. №2943
    Turbonist
    Turbonist 27 авг. 2010 г., 16:58:37

    Скорее всего в curl. Параметры либе вроде передаются.

    на кодах в вопросах, 12 вопрос, чел тоже испытывает проблемы с тем же самым.

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

  23. №3272
    Андрей
    Андрей 22 окт. 2010 г., 22:23:48

    Пробую использовать в библиотеке список (массив) проксей - если урл не сработал с текущей проксей, необходимо заново поставить этот урл в очередь "скачиваний" со следующей по порядку проксей.

    Создавать еще один новый объект RollingCurl и запускать еще один execute не хочется, т.к., вероятно, одновременных процессов уже будет не window_size, а больше.

    Если делать $rc->add($request), то как лучше его перезапустить с учетом того, что часть запросов уже была отработана и второй раз делать их же нелогично? Сразу же заново запускать $rc->execute()? Или $rc->add($request) добавит урл с новой проксей в существующую очередь? Может ли получится так, что execute полностью выполнится до того, как будет добавлена урл с новой проксей и как это избежать?

    P.S. Проверку, сработала ли прокси, пока планирую делать в функции request_callback примерно таким образом: if ($info['http_code']==501|502|503|504|505) {...}

  24. №3328
    Андрей
    Андрей 01 нояб. 2010 г., 9:33:55

    Еще один вопрос...

    Подскажите, пожалуйста, как настроить паузу между отправкой запросов на удаленный сервер, чтобы распределить его нагрузку во времени, т.е. чтобы между запусками отдельных curl было не менее nn секунд.

  25. №3331
    Sam
    Sam 01 нояб. 2010 г., 12:35:47

    С очередями пока есть определённые проблемы. И вроде как есть решение. Надо будет его обкатать.

    Пауз между запросами пока не предусмотрено. Нагрузку можно уменьшить выставив размер окна в единичку.

  26. №3338
    Андрей
    Андрей 02 нояб. 2010 г., 12:57:17

    Судя по обсуждениям в форумам curl_multi_exec, возможно, это можно сделать добавлением usleep в RollingCurl.php примерно так:

        do {
            while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) usleep(4000000); //делаем паузы 4 сек между запросами
            if ($execrun != CURLM_OK)
                break;
            // a request was just completed -- find out which one
            while ($done = curl_multi_info_read($master)) {
    
    

    Если гипотеза подтвердиться, пожалуйста, проапдейте RollingCurl.php.

  27. №3339
    Sam
    Sam 02 нояб. 2010 г., 13:27:05

    Есть подозрение, что usleep() затормозит все потоки.

  28. №3355
    Stargazer
    Stargazer 09 нояб. 2010 г., 0:13:57

    Sam, а можно ли сделать так, чтобы каждый запрос через свою проксю ходил?

  29. №3356
    Sam
    Sam 09 нояб. 2010 г., 12:53:22

    Можно. См. RollingCurlGroup.

  30. №3794
    Владимир
    Владимир 26 янв. 2011 г., 0:36:26

    Здравствуйте! У меня приведенный пример не работает, выводится ошибка Fatal error: Class 'Request' not found in P:\home\localhost\www\multicurl\testRollingCurl.php on line 114 а если я вместо строки $request = new Request($url); пишу строку $rc->request($url); то скрипт работает неправильно. у меня в массиве порядка 90 адресов url, в настройках я указываю $rc->window_size = 10; скрипт выводит всего 6 результатов. Подскажите пожалуйста, где искать ошибку, т.к. я только учусь.

  31. №3795
    Sam
    Sam 26 янв. 2011 г., 4:15:44

    Request в новых версиях переименован в RollingCurlRequest.

  32. №3969
    Sasha
    Sasha 26 февр. 2011 г., 12:36:21

    Не могли бы вы объяснить новичку, как без смены прокси и юзерагента, скрипт является неблокируемым?

  33. №4057
    Raimond
    Raimond 10 марта 2011 г., 0:53:14

    замерял время выполнения 2 скриптов 1: при помощи RollingCurl запросить 10 страниц с удаленного сервера (в 10 потоков) 2: то же самое, но при помощи обычного curl в 1 поток.

    результат - прирост скорости ~20% в пользу RollingCurl

  34. №6918
    Макс
    Макс 28 окт. 2012 г., 2:23:51

    2Sasha

    Для подмены useragent и proxy подойдёт AngryCurl - улучшенная версия Rolling Curl на php.

  35. №7047
    Shua
    Shua 15 нояб. 2012 г., 17:20:17

    Что-то не хочет у меня пример работать. В $response - NULL :( Подскажите плз, в чем проблема

  36. №8870
    seyfer
    seyfer 13 марта 2014 г., 13:00:55

    Каким образом можно присоединиться к поддержке?

  37. №8871
    seyfer
    seyfer 13 марта 2014 г., 13:07:56

    Снимаю свой вопрос. Вот более доработанная версия с композером и psr0

    github.com/chuyskywalker/rolling-curl

  38. №8874
    Sam
    Sam 13 марта 2014 г., 15:38:01

    Делали бы уж сразу PSR-4.

  39. №10429
    Сергей
    Сергей 01 апр. 2016 г., 10:48:14

    Здравствуйте! Скрипт RollingCurl еще поддерживаете?

  40. №10430
    Sam
    Sam 01 апр. 2016 г., 16:03:50

    Нет.

  41. №10431
    Сергей
    Сергей 01 апр. 2016 г., 16:11:52

    Можете порекомендовать что-то подобное?

  42. №10432
    Sam
    Sam 01 апр. 2016 г., 18:43:59

    Нет. Я давно не занимался этой темой.

  43. №10855
    Paul
    Paul 17 февр. 2017 г., 19:35:30

    $rollingCurl = new RollingCurl();

        foreach (self::getCameraListByCity() as $pageWithCamListInCity) {
            $rollingCurl->get(self::HOST.$pageWithCamListInCity);
        }
        $results = [];
    
        $rollingCurl
            ->setCallback(function (Request $request, RollingCurl $rollingCurl) use (&$results) {
                $HTML = new \DOMDocument();
    
                @$HTML->loadHTML($request->getResponseText());
                $DOMXPath = new \DOMXPath($HTML);
    
                $pagination = $HTML->getElementsByTagName('ul')->item(5)->nodeValue;
                $countOfPagesForCurrentCity = explode(",", $pagination); // returns count of pages for every city
    
                $counter = 0;
                $k = 0;
    
                if ($k < $countOfPagesForCurrentCity[1]) {
                    $k++;
                    $rollingCurl->get($request->getUrl() . '?page=' . $k);
                    echo "Fetch for (" . $request->getUrl() . '?page=' . $k . ")" . " [Page: " . $k . " / " . trim($countOfPagesForCurrentCity[1]) . "] " . PHP_EOL;
    
    

    foreach ($DOMXPath->query('//a[contains(@class, "thumbnail-item__wrap")]/@href') as $linkToPageWithCamera) { if (isset($linkToPageWithCamera->nodeValue)) { $results[] = $linkToPageWithCamera->nodeValue; $counter++; } else { $this->addError("[ " . $request->getResponseErrno() . "]" . $request->getResponseError()); return; } } } }) ->setSimultaneousLimit(1000) ->execute(); echo "All results = " . print_r($results) . PHP_EOL;

    Вопрос (поучаствовать могут все желающие) - Почему вызов коллбэка будет бесконечным (постоянно будет слать урлы вида

    www.insecam.org/en/bycity/Aigaleo/?page=1) www.insecam.org/en/bycity/Aigaleo/?page=1?page=1 www.insecam.org/en/bycity/Aigaleo/?page=1?page=1?page=1

    Хотя в коллбэке есть условие if ($k < $countOfPagesForCurrentCity[1] считаем количество ссылок в пагинаторе и в каллбэке вызываем подстановку соотвествующего урла для каждой базовой ссылки

    Что я делаю не так, почему цикл бескнечный?

  44. №10856
    Paul
    Paul 17 февр. 2017 г., 19:51:03

    Нет, вот, Саш, ссылка на pastebin/ Так по понятнее будет.

    pastebin.com/SVRzyv2D

    Вопрос прежний, почему уходит в бесконечность цикл?

    Есть страница, которую надо распарсить у нее есть пагинация

    На первых итерациях парсер работает верно, генерирует страниццы например, someurl.ru/?page=1 someurl.ru/?page=2 someurl.ru/?page=3

    То есть счетчик работает правильно.

    Но когда нужно переключиться на другой урл то он начинает вместо перехода по условию (пока количество элементов в пагинации не меньше $i++) генерировать урлы вида

    someurl.ru/?page=1?page=1 someurl.ru/?page=2?page=1 someurl.ru/?page=3?page=1

    Что Я делаю не так? Или метод

    $rollingCurl->get($request->getUrl() . '?page=' . $k); багует?

    Спасибо.

  45. №10857
    Сергей
    Сергей 19 февр. 2017 г., 11:04:47

    Были у меня похожие проблемы с этим скриптом. Я его уже не использую. Нашел другую версией , более удобную и понятную для меня в плане механизма работы. github.com/2naive/AngryCurl

  46. №10862
    Sam
    Sam 27 февр. 2017 г., 23:37:19

    Paul, вы в конец параметр дописываете. А надо оригинальный URL распарсить и параметр страницы заменить.

  1. Почта опубликована не будет.

  2. Можно использовать синтаксис Markdown или HTML.

  3. Введите ответ в поле. Щёлкните, чтобы получить другую задачу.