標籤:blog http io ar os 使用 sp for on
cURL 是一個利用URL文法規定來傳輸檔案和資料的工具,支援很多協議,如HTTP、FTP、TELNET等。最爽的是,PHP也支援 cURL 庫。本文將介紹 cURL 的一些進階特性,以及在PHP中如何運用它。為什麼要用 cURL?是的,我們可以通過其他辦法擷取網頁內容。大多數時候,我因為想偷懶,都直接用簡單的PHP函數:以下為引用的內容:$content = file_get_contents("http://www.nettuts.com");// or$lines = file("http://www.nettuts.com");// orreadfile(http://www.nettuts.com);不過,這種做法缺乏靈活性和有效錯誤處理。而且,你也不能用它完成一些高難度任務——比如處理coockies、驗證、表單提交、檔案上傳等等。引用:cURL 是一種功能強大的庫,支援很多不同的協議、選項,能提供 URL 請求相關的各種細節資訊。基本結構在學習更為複雜的功能之前,先來看一下在PHP中建立cURL請求的基本步驟: 初始化 設定變數 執行並擷取結果 釋放cURL控制代碼以下為引用的內容:// 1. 初始化$ch = curl_init();// 2. 設定選項,包括URLcurl_setopt($ch, CURLOPT_URL, "http://www.nettuts.com");curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_HEADER, 0);// 3. 執行並擷取HTML文檔內容$output = curl_exec($ch);// 4. 釋放curl控制代碼curl_close($ch);第二步(也就是 curl_setopt() )最為重要,一切玄妙均在此。有一長串cURL參數可供設定,它們能指定URL請求的各個細節。要一次性全部看完並理解可能比較困難,所以今天我們只試一下那些更常用也更有用的選項。檢查錯誤你可以加一段檢查錯誤的語句(雖然這並不是必需的):以下為引用的內容:// ...$output = curl_exec($ch);if ($output === FALSE) { echo "cURL Error: " . curl_error($ch);}// ...請注意,比較的時候我們用的是“=== FALSE”,而非“== FALSE”。因為我們得區分 空輸出 和 布爾值FALSE,後者才是真正的錯誤。擷取資訊這是另一個可選的設定項,能夠在cURL執行後擷取這一請求的有關資訊:以下為引用的內容:// ...curl_exec($ch);$info = curl_getinfo($ch);echo ‘擷取‘. $info[‘url‘] . ‘耗時‘. $info[‘total_time‘] . ‘秒‘;// ...返回的數組中包括了以下資訊: “url” //資源網路地址 “content_type” //內容編碼 “http_code” //HTTP狀態代碼 “header_size” //header的大小 “request_size” //請求的大小 “filetime” //檔案建立時間 “ssl_verify_result” //SSL驗證結果 “redirect_count” //跳轉技術 “total_time” //總耗時 “namelookup_time” //DNS查詢耗時 “connect_time” //等待串連耗時 “pretransfer_time” //傳輸前準備耗時 “size_upload” //上傳資料的大小 “size_download” //下載資料的大小 “speed_download” //下載速度 “speed_upload” //上傳速度 “download_content_length”//下載內容的長度 “upload_content_length” //上傳內容的長度 “starttransfer_time” //開始傳輸的時間 “redirect_time”//重新導向耗時基於瀏覽器的重新導向在第一個例子中,我們將提供一段用於偵測伺服器是否有基於瀏覽器的重新導向的代碼。例如,有些網站會根據是否是手機瀏覽器甚至使用者來自哪個國家來重新導向網頁。我們利用 CURLOPT_HTTPHEADER 選項來設定我們發送出的HTTP要求標頭資訊(http headers),包括user agent資訊和預設語言。然後我們來看看這些特定網站是否會把我們重新導向到不同的URL。 以下為引用的內容:// 測試用的URL$urls = array( "http://www.cnn.com", "http://www.mozilla.com", "http://www.facebook.com");// 測試用的瀏覽器資訊$browsers = array( "standard" => array ( "user_agent" => "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)", "language" => "en-us,en;q=0.5" ), "iphone" => array ( "user_agent" => "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A537a Safari/419.3", "language" => "en" ), "french" => array ( "user_agent" => "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; GTB6; .NET CLR 2.0.50727)", "language" => "fr,fr-FR;q=0.5" ));foreach ($urls as $url) { echo "URL: $url\n"; foreach ($browsers as $test_name => $browser) { $ch = curl_init(); // 設定 url curl_setopt($ch, CURLOPT_URL, $url); // 設定瀏覽器的特定header curl_setopt($ch, CURLOPT_HTTPHEADER, array( "User-Agent: {$browser[‘user_agent‘]}", "Accept-Language: {$browser[‘language‘]}" )); // 頁面內容我們並不需要 curl_setopt($ch, CURLOPT_NOBODY, 1); // 只需返回HTTP header curl_setopt($ch, CURLOPT_HEADER, 1); // 返回結果,而不是輸出它 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); // 有重新導向的HTTP頭資訊嗎? if (preg_match("!Location: (.*)!", $output, $matches)) { echo "$test_name: redirects to $matches[1]\n"; } else { echo "$test_name: no redirection\n"; } } echo "\n\n";}首先,我們建立一組需要測試的URL,接著指定一組需要測試的瀏覽器資訊。最後通過迴圈測試各種URL和瀏覽器匹配可能產生的情況。因為我們指定了cURL選項,所以返回的輸出內容則只包括HTTP頭資訊(被存放於 $output 中)。利用一個簡單的正則,我們檢查這個頭資訊中是否包含了“Location:”字樣。運行這段代碼應該會返回如下結果: 用POST方法發送資料當發起GET請求時,資料可以通過“查詢字串”(query string)傳遞給一個URL。例如,在google中搜尋時,搜尋關鍵即為URL的查詢字串的一部分:http://www.google.com/search?q=nettuts這種情況下你可能並不需要cURL來類比。把這個URL丟給“file_get_contents()”就能得到相同結果。不過有一些HTML表單是用POST方法提交的。這種表單提交時,資料是通過 HTTP請求體(request body) 發送,而不是查詢字串。例如,當使用CodeIgniter論壇的表單,無論你輸入什麼關鍵字,總是被POST到如下頁面:http://codeigniter.com/forums/do_search/你可以用PHP指令碼來類比這種URL請求。首先,建立一個可以接受並顯示POST資料的檔案,我們給它命名為post_output.php:print_r($_POST);接下來,寫一段PHP指令碼來執行cURL請求: 以下為引用的內容:$url = "http://localhost/post_output.php";$post_data = array ( "foo" => "bar", "query" => "Nettuts", "action" => "Submit");$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// 我們在POST資料哦!curl_setopt($ch, CURLOPT_POST, 1);// 把post的變數加上curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);$output = curl_exec($ch);curl_close($ch);echo $output;執行代碼後應該會得到以下結果:這段指令碼發送一個POST請求給 post_output.php ,這個頁面 $_POST 變數並返回,我們利用cURL捕捉了這個輸出。檔案上傳上傳檔案和前面的POST十分相似。因為所有的檔案上傳表單都是通過POST方法提交的。首先建立一個接收檔案的頁面,命名為 upload_output.php:print_r($_FILES);以下是真正執行檔案上傳任務的指令碼: 以下為引用的內容:$url = "http://localhost/upload_output.php";$post_data = array ( "foo" => "bar", // 要上傳的本地檔案地址 "upload" => "@C:/wamp/www/test.zip");$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_POST, 1);curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);$output = curl_exec($ch);curl_close($ch);echo $output;如果你需要上傳一個檔案,只需要把檔案路徑像一個post變數一樣傳過去,不過記得在前面加上@符號。執行這段指令碼應該會得到如下輸出: cURL批處理(multi cURL)cURL還有一個進階特性——批處理控制代碼(handle)。這一特性允許你同時或非同步地開啟多個URL串連。下面是來自來自php.net的範例程式碼: 以下為引用的內容:// 建立兩個cURL資源$ch1 = curl_init();$ch2 = curl_init();// 指定URL和適當的參數curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/");curl_setopt($ch1, CURLOPT_HEADER, 0);curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");curl_setopt($ch2, CURLOPT_HEADER, 0);// 建立cURL批處理控制代碼$mh = curl_multi_init();// 加上前面兩個資源控制代碼curl_multi_add_handle($mh,$ch1);curl_multi_add_handle($mh,$ch2);// 預定義一個狀態變數$active = null;// 執行批處理do { $mrc = curl_multi_exec($mh, $active);} while ($mrc == CURLM_CALL_MULTI_PERFORM);while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); }}// 關閉各個控制代碼curl_multi_remove_handle($mh, $ch1);curl_multi_remove_handle($mh, $ch2);curl_multi_close($mh);這裡要做的就是開啟多個cURL控制代碼並指派給一個批處理控制代碼。然後你就只需在一個while迴圈裡等它執行完畢。這個樣本中有兩個主要迴圈。第一個 do-while 迴圈重複調用 curl_multi_exec() 。這個函數是無隔斷(non-blocking)的,但會儘可能少地執行。它返回一個狀態值,只要這個值等於常量 CURLM_CALL_MULTI_PERFORM ,就代表還有一些刻不容緩的工作要做(例如,把對應URL的http頭資訊發送出去)。也就是說,我們需要不斷調用該函數,直到傳回值發生改變。而接下來的 while 迴圈,只在 $active 變數為 true 時繼續。這一變數之前作為第二個參數傳給了 curl_multi_exec() ,代表只要批處理控制代碼中是否還有活動串連。接著,我們調用 curl_multi_select() ,在活動串連(例如接受伺服器響應)出現之前,它都是被“屏蔽”的。這個函數成功執行後,我們又會進入另一個 do-while 迴圈,繼續下一條URL。還是來看一看怎麼把這一功能用到實處吧:WordPress 串連檢查器想象一下你有一個文章數目龐大的部落格,這些文章中包含了大量外部網站連結。一段時間之後,因為這樣那樣的原因,這些連結中相當數量都失效了。要麼是被和諧了,要麼是整個網站都被功夫網了...我們下面建立一個指令碼,分析所有這些連結,找出打不開或者404的網站/網頁,並產生一個報告。請注意,以下並不是一個真正可用的WordPress外掛程式,僅僅是一段獨立功能的指令碼而已,僅供示範,謝謝。好,開始吧。首先,從資料庫中讀取所有這些連結: 以下為引用的內容:// CONFIG$db_host = ‘localhost‘;$db_user = ‘root‘;$db_pass = ‘‘;$db_name = ‘wordpress‘;$excluded_domains = array( ‘localhost‘, ‘www.mydomain.com‘);$max_connections = 10;// 初始化一些變數$url_list = array();$working_urls = array();$dead_urls = array();$not_found_urls = array();$active = null;// 連到 MySQLif (!mysql_connect($db_host, $db_user, $db_pass)) { die(‘Could not connect: ‘ . mysql_error());}if (!mysql_select_db($db_name)) { die(‘Could not select db: ‘ . mysql_error());}// 找出所有含有連結的文章$q = "SELECT post_content FROM wp_posts WHERE post_content LIKE ‘%href=%‘ AND post_status = ‘publish‘ AND post_type = ‘post‘";$r = mysql_query($q) or die(mysql_error());while ($d = mysql_fetch_assoc($r)) { // 用正則匹配連結 if (preg_match_all("!href=\"(.*?)\"!", $d[‘post_content‘], $matches)) { foreach ($matches[1] as $url) { // exclude some domains $tmp = parse_url($url); if (in_array($tmp[‘host‘], $excluded_domains)) { continue; } // store the url $url_list []= $url; } }}// 移除重複的連結$url_list = array_values(array_unique($url_list));if (!$url_list) { die(‘No URL to check‘);}我們首先配置好資料庫,一系列要排除的網域名稱($excluded_domains),以及最大並發串連數($max_connections)。然後,串連資料庫,擷取文章和包含的連結,把它們收集到一個數組中($url_list)。下面的代碼有點複雜了,因此我將一小步一小步地詳細解釋: 以下為引用的內容:// 1. 批處理器$mh = curl_multi_init();// 2. 加入需批量處理的URLfor ($i = 0; $i < $max_connections; $i++) { add_url_to_multi_handle($mh, $url_list);}// 3. 初始處理do { $mrc = curl_multi_exec($mh, $active);} while ($mrc == CURLM_CALL_MULTI_PERFORM);// 4. 主迴圈while ($active && $mrc == CURLM_OK) { // 5. 有活動串連 if (curl_multi_select($mh) != -1) { // 6. 幹活 do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); // 7. 有資訊否? if ($mhinfo = curl_multi_info_read($mh)) { // 意味著該串連正常結束 // 8. 從curl控制代碼擷取資訊 $chinfo = curl_getinfo($mhinfo[‘handle‘]); // 9. 死鏈嗎? if (!$chinfo[‘http_code‘]) { $dead_urls []= $chinfo[‘url‘]; // 10. 404了? } else if ($chinfo[‘http_code‘] == 404) { $not_found_urls []= $chinfo[‘url‘]; // 11. 還能用 } else { $working_urls []= $chinfo[‘url‘]; } // 12. 移除控制代碼 curl_multi_remove_handle($mh, $mhinfo[‘handle‘]); curl_close($mhinfo[‘handle‘]); // 13. 加入新URL,幹活 if (add_url_to_multi_handle($mh, $url_list)) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } }}// 14. 完了curl_multi_close($mh);echo "==Dead URLs==\n";echo implode("\n",$dead_urls) . "\n\n";echo "==404 URLs==\n";echo implode("\n",$not_found_urls) . "\n\n";echo "==Working URLs==\n";echo implode("\n",$working_urls);// 15. 向批處理器添加urlfunction add_url_to_multi_handle($mh, $url_list) { static $index = 0; // 如果還剩url沒用 if ($url_list[$index]) { // 建立curl控制代碼 $ch = curl_init(); // 配置url curl_setopt($ch, CURLOPT_URL, $url_list[$index]); // 不想輸出返回的內容 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 重新導向到哪兒我們就去哪兒 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 不需要內容體,能夠節約頻寬和時間 curl_setopt($ch, CURLOPT_NOBODY, 1); // 加入到批處理器中 curl_multi_add_handle($mh, $ch); // 撥一下計數器,下次調用該函數就能添加下一個url了 $index++; return true; } else { // 沒有新的URL需要處理了 return false; }}下面解釋一下以上代碼。列表的序號對應著代碼注釋中的順序數字。 建立一個批處理器。Created a multi handle. 稍後我們將建立一個把URL加入批處理器的函數 add_url_to_multi_handle() 。每當這個函數被調用,就有一個新url被加入批處理器。一開始,我們給批處理器添加了10個URL(這一數字由 $max_connections 所決定)。 運行 curl_multi_exec() 進行初始化工作是必須的,只要它返回 CURLM_CALL_MULTI_PERFORM 就還有事情要做。這麼做主要是為了建立串連,它不會等待完整的URL響應。 只要批處理中還有活動串連主迴圈就會一直持續。 curl_multi_select() 會一直等待,直到某個URL查詢產生活動串連。 cURL的活兒又來了,主要是擷取響應資料。 檢查各種資訊。當一個URL請求完成時,會返回一個數組。 在返回的數組中有一個 cURL 控制代碼。我們利用其擷取單個cURL請求的相應資訊。 如果這是一個死鏈或者請求逾時,不會返回http狀態代碼。 如果這個頁面找不到了,會返回404狀態代碼。 其他情況我們都認為這個連結是可用的(當然,你也可以再檢查一下500錯誤之類...)。 從該批次移除這個cURL控制代碼,因為它已經沒有利用價值了,關了它! 很好,現在可以另外加一個URL進來了。再一次地,初始化工作又開始進行... 嗯,該乾的都幹了。關閉批處理器,產生報告。 回過頭來看給批處理器添加新URL的函數。這個函數每調用一次,靜態變數 $index 就遞增一次,這樣我們才能知道還剩多少URL沒處理。我把這個指令碼在我的部落格上跑了一遍(測試需要,有一些錯誤連結是故意加上的),結果如下: 以下為引用的內容:<img border="0" src="http://nettuts.s3.cdn.plus.org/534_curl/ss_4.png" />共檢查約40個URL,只耗費兩秒不到。當需要檢查更加大量的URL時,其省心省力的效果可想而知!如果你同時開啟10個串連,還能再快上10倍!另外,你還可以利用cURL批處理的無隔斷特性來處理大量URL請求,而不會阻塞你的Web指令碼。另一些有用的cURL 選項HTTP 認證如果某個URL請求需要基於 HTTP 的身分識別驗證,你可以使用下面的代碼:複製內容到剪貼簿代碼: 以下為引用的內容:$url = "http://www.somesite.com/members/";$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// 發送使用者名稱和密碼curl_setopt($ch, CURLOPT_USERPWD, "myusername:mypassword");// 你可以允許其重新導向curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);// 下面的選項讓 cURL 在重新導向後// 也能發送使用者名稱和密碼curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, 1);$output = curl_exec($ch);curl_close($ch);FTP 上傳PHP 內建有 FTP 類庫, 但你也能用 cURL: 以下為引用的內容:// 開一個檔案指標$file = fopen("/path/to/file", "r");// url裡包含了大部分所需資訊$url = "ftp://username:[email protected]:21/path/to/new/file";$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// 上傳相關的選項curl_setopt($ch, CURLOPT_UPLOAD, 1);curl_setopt($ch, CURLOPT_INFILE, $fp);curl_setopt($ch, CURLOPT_INFILESIZE, filesize("/path/to/file"));// 是否開啟ASCII模式 (上傳文字檔時有用)curl_setopt($ch, CURLOPT_FTPASCII, 1);$output = curl_exec($ch);curl_close($ch);FQ術你可以用代理髮起cURL請求:以下為引用的內容:$ch = curl_init();curl_setopt($ch, CURLOPT_URL,‘http://www.example.com‘);curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// 指定Proxy 位址curl_setopt($ch, CURLOPT_PROXY, ‘11.11.11.11:8080‘);// 如果需要的話,提供使用者名稱和密碼curl_setopt($ch, CURLOPT_PROXYUSERPWD,‘user:pass‘);$output = curl_exec($ch);curl_close ($ch);回呼函數可以在一個URL請求過程中,讓cURL調用某指定的回呼函數。例如,在內容或者響應下載的過程中立刻開始利用資料,而不用等到完全下載完。以下為引用的內容:$ch = curl_init();curl_setopt($ch, CURLOPT_URL,‘http://net.tutsplus.com‘);curl_setopt($ch, CURLOPT_WRITEFUNCTION,"progress_function");curl_exec($ch);curl_close ($ch);function progress_function($ch,$str) { echo $str; return strlen($str);}這個回呼函數必須返回字串的長度,不然此功能將無法正常使用。在URL響應接收的過程中,只要收到一個資料包,這個函數就會被調用。小結今天我們一起學習了cURL庫的強大功能和靈活的擴充性。希望你喜歡。下一次要發起URL請求時,考慮下cURL吧!
基於PHP的cURL快速入門