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 与 Web 服务器使用的 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 URL 下载吗?
可以。使用 https:// URL,并保持证书验证启用。如果验证失败,应修复 CA 证书配置,而不是在生产环境中关闭 TLS 检查。
为什么 cURL 显示成功,下载却仍然失败?
对于 HTTP,请单独检查响应状态,不要只看 curl_exec()。传输完成后,响应内容仍可能是错误页面,或带有非成功状态码。还应确认 PHP 有权限写入目标位置,并且磁盘空间充足。
如何跟踪下载进度?
启用传输回调并使用其字节计数,同时考虑总长度可能未知。回调报告的是传输进度;它不能保证服务器提供了可靠的内容长度。