在內容聚合、編輯器圖片自動儲存、爬蟲等場景中,將遠端圖片下載至本地伺服器是常見需求。本文整理 5 種不同層級的實作方案,從簡單的單張下載到支援斷點續傳的完整類庫,並分析各方案適用場景與效能考量。
適合下載前預檢檔案大小、類型,避免下載超大或非預期檔案。
function getFileSize($url) {
$parsed = parse_url($url);
$host = $parsed['host'];
$port = $parsed['port'] ?? 80;
$path = $parsed['path'] ?? '/';
if ($fp = @fsockopen($host, $port, $errno, $errstr, 5)) {
fputs($fp, "GET $path HTTP/1.1\r\nHost: $host\r\n\r\n");
while (!feof($fp)) {
$line = fgets($fp);
if (trim($line) === '') break;
if (preg_match('/Content-Length:\s*(\d+)/i', $line, $m)) {
return (int)$m[1];
}
}
}
return null;
}
最常見的簡易做法,利用輸出緩衝捕獲二進位資料再寫入檔案。
function grabImage($url, $filename = '') {
if (!$url) return false;
if (!$filename) {
$ext = strrchr($url, '.');
if (!in_array(strtolower($ext), ['.gif', '.jpg', '.jpeg', '.png'])) return false;
$filename = date('YmdHis') . $ext;
}
ob_start();
readfile($url);
$img = ob_get_clean();
file_put_contents($filename, $img);
return $filename;
}
適用於文章內容本地化,配合正則提取 src 屬性,逐一下載並替換路徑。
function saveRemoteImages(&$body, $saveDir) {
preg_match_all('/src\s*=\s*["\']?(https?:\/\/[^"\'>\s]+\.(?:gif|jpg|jpeg|png|bmp))/i', $body, $matches);
$urls = array_unique($matches[1]);
$dateDir = $saveDir . '/' . date('Ymd');
if (!is_dir($dateDir)) mkdir($dateDir, 0777, true);
foreach ($urls as $i => $url) {
$ext = pathinfo($url, PATHINFO_EXTENSION);
$newName = date('His') . $i . '.' . $ext;
$localPath = $dateDir . '/' . $newName;
if ($data = @file_get_contents($url)) {
file_put_contents($localPath, $data);
$body = str_replace($url, $localPath, $body);
}
}
}
針對大檔案下載,實作 HTTP Range 請求、頻寬控制、進度追蹤,適合建立下載站或大型素材庫。
class HttpDownload {
public $use_resume = true;
public $bufsize = 8192;
public $speed = 0; // KB/s, 0 = 不限速
private $filePath;
private $fileSize;
public function set_byfile($path) {
if (!is_readable($path)) return false;
$this->filePath = $path;
$this->fileSize = filesize($path);
return true;
}
public function download() {
if (!$this->filePath) return false;
$fp = fopen($this->filePath, 'rb');
$start = 0; $end = $this->fileSize - 1;
if ($this->use_resume && isset($_SERVER['HTTP_RANGE'])) {
list(, $range) = explode('=', $_SERVER['HTTP_RANGE']);
list($start, $end) = array_map('intval', explode('-', $range));
if ($end == 0 || $end > $this->fileSize - 1) $end = $this->fileSize - 1;
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $start-$end/{$this->fileSize}");
}
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($this->filePath) . '"');
header("Content-Length: " . ($end - $start + 1));
header('Accept-Ranges: bytes');
fseek($fp, $start);
while (!connection_aborted() && $start <= $end) {
$read = min($this->bufsize, $end - $start + 1);
echo fread($fp, $read);
flush();
$start += $read;
if ($this->speed && ftell($fp) % (1024 * $this->speed) === 0) sleep(1);
}
fclose($fp);
}
}
// 使用範例
$dl = new HttpDownload();
$dl->set_byfile('/data/large.zip');
$dl->download();
若需在下載時同步處理圖片(縮放、裁切、加浮水印),可透過 GD 讀取遠端圖片資源。
$src = imagecreatefromjpeg('https://example.com/photo.jpg');
$w = imagesx($src); $h = imagesy($src);
$dst = imagecreatetruecolor($w, $h);
imagecopy($dst, $src, 0, 0, 0, 0, $w, $h);
imagejpeg($dst, 'local_copy.jpg', 90);
imagedestroy($src); imagedestroy($dst);
注意: GD 讀取遠端圖片需啟用 allow_url_fopen,且受 memory_limit 限制,大圖建議改用方案四或 Imagick 串流處理。