常見需求:抓取遠程圖片/文件存入本地服務器(如采集、鏡像、遷移)。本文提供兩種方案並分析優劣。
<?php
function downloadWithFGC(string $url, string $saveDir = '.'): ?string {
$content = @file_get_contents($url);
if ($content === false) return null;
$ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION) ?: '.bin';
$filename = $saveDir . '/' . date('YmdHis') . '_' . bin2hex(random_bytes(4)) . $ext;
if (file_put_contents($filename, $content) === false) return null;
return $filename;
}
缺點:無超時控制、無重試、無進度、大文件佔用記憶體(一次性讀入)、無法處理重定向/認證/斷點續傳。
<?php
function downloadWithCurl(string $url, string $saveDir = '.', array $options = []): ?string {
$defaults = [
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Downloader)',
];
$ch = curl_init($url);
curl_setopt_array($ch, $defaults + $options);
// 獲取文件名
$ext = '.bin';
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($ch, $header) use (&$ext) {
if (stripos($header, 'Content-Disposition:') === 0) {
if (preg_match('/filename=["\']?([^"\';]+)/i', $header, $m)) {
$ext = pathinfo($m[1], PATHINFO_EXTENSION) ? '.' . pathinfo($m[1], PATHINFO_EXTENSION) : $ext;
}
} elseif (stripos($header, 'Content-Type:') === 0) {
$mime = trim(substr($header, 13));
$extMap = ['image/jpeg'=>'.jpg','image/png'=>'.png','image/gif'=>'.gif','application/pdf'=>'.pdf'];
$ext = $extMap[$mime] ?? $ext;
}
return strlen($header);
});
$filename = $saveDir . '/' . date('YmdHis') . '_' . bin2hex(random_bytes(4)) . $ext;
$fp = fopen($filename, 'wb');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 8192);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fp);
if ($result === false || $httpCode >= 400) {
@unlink($filename);
return null;
}
return $filename;
}
function downloadResumable(string $url, string $dest, int $chunkSize = 1024*1024): bool {
$fileSize = filesize($dest) ?? 0;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RANGE => $fileSize . '-',
CURLOPT_FILE => fopen($dest, 'ab'),
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 300,
]);
$result = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $result !== false && ($code === 200 || $code === 206);
}
| 場景 | 推薦方案 | 關鍵參數 |
|---|---|---|
| 小圖標/配置文件 (<1MB) | file_get_contents | allow_url_fopen=On |
| 大文件/視頻/安裝包 | cURL 流式 | CURLOPT_BUFFERSIZE, CURLOPT_WRITEFUNCTION |
| 需認證/重定向/斷點續傳 | cURL 完整參數 | CURLOPT_RANGE, CURLOPT_COOKIE, CURLOPT_HTTPHEADER |
| 高併發采集 | cURL 多線程 | curl_multi_* 系列 |