PHPのcURLでのマルチスレッドダウンロード

このトピックでは、私の意見では、PHP用のマルチスレッドcURLダウンロードの便利で機能的な実装が提示されています。 おそらくそれは誰かに役立つでしょうが、招待が私をもたらします;)



興味のない怠け者であっても、cURL経由のダウンロードは使用しませんでした。 コンソールからでも、ある種の言語でコードを実装することでも可能です。 1つのリンクのダウンロードをブロックするソリューションは、たとえばphp.netなど、ネットワークの隅々にあります。 ただし、PHPでの実装を検討する場合、この方法は、補助操作(DNSルックアップ、リクエスト待機など)に時間がかかるため、適切でない場合があります。 多数のページをダウンロードする場合、順次バージョンは受け入れられません。 あなたが満足している場合-あなたはこれ以上読むことができません:)



たとえば、Perlでは、 fork ()またはthread( use threads )を使用して、シングルスレッドのダウンロードを並列化できます。 これは、この言語のライブラリの豊富な機能をカウントしていません。 個人的にスレッドとLWPを適用しました。 ただし、PHPについて話しているため、原則的にこの機能がないため、並列化には大きな問題があります。 スレッドを作成する方法を知っている人がいたら教えてください。 はい、cURLにはcurl_multi_ *関数がありますが、それらに基づく実装の例は私には向いていませんでした。 そして、最終的には、自転車を組み立てることにしました。



最初は、 offから最も単純な例を参照します。 参考書 。 ここに持ってきてください:)

<?php

//両方のcURLリソースを作成します

$ ch1 = curl_init ();

$ ch2 = curl_init ();



// URLおよびその他の適切なオプションを設定します



curl_setopt $ ch1 CURLOPT_URL " www.example.com " );

curl_setopt $ ch1 CURLOPT_HEADER 0 );

curl_setopt $ ch2 CURLOPT_URL " www.php.net " );



curl_setopt $ ch2 CURLOPT_HEADER 0 );



//複数のcURLハンドルを作成します

$ mh = curl_multi_init ();





// 2つのハンドルを追加します

curl_multi_add_handle $ mh $ ch1 );

curl_multi_add_handle $ mh $ ch2 );





$ running = null ;

//ハンドルを実行します

{

curl_multi_exec $ mh $ running );

} while( $ running > 0 );





//ハンドルを閉じます

curl_multi_remove_handle $ mh $ ch1 );

curl_multi_remove_handle $ mh $ ch2 );

curl_multi_close $ mh );



?>



このコードは、アプリケーションコードとライブラリの相互作用のより複雑な構成により、シングルスレッドアプローチとは異なります。

1)各接続には独自のcurl_init ()があり、パラメーターはcurl_setopt ()で設定されます。 ここではすべてが標準です。説明なしに引用します。

2) curl_multi_init ()呼び出しのダウンロードの一般的な制御のために、個別の記述子が作成され、それを介してすべてのさらなる作業が実行されます。

3)指定された記述子へのcurl_multi_add_handle ()呼び出しは、最初に別の接続を作成します

準備段階が完了し、今すぐ直接ダウンロードします:

4)ライブラリは自動的にダウンロードされます; curl_exec ()のように明示的な呼び出しはもうありません。 curl_multi_exec ()を繰り返し呼び出すことで置き換えられます。 名前は似ていますが、この関数はわずかに異なる役割を果たします-アクティブなスレッドの数の変化をブロックして通知します(発生したエラー)。 2番目のパラメーターは、呼び出されると、現在アクティブな接続の数を格納する数値変数への参照です。 数量が変更されました-これは、一部のスレッドが作業を完了したことを意味します。 このため、ダウンロードサイクルは

{

curl_multi_exec $ mh $ running );

} while( $ running > 0 );



5)最後に、ダウンロード後、リソースが解放されます。 重要! curl_init ()によって作成された接続はメイン記述子に「固執」しますが、自動的には閉じません。curl_close ()に加えてcurl_multi_remove_handle ()を呼び出して手動で閉じる必要があります。



誰かがそのような実装を十分に持っているかもしれず、彼らはそれ以上読むことができないかもしれません。 さらに進んでいきます。

この実装の何が悪いのですか? 最も明白なポイントのいくつか:

  1. コードで直接指定された2つのリンクのダウンロードに関する厳しい制限
  2. 結果のページはSTDOUTに直接表示されます


これはほんの一部であり、残りは以下で説明します。



私はこれらの欠点を修正し、たとえば次のようになります:

<?php

$ urls = array( " www.example.com " " www.php.net " );



$ mh = curl_multi_init ();



$ chs = array();



foreach( $ urls as $ url ){

$ chs [] =( $ ch = curl_init ());

curl_setopt $ ch CURLOPT_URL $ url );



curl_setopt $ ch CURLOPT_HEADER 0 );

// CURLOPT_RETURNTRANSFER-関数の結果として値を返し、stdoutには出力しません



curl_setopt $ ch CURLOPT_RETURNTRANSFER 1 );

curl_multi_add_handle $ mh $ ch );

}



$ prev_running = $ running = null ;



{

curl_multi_exec $ mh $ running );



if( $ running != $ prev_running ){

//現在の接続に関する情報を取得します



$ info = curl_multi_info_read $ mh );



if( is_array $ info )&&( $ ch = $ info [ 'handle' ])){



//ロードされたページのコンテンツを取得します

$ content = curl_multi_getcontent $ ch );



//ここにある種のページテキスト処理



//現時点では元のように-STDOUTへの出力

エコー $コンテンツ ;

}





//現在アクティブな接続のキャッシュ数を更新します

$ prev_running = $ running ;

}



} while( $ running > 0 );





foreach( $ chs as $ ch ){

curl_multi_remove_handle $ mh $ ch );

curl_close $ ch );



}

curl_multi_close $ mh );

?>





さらに、ほとんどの場合、STDOUTでページを表示するのは非常に簡単です。 さらに、これは実際のダウンロードの順序に応じてランダムな順序で発生します(ジョブはcurl_multi_add_handle ()を呼び出しません)。 また、大量のボリュームがダウンロードされる場合、すべてのページが受信されるのを待つことは意味がありません。受信したページの処理を開始できます。 しかし、すべてを一括で取得するオプションも、離陸する価値はありません。

これを行うには、1)関数の形式ですべてを実装します。2)受信した各ファイルに対して呼び出されるコールバック関数を指定するパラメーターを導入します。 コールバックが設定されていない場合、すべてのページを一度に取得するオプションが適用されます。 以下に例を示します。

<?php

//単純なコールバックの例。 実質的にダミー機能。

function my_callback $ url $ content $ curl_status $ ch ){



echo "ページのダウンロード[$ url]" ;

if(! $ curl_status ){

エコーは 「成功しました。 ページテキスト:\ n $ content \ n " ;



}

その他{

echo "エラーで失敗しました#$ curl_status:" curl_error $ ch )。 "\ n" ;

}



}



function http_load $ urls $ callback = false ){

$ mh = curl_multi_init ();





$ chs = array();

foreach( $ urls as $ url ){

$ chs [] =( $ ch = curl_init ());



curl_setopt $ ch CURLOPT_URL $ url );

curl_setopt $ ch CURLOPT_HEADER 0 );

// CURLOPT_RETURNTRANSFER-関数の結果として値を返し、stdoutには出力しません



curl_setopt $ ch CURLOPT_RETURNTRANSFER 1 );

curl_multi_add_handle $ mh $ ch );

}



// $コールバックがfalseに設定されている場合、関数は$コールバックを呼び出すべきではありませんが、作業の結果としてページを返します



if( $ callback === false ){

$ results = array();

}



$ prev_running = $ running = null ;



{

curl_multi_exec $ mh $ running );



if( $ running != $ prev_running ){

//現在の接続に関する情報を取得します



$ info = curl_multi_info_read $ ghandler );



if( is_array $ info )&&( $ ch = $ info [ 'handle' ])){



//ロードされたページのコンテンツを取得します

$ content = curl_multi_getcontent $ ch );



//ダウンロードしたリンク

$ url = curl_getinfo $ ch CURLINFO_EFFECTIVE_URL );





if( $ callback !== false ){

//コールバックハンドラーを呼び出します

$コールバック $ url $ content $ info [ 'result' ]、 $ ch );



}

その他{

//結果のハッシュに追加します

$ results [ $ url ] = array( 'content' => $ content 'status' => $ info [ 'result' ]、 'status_text' => curl_error $ ch ));



}



}



//現在アクティブな接続のキャッシュ数を更新します

$ prev_running = $ running ;

}





} while( $ running > 0 );



foreach( $ chs as $ ch ){

curl_multi_remove_handle $ mh $ ch );



curl_close $ ch );

}

curl_multi_close $ mh );



//結果

return( $ callback !== false )? true $ results ;



}



$ urls = array( " www.example.com " " www.php.net " );



//簡単な発行のオプション

print_r http_load $ urls ));





//コールバック付きオプション

var_export http_load $ urls my_callback ));



?>



すでにはるかに興味深い。 重要な点:コールバックの場合、4番目のパラメーターは$ ch接続記述子であり、ハッシュを出力する場合、エラーの単なる文字列の説明です(すべてが問題なければ、空の文字列です)。 なんで? curl_error()は記述子を渡す必要があり、関数の最後で終了します。 そのため、コールバックではまだ存在し、使用できますが、ハッシュでは何の価値も与えられません。 または、エラーコードの文字列の説明はこちらにあります



それでは、先に進みましょう。 リンクの配列に対してだけでなく、単一のページをダウンロードできるように関数を呼び出したいです。 これを行うには、1行だけ追加します。

<?php function http_load $ urls $ callback = false ){

...

//唯一のパラメータが渡されても、配列要素とみなします



//これはアナログです:$ urls = is_array($ urls)? $ urls:配列($ urls);

$ urls =(配列) $ urls ;



.... ?>



これで、リンクを一度に1つずつダウンロードできます:http_load( 'google.com')。 ある種の基本への回帰。



次に、接続用にさらに多くの送信ヘッダーを設定する必要がありました。 curl_setopt()で一度に1つずつ指定するのは実用的ではありません。 curl_setopt_array関数を使用することをお勧めします。 やり直して取得(コードの一部):

<?php

{ //すべての接続に共通のヘッダー

$ ext_headers = array(

「期待:」

'Accept:text / html、application / xhtml + xml、application / xml; q = 0.9'



'Accept-Language:ru、en-us; q = 0.7、en; q = 0.7'

// 'Accept-Encoding:gzip、deflate'、//後で解凍する必要があります。 さて、今のところ...

'文字セットを受け入れる:utf-8、windows-1251; q = 0.7、*; q = 0.5'

);

$ curl_options = array(



CURLOPT_PORT => 80

CURLOPT_RETURNTRANSFER => 1 //関数の結果として値を返し、stdoutには出力しません



CURLOPT_BINARYTRANSFER => 1 //バイナリセーフに渡します

CURLOPT_CONNECTTIMEOUT => 10 //接続タイムアウト(ルックアップ+接続)



CURLOPT_TIMEOUT => 30 //データ受信のタイムアウト

CURLOPT_USERAGENT => 'Mozilla / 5.0(X11; U; Linux x86_64; en-US; rv:1.9.1.1)Gecko / 20090716 Ubuntu / 9.04(jaunty)Shiretoko / 3.5.1'



CURLOPT_VERBOSE => 2 //情報レベル

CURLOPT_HEADER => 0 //ヘッダーは機能しません

CURLOPT_FOLLOWLOCATION => 1 //リダイレクトに従う



CURLOPT_MAXREDIRS => 7 //リダイレクトの最大数

CURLOPT_AUTOREFERER => 1 //リダイレクトする場合、「Referer:」を「Location:」の値に置き換えます



// CURLOPT_FRESH_CONNECT => 0、//毎回新しい接続を使用

CURLOPT_HTTPHEADER => $ ext_headers

);

}





function http_load $ urls $ callback = false ){

グローバル $ curl_options ;



$ mh = curl_multi_init ();



if( $ mh === false falseを返す ;



$ urls =(配列) $ urls ;



$ chs = array();



foreach( $ urls as $ url ){

$ chs [] =( $ ch = curl_init ());





curl_setopt_array $ ch $ curl_options ); //ヘッダーを一括で設定します

curl_setopt $ ch CURLOPT_URL $ url );





curl_multi_add_handle $ mh $ ch );

}

...

?>





Firefoxのふりをします。 見出しについてコメントしました。 詳細な説明については、 こちらに送信してください

そして、これらのヘッダーを追跡するために、関数に3番目のパラメーターが追加されます。

<?php function http_load( $urls, $callback = false, $urls_params = array() ) {} ?>





ヘッダーを指定できます。ヘッダーは、初期化時に接続に追加されます。 したがって、パラメーターを使用してPOSTリクエストを正常に送信したり、紹介や送信データの形式を指定したりできます(たとえば、圧縮中)。

<?php

...

foreach( $ urls as $ ind => $ url ){

$ chs [] =( $ ch = curl_init ());





curl_setopt_array $ ch $ curl_options ); //ヘッダーを一括で設定します

curl_setopt $ ch CURLOPT_URL $ url );





//この接続を初期化する追加のパラメーターはありますか?

if(isset( $ urls_params [ $ ind ])&& is_array $ urls_params [ $ ind ])){



curl_setopt_array $ ch $ urls_params [ $ ind ]);

}



curl_multi_add_handle $ mh $ ch );



}

...

?>





これがそのような関数です。 また、CookieとPOSTリクエストの操作について書くこともできますが、これは招待を受けた場合です。 そして、彼はたくさん書いた、何人がマスターしたか? ;)



All Articles