內容採集時常需將遠端圖片下載至本地伺服器,避免防盜鏈、提升載入速度、確保資料永久保存。本文提供一套完整方案:支援 cURL/fopen 雙模式下載、自動建立日期目錄、檔名衝突處理、下載失敗佔位圖、HTML 圖片路徑批次替換。
my_file_get_contents():優先使用 cURL,回退 file_get_contents,支援超時設定mkdirs():遞迴建立多層目錄,回傳布林結果get_remote():正則提取 src、下載、儲存、替換 HTML 路徑<?php
/**
* 通用遠端內容獲取 (支援 cURL / fopen)
* @param string $url
* @param int $timeout 秒
* @return string|false
*/
function my_file_get_contents($url, $timeout = 30) {
if (function_exists('curl_init')) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Image Collector)',
CURLOPT_SSL_VERIFYPEER => false, // 採集場景常關閉驗證
]);
$content = curl_exec($ch);
curl_close($ch);
return $content;
}
if (ini_get('allow_url_fopen')) {
$ctx = stream_context_create(['http' => ['timeout' => $timeout]]);
return @file_get_contents($url, false, $ctx);
}
return false;
}
/**
* 遞迴建立目錄
* @param string $dir
* @return bool
*/
function mkdirs($dir) {
if (is_dir($dir)) return true;
if (mkdirs(dirname($dir)) && mkdir($dir, 0777, true)) return true;
return false;
}
/**
* 採集內容中的遠端圖片並本地化
* @param string $body HTML 內容
* @param string $title 標題 (用於生成檔名鹽值)
* @return string 處理後的 HTML 內容
*/
function get_remote($body, $title = '') {
$imgArray = [];
$basePath = realpath(__DIR__ . "/../../../upfile/news/") . '/';
$dateDir = date("Y/m/d/");
$saveDir = $basePath . $dateDir;
$visitPath = '/upfile/news/' . $dateDir; // 前端存取路徑
$body = stripslashes(strtolower($body));
// 提取所有
的網址 (支援單雙引號、無引號)
preg_match_all('/(src|SRC)\s*=\s*["\']?\s*(https?:\/\/[^\s"\'>]+\.(gif|jpg|jpeg|png))/isu', $body, $matches);
$imgUrls = array_unique($matches[2] ?? []);
foreach ($imgUrls as $url) {
$content = my_file_get_contents($url, 60);
if (empty($content)) {
// 重試一次
sleep(10);
$content = my_file_get_contents($url, 30);
}
if (empty($content)) {
// 下載失敗:替換為佔位圖
$body = preg_replace('#' . preg_quote($url, '#') . '#isu', '/notfound.jpg', $body);
continue;
}
// 生成唯一檔名:年月日時分秒 + 隨機數 + 副檔名
$ext = strtolower(pathinfo($url, PATHINFO_EXTENSION));
$ext = in_array($ext, ['gif','jpg','jpeg','png']) ? $ext : 'jpg';
$filename = date('YmdHis') . rand(1000, 9999) . '.' . $ext;
// 確保目錄存在並寫入
if (mkdirs($saveDir)) {
$fp = fopen($saveDir . $filename, 'wb');
if ($fp && fwrite($fp, $content)) {
fclose($fp);
// 替換 HTML 中的圖片路徑
$body = preg_replace('#' . preg_quote($url, '#') . '#isu', $visitPath . $filename, $body);
} else {
if ($fp) fclose($fp);
}
}
// 禮貌延遲,避免對來源站造成壓力
sleep(6);
}
// 修正可能被壓縮的標籤
$body = str_replace('
...更多內容
?>
| 環節 | 處理方式 | 目的 |
|---|---|---|
| 下載器選擇 | 優先 cURL,其次 fopen | 相容不同主機環境,cURL 功能更完整 |
| SSL 驗證 | CURLOPT_SSL_VERIFYPEER => false | 採集 HTTPS 站點時避免憑證錯誤中斷 |
| User-Agent | 模擬瀏覽器 UA | 降低被反爬蟲攔截機率 |
| 檔名生成 | 時間戳 + 隨機數 + 原副檔名 | 避免衝突、可追溯時間、保留格式 |
| 目錄結構 | 年/月/日 三層 | 單目錄檔案過多導致 inode 耗盡、列表緩慢 |
| 失敗處理 | 重試 1 次 → 替換佔位圖 | 不中斷整體採集流程、保持版面完整 |
| 路徑替換 | preg_replace + preg_quote | 精確替換、避免特殊字元干擾正則 |
| 頻率控制 | sleep(6) | 禮貌採集、降低被封 IP 風險 |
imagemin/mozjpeg/webp 壓縮,節省儲存與頻寬data-src、data-original 等延遲載入屬性,需視來源站結構擴充/images/a.jpg) 需先補全為絕對 URL 再下載preg_replace 替換時若同一張圖在頁面出現多次,會全部被替換 (通常為預期行為)sleep() 會嚴重拖慢總耗時,務必改用非同步佇列realpath() 與路徑分隔符需注意相容性將此方案模組化封裝為 Composer 套件,即可在多專案中複用,並持續迭代優化下載器、儲存策略與替換邏輯。