Скрипт многопоточного скачивания страниц при помощи multi CURL

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

Вот, например, реализация многопотокового скачивания страниц при помощи мультикурла:

class MCurl
{

var $timeout = 20; // максимальное время загрузки страницы в секундах
var $threads = 10; // количество потоков 

var $all_useragents = array(
"Opera/9.23 (Windows NT 5.1; U; ru)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.4;MEGAUPLOAD 1.0",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; Alexa Toolbar; MEGAUPLOAD 2.0; rv:1.8.1.7) Gecko/20070914 Firefox/2.0.0.7;MEGAUPLOAD 1.0",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; Maxthon)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; Maxthon)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; Maxthon)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; Maxthon; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; Media Center PC 5.0; InfoPath.1)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; Maxthon)",
"Opera/9.10 (Windows NT 5.1; U; ru)",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2.1; aggregator:Tailrank; http://tailrank.com/robot) Gecko/20021130",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; Maxthon)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8",
"Opera/9.22 (Windows NT 6.0; U; ru)",
"Opera/9.22 (Windows NT 6.0; U; ru)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.1.8) Gecko/20071008 Firefox/2.0.0.8",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MRSPUTNIK 1, 8, 0, 17 HW; MRA 4.10 (build 01952); .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9"
);


function multiget($urls, &$result)
{
	$threads = $this->threads;
	$useragent = $this->all_useragents[array_rand($this->all_useragents)];
		
	$i = 0;
	for($i=0;$i<count($urls);$i=$i+$threads)
	{
		$urls_pack[] = array_slice($urls, $i, $threads);
	}
	foreach($urls_pack as $pack)
	{
		$mh = curl_multi_init(); unset($conn);
		foreach ($pack as $i => $url) 
		{
	    	$conn[$i]=curl_init(trim($url));
			curl_setopt($conn[$i],CURLOPT_RETURNTRANSFER, 1);
			curl_setopt($conn[$i],CURLOPT_TIMEOUT, $this->timeout);
			curl_setopt($conn[$i],CURLOPT_USERAGENT, $useragent);
			curl_multi_add_handle ($mh,$conn[$i]);
		}
		do { $n=curl_multi_exec($mh,$active); usleep(100); } while ($active); 
		foreach ($pack as $i => $url) 
		{
      		$result[]=curl_multi_getcontent($conn[$i]);
      		curl_close($conn[$i]);
		}
curl_multi_close($mh);
	}
	
}
}

Собственно, самое интересное здесь – это функция multiget , которая принимает на вход масссив урлов, которые надо скачать и возвращает массив с контентом страниц (для экономии памяти, массив передается как ссылка в аргументе функции).

Формат использования:


$urls = array("http://site.com/page1.php","http://site.com/page2.php","http://site.com/page3.php");
$mcurl = new MCurl;
$mcurl->threads = 20;
$mcurl->timeout = 5; // нам нужна максимально быстрая скачка, пусть теряются медленные страницы
unset($results); // очищаем массив $results (если он использовался раньше где-то в коде)
$mcurl->multiget($urls, $results); 
// в массиве $results - контент страниц
 

Причем в $results[0] будет контент строго от $urls[0] и т.д.

MultiCURL – это не лучший способ многопоточного скачивания страниц, но зато он самый простой. Почему не лучший ? Потому что скачивание происходит пачками по threads урлов и время обработки каждой пачки равняется времени скачивания самого тормозного урла. Правильнее сделать на сокетах, но с ними слишком много мороки. В повседневной жизни вполне хватает мультикурла.


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

Comments

  • Gyrus
    December 21st, 2008 | 5:25 pm

    “Потому что скачивание происходит пачками по threads урлов и время обработки каждой пачки равняется времени скачивания самого тормозного урла.”

    Это ограничение обходится достаточно просто на самом деле. :) И нет необходимости в ожидании окончания всех потоков. Например, достаточно сделать атрибут онЛоад, и передавать его вместе с урлом.. ну и в обработчике смотреть – если какой-то из потоков загрузился – то данные можно обрабатывать, и добавлять новые потоки.. Эдакий нон-стоп получается. :)

  • Скрипт многопоточного скачивания страниц при помощи multi CURL : Блог Молчуна
    December 22nd, 2008 | 1:10 am

    [...] Начну, наверное, выкладывать куски полезного кода, вдруг кому-нибудь пригодятся. Вот, например, реализация многопотокового скачивания страниц при помощи мультикурла. Собственно, самое интересное здесь – это функция multiget , которая принимает на вход масссив урлов, которые надо скачать и возвращает массив с контентом страниц. Для экономии памяти, массив передается как ссылка в аргументе функции. Дальше [...]

  • medar
    December 22nd, 2008 | 8:54 am

    Gyrus, если грамотно делать то да.
    Но мультикурл, насколько я понимаю, не предоставляет такого функционала. Можно запустить пачку на скачку функцией curl_multi_exec , но смотреть, что там внутри происходит и подбрасывать новых урлов при необходимости – нельзя.

  • ikpwl
    December 22nd, 2008 | 5:49 pm

    $mcurl->multiget($urls, $results);

    если я правильно понимаю то этот синтаксис не отработает

    только если так

    $mcurl->multiget($urls, &$results);

  • medar
    December 22nd, 2008 | 6:20 pm

    Нет, работает $mcurl->multiget($urls, $results); .
    Как у preg_match_all , результат оказывается в одном из аргументов.

    Про “передачу ссылки” я сам сомневался когда писал. Но как-то же резалты попадают в аргумент. Кроме как передачей ссылки на массив в функцию – я другого способа придумать не могу. И да, в этом случае нужен амперсанд. Но работает без него.

    Хз что там у php внутри на этот счет..

  • user
    December 22nd, 2008 | 8:40 pm

    а прокси можно использовать ? так пойдёт ? curl_setopt($curl, CURLOPT_PROXY, “121.150.66.51:8080″);

  • medar
    December 22nd, 2008 | 9:00 pm

    user, да, можно юзать любые опции курла

  • Gyrus
    December 28th, 2008 | 6:43 pm

    Medar…

    … но смотреть, что там внутри происходит …
    …и подбрасывать новых урлов при необходимости – нельзя.”
    Ну, значит я реализовал невозможное… :) Мало того, я смотрю что там внутри происходит (есть параметры например CURLOPT_WRITEFUNCTION, CURLOPT_HEADERFUNCTION), и могу в любой момент прерывать загрузку урла.. например, если нужен текст, а приходит картинка, либо, обрывать загрузку на нужном количестве байт.. либо, делать тот же метарефреш, не используя FOLLOWLOCATION.. :)

    И таки далее, кто мешает сделать например так:
    $n=curl_multi_exec($mh,$active);
    usleep(100);
    if (!$active){
    а уже тут дергаем онлоад, который в свою очередь, может добавлять новые запросы, новые урлы и задания, которые будут браться из очереди и т.д? тоесть, подготавливать курл, чтобы можно было дергать курл_мульти_экзек? :)
    }

    Вобщем, у меня это реализовано, именно на мультикурле. И подбрасываются новые урлы, и обрабатываются по мере загрузки, и добавлена еще куча всяческой логики – типа чайлд реквестов и прочее.. для случаев, когда надо гетом дернуть форму, и в этом же контексте (браузер, куки и прочее) – заполнить ее, и отправить постом данные..
    Единственная оговорка. на один mh – приходится один ch. :)

  • Игорь
    January 13th, 2009 | 5:31 pm

    >Нет, работает $mcurl->multiget($urls, $results);

    я ошибся, эта тема про & нужна в объялении функции а не в использовании

    function multiget ($urls,&$results)

    а вызывается уже multiget($_1,$_2)

    всё встало на места

  • Cttr
    July 25th, 2010 | 4:22 pm

    ini_set (‘max_execution_time’, 0);

    не помешает, хотя вся зависит от количества потоков

  • Александр
    September 28th, 2010 | 10:50 am

    А не подскажете, случаем, как обрабатывать Transfer-Encoding: chunked. Некоторые страницы при запросе через прокси приходят не полностью и останавливают передачу после получения первой порции данных.

Leave a reply