興味のない怠け者であっても、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 ()を呼び出して手動で閉じる必要があります。
誰かがそのような実装を十分に持っているかもしれず、彼らはそれ以上読むことができないかもしれません。 さらに進んでいきます。
この実装の何が悪いのですか? 最も明白なポイントのいくつか:
- コードで直接指定された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リクエストの操作について書くこともできますが、これは招待を受けた場合です。 そして、彼はたくさん書いた、何人がマスターしたか? ;)