HOWTO · PHP
在 PHP 中使用 CURL 下載檔案
本教程演示如何使用 cURL 庫在 PHP 中下載檔案。
本頁內容
使用 PHP 的 cURL 擴充功能向 URL 發出請求,並將回應串流寫入本機檔案。PHP 擴充功能與 curl 命令列程式不同:PHP 程式碼會使用 curl_init() 等函式,以及 CURLOPT_FILE 等選項。
cURL 在 PHP 中的用途
cURL 為 PHP 提供透過 libcurl 發出請求的介面。下載時,關鍵在於決定回應資料要寫到哪裡。將 CURLOPT_FILE 設為已開啟的檔案控制代碼,回應資料便會在傳送過程中直接寫入檔案,而不必先將完整回應存入 PHP 記憶體。以下範例使用本機測試檔,因此不需要外部服務即可執行;在應用程式中,請將 $url 替換成 HTTPS 下載網址。
檢查 PHP 是否可使用 cURL
請在實際執行下載的同一個 PHP 環境中檢查擴充功能。命令列使用的 PHP 安裝環境,與網頁伺服器使用的 PHP 安裝環境,可能載入不同的設定檔。你可以在終端機執行 php -m 並尋找 curl,或在應用程式中呼叫 extension_loaded('curl')。phpinfo() 頁面也會列出 cURL,但請移除或妥善保護該頁面,因為它會公開設定資訊。
如果缺少此擴充功能,請依照該 PHP 版本的指示啟用或安裝,並重新啟動相關的 PHP 程序。若使用代管服務,請聯絡服務供應商,要求啟用 PHP 的 cURL 擴充功能。請勿將啟用此擴充功能與安裝獨立的 curl 命令列工具混為一談。
將檔案下載到磁碟
這個完整的 PHP 範例會建立一個本機來源檔案,再將內容儲存到另一個檔案。若 cURL 擴充功能可用,範例會使用 CURLOPT_FILE 串流寫入回應,並檢查傳輸結果,以及(使用 HTTP 或 HTTPS 時)回應狀態。如果無法使用該擴充功能,本機測試檔會透過 PHP 的 copy() 串流包裝器處理,讓範例仍可執行;這個替代方式不會測試 cURL。在已啟用 cURL 的應用程式中,請將 $url 替換成要下載檔案的 HTTPS 網址。
執行此範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
<?php
$sourcePath = tempnam(sys_get_temp_dir(), 'curl-source-');
$destinationPath = tempnam(sys_get_temp_dir(), 'curl-download-');
if ($sourcePath === false || $destinationPath === false) {
fwrite(STDERR, "Could not create temporary files.\n");
exit(1);
}
file_put_contents($sourcePath, "Sample download\n");
$url = 'file://' . $sourcePath;
$isHttp = strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0;
$downloaded = false;
$failure = '';
if (function_exists('curl_init')) {
$handle = curl_init($url);
$destination = fopen($destinationPath, 'wb');
if ($handle !== false && $destination !== false) {
curl_setopt($handle, CURLOPT_FILE, $destination);
$transferred = curl_exec($handle);
$error = curl_error($handle);
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$downloaded = $transferred !== false && (!$isHttp || ($status >= 200 && $status < 300));
$failure = $error !== '' ? $error : "HTTP status $status";
} else {
$failure = 'Could not initialize cURL or open the destination file.';
}
if ($handle !== false) {
curl_close($handle);
}
if ($destination !== false) {
fclose($destination);
}
} else {
$downloaded = copy($url, $destinationPath);
if (!$downloaded) {
$failure = 'PHP cURL is unavailable and the local stream copy failed.';
}
}
if (!$downloaded) {
fwrite(STDERR, "Download failed: $failure\n");
unlink($destinationPath);
unlink($sourcePath);
exit(1);
}
printf("Saved %d bytes.\n", filesize($destinationPath));
unlink($destinationPath);
unlink($sourcePath);
Saved 16 bytes.
以二進位寫入模式(wb)開啟目的地會取代現有檔案,並避免在區分文字與二進位檔案的平台上發生文字模式轉換。在應用程式中,請先驗證目的目錄與檔名,再開啟檔案;切勿直接從不受信任的 URL 建立可信任的檔案系統路徑。使用 fopen() 和 curl_init() 的回傳值前,請先檢查是否成功;即使傳輸失敗,也要關閉這兩個資源。較大型的程式可使用 try/finally 或相同用途的清理機制,避免提早返回時留下未關閉的控制代碼。
處理 cURL 與 HTTP 錯誤
curl_exec() 失敗表示傳輸層發生問題,例如 DNS、TLS、逾時或連線失敗。關閉控制代碼前,請檢查 curl_error($handle);若程式需要穩定的錯誤編號,則可使用 curl_errno($handle)。cURL 傳輸成功,並不代表伺服器回傳了成功的 HTTP 回應:HTTP 404 和 500 仍然是回應。下載 HTTP 資源時,請檢查 CURLINFO_RESPONSE_CODE,並判斷應用程式接受哪些狀態碼範圍。
開始請求前,也要確認目的地檔案控制代碼有效。權限問題、目錄不存在或磁碟已滿,都可能導致本機檔案無法建立或寫入。請記錄足以診斷問題的內部資訊,但向使用者顯示簡短訊息,並避免公開憑證或敏感的伺服器資訊。如果不希望部分下載的檔案被誤認為完整檔案,請在傳輸或狀態檢查失敗後刪除它;範例的失敗處理也採取此做法。
下載大型檔案並回報進度
使用 CURLOPT_FILE 寫入時,回應本文不會存入單一 PHP 字串,因此記憶體用量不會隨整個檔案大小增加。目的地仍須有足夠的可用磁碟空間。請依照服務和預期檔案大小設定連線逾時與總逾時;長時間傳輸時,應審慎延長總逾時,而不是讓請求不受時間限制。伺服器可能不提供檔案總長度,因此直到傳輸完成前,進度都可能無法確定。
若要顯示進度,請將 CURLOPT_NOPROGRESS 設為 false 以啟用回呼,並在支援的 PHP/libcurl 版本中提供 CURLOPT_XFERINFOFUNCTION。回呼會收到已下載的位元組數與總位元組數;只有在總數大於零時才計算百分比。進度回呼可能頻繁執行,因此請避免耗費大量資源的工作,也不要每次更新都新增一行記錄。回呼與逾時值取決於應用程式的 PHP/libcurl 環境;此處沒有展示其實際執行結果。
常見問題
PHP 的 cURL 和 curl 命令相同嗎?
不相同。PHP 的 cURL 是從 PHP 程式碼呼叫的擴充功能。curl 命令列程式是另一個獨立工具,具有自己的選項,例如 -o;這些命令列選項不是 PHP 語法。
可以從 HTTPS 網址下載嗎?
可以。請使用 https:// 網址,並保持憑證驗證啟用。如果驗證失敗,請修正 CA 憑證設定,不要在正式環境中停用 TLS 檢查。
為什麼 cURL 顯示成功,下載卻仍然失敗?
使用 HTTP 時,請將回應狀態與 curl_exec() 的結果分開檢查。傳輸完成後,內容仍可能是錯誤頁面,或回應狀態並非成功。也請確認 PHP 有權寫入目的地,且磁碟空間足夠。
如何追蹤下載進度?
啟用傳輸回呼並使用它提供的位元組數,同時考量總長度可能未知。回呼回報的是傳輸進度,並不保證伺服器提供了可靠的內容長度。