基於 Socket 底層實現的圖片下載器,可繞過防盜鏈、偽造來路、處理重定向、支援大檔案分塊下載。
<?php
/**
* 下載遠程圖片
* @param string $gurl 圖片 URL
* @param string $rfurl Referer (防盜鏈繞過)
* @param string $filename 儲存檔名 (相對路徑)
* @param string $gcookie 偽造 Cookie
* @param int $jumpCount 重導向計數 (內部用)
* @param int $maxtime 最大執行秒數
* @return bool
*/
function DownImageKeep($gurl, $rfurl, $filename, $gcookie = '', $jumpCount = 0, $maxtime = 30) {
$urlInfos = GetHostInfo($gurl);
$ghost = trim($urlInfos['host']);
if ($ghost === '') return false;
$gquery = $urlInfos['query'];
// 自動從 Referer 獲取 Cookie
if ($gcookie === '' && !empty($rfurl)) {
$gcookie = RefurlCookie($rfurl);
}
// 建構 HTTP 請求
$request = "GET $gquery HTTP/1.1\r\n";
$request .= "Host: $ghost\r\n";
$request .= "Referer: $rfurl\r\n";
$request .= "Accept: */*\r\n";
$request .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)\r\n";
if ($gcookie !== '' && !preg_match('/[\r\n]/', $gcookie)) {
$request .= $gcookie . "\r\n";
}
$request .= "Connection: Keep-Alive\r\n\r\n";
// 連線
$errno = $errstr = '';
$fp = fsockopen($ghost, 80, $errno, $errstr, 10);
if (!$fp) return false;
fwrite($fp, $request);
// 解析回應標頭
$headers = [];
$statusLine = fgets($fp, 256);
$statusParts = explode(' ', $statusLine);
$headers['http-version'] = trim($statusParts[0]);
$headers['http-code'] = trim($statusParts[1]);
$lineNum = 0;
while (!feof($fp)) {
$line = trim(fgets($fp, 256));
if ($line === '' || $lineNum > 100) break;
if (strpos($line, ':') !== false) {
list($key, $value) = explode(':', $line, 2);
$headers[strtolower(trim($key))] = trim($value);
}
$lineNum++;
}
// 處理重定向 (3xx)
if (preg_match('/^3/', $headers['http-code'])) {
if (isset($headers['location']) && $jumpCount < 3) {
return DownImageKeep($headers['location'], $rfurl, $filename, $gcookie, $jumpCount + 1, $maxtime);
}
return false;
}
// 非 2xx 失敗
if (!preg_match('/^2/', $headers['http-code'])) return false;
$contentLength = isset($headers['content-length']) ? (int)$headers['content-length'] : 0;
// 寫入檔案
$outFp = fopen($filename, 'w');
if (!$outFp) return false;
$written = 0;
$startTime = time();
while (!feof($fp)) {
$chunk = fread($fp, 8192);
if ($chunk === '') break;
fwrite($outFp, $chunk);
$written += strlen($chunk);
// 逾時檢查
if (time() - $startTime > $maxtime) break;
// 大小檢查
if ($contentLength > 0 && $written >= $contentLength) break;
}
fclose($outFp);
fclose($fp);
if ($written === 0) {
@unlink($filename);
return false;
}
return true;
}
/** 解析 URL 取得 host 與 query */
function GetHostInfo($gurl) {
$gurl = preg_replace('/^https?:\/\//i', '', trim($gurl));
$host = preg_replace('/\/(.*)$/', '', $gurl);
$query = '/' . preg_replace('/^[^\/]*\//', '', $gurl);
return ['host' => $host, 'query' => $query];
}
/** 從 Referer 獲取 Cookie */
function RefurlCookie($gurl) {
static $lastUrl = '', $cachedCookie = '';
if ($cachedCookie && $lastUrl === $gurl) return $cachedCookie;
$lastUrl = $gurl;
$info = GetHostInfo($gurl);
$request = "GET {$info['query']} HTTP/1.1\r\n";
$request .= "Host: {$info['host']}\r\n";
$request .= "Accept: */*\r\n";
$request .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)\r\n";
$request .= "Connection: Close\r\n\r\n";
$fp = fsockopen($info['host'], 80, $errno, $errstr, 10);
if (!$fp) return '';
fwrite($fp, $request);
while (!feof($fp)) {
$line = trim(fgets($fp, 256));
if ($line === '') break;
if (stripos($line, 'set-cookie:') === 0 || stripos($line, 'cookie:') === 0) {
$cachedCookie = $line;
break;
}
}
fclose($fp);
return $cachedCookie;
}
<?php
// 基本用法
DownImageKeep(
'https://www.baidu.com/img/baidu_jgylogo2.gif',
'https://baidu.com',
'baidu_logo.gif'
);
// 帶 Cookie、偽造 Referer 繞過防盜鏈
DownImageKeep(
'https://example.com/protected/image.jpg',
'https://example.com/referer-page.html',
'image.jpg',
'sessionid=abc123; user_token=xyz'
);
// 批量下載
$images = [
['url' => 'https://site.com/1.jpg', 'ref' => 'https://site.com/', 'name' => '1.jpg'],
['url' => 'https://site.com/2.jpg', 'ref' => 'https://site.com/', 'name' => '2.jpg'],
];
foreach ($images as $img) {
DownImageKeep($img['url'], $img['ref'], $img['name']);
sleep(1); // 禮貌間隔
}
function downloadImageCurl($url, $referer = '', $savePath, $cookie = '', $timeout = 30) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_REFERER => $referer,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
CURLOPT_COOKIE => $cookie,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
]);
$data = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || $data === false) return false;
return file_put_contents($savePath, $data) !== false;
}
| 場景 | 推薦方案 |
|---|---|
| 現代專案、HTTPS、複雜重定向 | cURL 版本 |
| 無 cURL 擴充環境、需精細控制 Socket | Socket 版本 |
| 大檔案、斷點續傳、限速 | cURL + CURLOPT_RANGE |
| 並發下載多張圖片 | curl_multi |