核心需求分析
- 從 HTML 中提取
<img> 標簽的 src 屬性值
- 排除包含特定屬性 (如
ico, data-ignore) 的圖片
- 將相對路徑轉換為絕對 URL (注入當前域名/CDN 域名)
- 替換原 HTML 中的 src 為新 URL
完整解決方案類
<?php
class ImageUrlProcessor {
private string $baseUrl;
private array $excludeAttrs = ['ico', 'data-ignore', 'data-no-cdn'];
private string $imgRegex;
public function __construct(string $baseUrl = '') {
$this->baseUrl = rtrim($baseUrl, '/');
// 正則: 匹配
excludeAttrs));
$this->imgRegex = '/
])*\bsrc=(["\'])(.*?)\1/';
}
public function setExcludeAttrs(array $attrs): self {
$this->excludeAttrs = $attrs;
return $this;
}
// 提取並轉換 URL
public function process(string $html): array {
$result = [
'html' => $html,
'images' => [],
'replaced' => 0,
];
$urls = [];
$patterns = [];
$replacements = [];
// 1. 找出所有符合條件的 img 標簽及 src
preg_match_all($this->imgRegex, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
foreach ($matches as $m) {
$fullTag = $m[0][0];
$quote = $m[1][0];
$src = $m[2][0];
$tagPos = $m[0][1];
$srcPos = $m[2][1];
// 2. 構建絕對 URL
$absoluteUrl = $this->toAbsoluteUrl($src);
// 3. 記錄
$urls[] = [
'original_src' => $src,
'absolute_url' => $absoluteUrl,
'tag' => $fullTag,
'position' => $srcPos,
];
// 4. 準備替換 (按位置倒序替換,避免位置偏移)
$patterns[] = '/' . preg_quote($quote . $src . $quote, '/') . '/';
$replacements[] = $quote . $absoluteUrl . $quote;
}
// 5. 執行替換 (倒序)
if ($patterns) {
// 為了精確替換對應位置,使用 preg_replace_callback 配合偏移
// 簡化版:直接全局替換 (假設 src 值唯一)
$result['html'] = preg_replace($patterns, $replacements, $html, -1, $count);
$result['replaced'] = $count;
}
$result['images'] = $urls;
return $result;
}
// 相對路徑轉絕對
private function toAbsoluteUrl(string $src): string {
// 已是絕對 URL 或協議相對 URL
if (preg_match('/^(?:https?:)?\/\//i', $src) || preg_match('/^[a-z]+:/i', $src)) {
return $src;
}
// 根相對路徑 /path
if (str_starts_with($src, '/')) {
return $this->baseUrl . $src;
}
// 相對路徑 path/to/img.jpg
return $this->baseUrl . '/' . ltrim($src, '/');
}
}
// 使用示例
$html = '<div>
<img ico src="/favicon.ico" /> <!-- 排除: 有 ico 屬性 -->
<img src="/images/logo.png" alt="Logo"> <!-- 轉換 -->
<img data-ignore src="banner.jpg"> <!-- 排除 -->
<img src="uploads/photo.jpg" class="thumb"> <!-- 轉換 -->
<img src="https://external.com/ad.jpg"> <!-- 保持不變 -->
</div>';
$processor = new ImageUrlProcessor('https://cdn.myapp.com');
$result = $processor->process($html);
echo "處理後 HTML:\n$result[html]\n\n";
echo "提取圖片:\n";
print_r($result['images']);
輸出結果
處理後 HTML:
<div>
<img ico src="/favicon.ico" />
<img src="https://cdn.myapp.com/images/logo.png" alt="Logo">
<img data-ignore src="banner.jpg">
<img src="https://cdn.myapp.com/uploads/photo.jpg" class="thumb">
<img src="https://external.com/ad.jpg">
</div>
提取圖片:
Array
(
[0] => Array
(
[original_src] => /images/logo.png
[absolute_url] => https://cdn.myapp.com/images/logo.png
[tag] =>
[position] => 45
)
[1] => Array
(
[original_src] => uploads/photo.jpg
[absolute_url] => https://cdn.myapp.com/uploads/photo.jpg
[tag] =>
[position] => 132
)
)
正則核心解析:負向前瞻排除屬性
// 關鍵片段: (?:(?!ico|data-ignore)[^>])*
// 拆解:
// (?: ... )* 非捕獲組,重複 0~n 次
// (?!ico|data-ignore) 負向前瞻:當前位置後面不能是 "ico" 或 "data-ignore"
// [^>] 匹配任意非 > 字符 (即標簽內屬性字符)
// 整體含義: 匹配任意長度的標簽內容,但內容中任意位置都不出現排除詞
DOMDocument 專業版 (推薦用於複雜 HTML)
<?php
function processImagesDom(string $html, string $baseUrl, array $excludeAttrs): array {
$dom = new DOMDocument();
libxml_use_internal_errors(true);
// 關鍵: 加入 XML 聲明防止中文亂碼,LIBXML_HTML_NOIMPLIED 防止自動補全 html/body
$dom->loadHTML('' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$images = [];
// 查找所有 img
foreach ($xpath->query('//img') as $img) {
// 檢查排除屬性
$excluded = false;
foreach ($excludeAttrs as $attr) {
if ($img->hasAttribute($attr)) { $excluded = true; break; }
}
if ($excluded) continue;
$src = $img->getAttribute('src');
if (empty($src)) continue;
// 轉換 URL
$absoluteUrl = toAbsoluteUrl($src, $baseUrl);
// 記錄
$images[] = ['original' => $src, 'absolute' => $absoluteUrl];
// 修改 DOM
$img->setAttribute('src', $absoluteUrl);
}
// 輸出 HTML (保持原始格式盡可能一致)
$newHtml = $dom->saveHTML();
// saveHTML 可能添加 ,若用 NOIMPLIED 則不會
return ['html' => $newHtml, 'images' => $images];
}
function toAbsoluteUrl(string $src, string $baseUrl): string {
if (preg_match('/^(?:https?:)?\/\//i', $src) || preg_match('/^[a-z]+:/i', $src)) return $src;
$baseUrl = rtrim($baseUrl, '/');
return $baseUrl . (str_starts_with($src, '/') ? $src : '/' . ltrim($src, '/'));
}
性能對比與選型建議
| 方案 | 速度 | 健壯性 | 功能性 | 適用場景 |
| 正則 (preg_replace) | 極快 | 中 (易受異常 HTML 影響) | 基礎提取替換 | 結構固定、高性能、簡單邏輯 |
| DOMDocument + XPath | 中等 | 極高 (標準解析器) | 完整 DOM 操作 | 複雜 HTML、需修改屬性/結構、生產環境 |
| symfony/dom-crawler | 中等 | 極高 | CSS 選擇器、鏈式 API | 現代項目、採集器、測試 |
常見坑點避坑
- src 值包含引號/特殊字符: 正則用
(["\'])(.*?)\1 捕獲引號並反向引用,比 src=["\']([^"\']*)["\'] 更可靠。
- 跨行屬性: 正則加
s 修飾符 (DOTALL) 讓 . 匹配換行,或用 [^>]* 替代 .*?。
- 編碼問題:
loadHTML 前必加 ,否則中文會被實體化為 &#xxx;。
- 遞歸替換位置偏移: 多次替換時按
PREG_OFFSET_CAPTURE 獲取位置,**倒序替換** 或用 preg_replace_callback 單次處理。