cURL 是一個利用URL語法規定來傳輸檔案和資料的工具,支援許多協議,如HTTP、FTP、TELNET等。最爽的是,php也支援cURL 函式庫。本文將介紹cURL 的一些高階特性,以及在PHP中如何運用它。
為什麼要用cURL?
是的,我們可以透過其他辦法取得網頁內容。大多數時候,我因為想偷懶,都直接用簡單的php函數:
$content = file_get_contents(" http://www.bizhicool.com ");
// or
$lines = file(" http://www.bizhicool.com ");
// or
readfile( http://www.bizhicool.com );不過,這種做法缺乏彈性和有效的錯誤處理。而且,你也不能用它完成一些高難度任務——例如處理coockies、驗證、表單提交、文件上傳等等。
cURL 是一種功能強大的函式庫,支援許多不同的協定、選項,能提供URL 請求相關的各種細節資訊。
基本結構在學習更為複雜的功能之前,先來看看在php中建立cURL請求的基本步驟:
初始化設定變數執行並取得結果釋放cURL句柄
// 1. 初始化
$ch = curl_init();
// 2. 設定選項,包括URL
curl_setopt($ch, CURLOPT_URL, " http://www.bizhicool.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: $urln";
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 redirectionn";
}
}
echo "nn";
}首先,我們建立一組需要測試的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) . "nn";
echo "==404 URLs==n";
echo implode("n",$not_found_urls) . "nn";
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沒處理。
我把這個腳本在我的部落格上跑了一遍(測試需要,有一些錯誤連結是故意加上的),結果如下:
共檢查約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);翻牆術你可以用代理發起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吧!
謝謝!
原文:基於PHP的cURL快速入門
英文原文: http://net.tutsplus.com/tutorials/php/techniques-and-resources-for-mastering-curl/
原文作者:Burak Guzel
轉載需保留出處。