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"); // or readfile(http://www.nettuts.com); 不過,這種作法缺少靈活性和有效的錯誤處理。並且,你也不能用它完成一些高難度任務——好比處理coockies、驗證、表單提交、文件上傳等等。 引用: cURL 是一種功能強大的庫,支持不少不一樣的協議、選項,能提供 URL 請求相關的各類細節信息。 基本結構 在學習更爲複雜的功能以前,先來看一下在PHP中創建cURL請求的基本步驟: 初始化 設置變量 執行並獲取結果 釋放cURL句柄 如下爲引用的內容: // 1. 初始化 $ch = curl_init(); // 2. 設置選項,包括URL curl_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; // 連到 MySQL if (!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. 加入需批量處理的URL for ($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. 向批處理器添加url function 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:password@mydomain.com: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); // 指定代理地址 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吧!