標準 HTTP URL 由三部分組成:
協議://服務器/路徑?查詢參數#錨點 https://www.example.com/path/page.html?id=123#section
http|https|ftp|mailto|tel 等/ 開頭,包含字母數字及 -._~!$&'()*+,;=:@%? 開頭,key=value 形式,& 分隔/(http|https):\/\/([\w\-\.]+)(:\d+)?(\/[\w\/\-\.]*)?/i
/(http|https|ftp):\/\/([\w\-\.]+)(:\d+)?(\/[\w\/\-\.\?\=\&\%]*)?/i
$urlRegex = '/
(https?|ftp):\/\/ # 協議
(?:[^\s\/?#]+\.)+[^\s\/?#]+ # 域名 (至少一個點)
(?::\d+)? # 可選端口
(?:[\/?#][^\s]*)? # 路徑/查詢/錨點
/ix';
$rfc3986 = '/
[a-z][a-z0-9+\-.]*:// # scheme
(?:[^@\/\s]+@)? # user:pass@
(?:
\[[0-9a-f:]+\] | # IPv6
[^\/\s:?#]+ # domain/IPv4
)
(?::\d+)? # port
(?:[/?#][^\s]*)? # path?query#fragment
/ix';
function extractUrls($html, $baseUrl = '') {
$regex = '/(https?|ftp):\/\/[^\s\/?#].[^\s]*/i';
preg_match_all($regex, $html, $matches);
$urls = $matches[0] ?? [];
// 去重
$urls = array_unique($urls);
// 相對轉絕對(可選)
if ($baseUrl) {
$parsed = parse_url($baseUrl);
$base = $parsed['scheme'] . '://' . $parsed['host'];
foreach ($urls as &$url) {
if (strpos($url, '://') === false) {
$url = $base . ($url[0] === '/' ? '' : '/') . $url;
}
}
}
// 解析組件
$result = [];
foreach ($urls as $url) {
$parts = parse_url($url);
$result[] = [
'url' => $url,
'scheme' => $parts['scheme'] ?? '',
'host' => $parts['host'] ?? '',
'port' => $parts['port'] ?? '',
'path' => $parts['path'] ?? '',
'query' => $parts['query'] ?? '',
'fragment' => $parts['fragment'] ?? '',
];
}
return $result;
}
// 使用
$html = file_get_contents('https://example.com');
$links = extractUrls($html, 'https://example.com');
print_r($links);
function extractUrlsDom($html, $baseUrl = '') {
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
libxml_clear_errors();
$urls = [];
foreach ($dom->getElementsByTagName('a') as $a) {
$href = $a->getAttribute('href');
if ($href) $urls[] = $href;
}
foreach ($dom->getElementsByTagName('img') as $img) {
$src = $img->getAttribute('src');
if ($src) $urls[] = $src;
}
foreach ($dom->getElementsByTagName('link') as $link) {
$href = $link->getAttribute('href');
if ($href) $urls[] = $href;
}
foreach ($dom->getElementsByTagName('script') as $script) {
$src = $script->getAttribute('src');
if ($src) $urls[] = $src;
}
return array_unique($urls);
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 正則 | 速度快、無依賴、可提取 JS/JSON 中的 URL | 易誤匹配、難處理複雜編碼 | 純文本、腳本、API 響應、大規模掃描 |
| DOMDocument | 語義準確、自動處理實體編碼、可獲取屬性上下文 | 內存佔用大、嚴格模式下易報錯 | HTML 頁面結構化提取、需區分標簽類型 |
| 組合 | DOM 提取標簽屬性 + 正則提取內聯腳本/樣式 | 代碼較多 | 通用爬蟲、站點地圖生成 |
%xx 格式data:image/...;base64,... 非 HTTP URL,可選擇過濾parse_url + 基礎 URL 拼接提取網頁 URL,**首選 DOMDocument 解析標簽屬性**,輔以正則提取內聯腳本/樣式/JSON 中的鏈接。生產級爬蟲建議:DOMDocument + 正則 + 相對轉絕對 + 去重 + 異常處理 組合方案。