HTML 結構複雜、巢狀、屬性順序不固定,正則表達式極難完整處理。PHP 內建 DOMDocument + DOMXPath 是標準、健壯的解析工具。
<?php
$url = 'https://www.example.com';
$html = @file_get_contents($url); // @ 抑制警告,正式環境建議用 Guzzle/cURL
if ($html === false) {
die("無法取得網頁內容");
}
$dom = new DOMDocument();
// 避免 HTML5 標籤/實體報錯
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$links = $xpath->query('//a[@href]'); // 所有擁有 href 屬性的
$results = [];
foreach ($links as $link) {
$href = $link->getAttribute('href');
$text = trim($link->textContent);
if ($href !== '' && $href !== '#') {
// 轉為絕對網址
$absolute = (parse_url($href, PHP_URL_SCHEME) ? $href : rtrim($url, '/') . '/' . ltrim($href, '/'));
$results[] = ['url' => $absolute, 'text' => $text];
}
}
print_r($results);
<?php
function extractLinks(string $html, string $baseUrl): array
{
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$anchors = $xpath->query('//a[@href]');
$internal = [];
$external = [];
$assets = [];
$mailto = [];
$tel = [];
foreach ($anchors as $a) {
$href = trim($a->getAttribute('href'));
if ($href === '' || $href === '#' || str_starts_with($href, 'javascript:')) continue;
$text = trim($a->textContent);
$parsed = parse_url($href);
if ($parsed === false) continue; // 相對路徑 parse_url 會失敗
if (isset($parsed['scheme'])) {
// 絕對網址
$host = $parsed['host'] ?? '';
$baseHost = parse_url($baseUrl, PHP_URL_HOST);
if ($host === $baseHost) {
$internal[] = ['url' => $href, 'text' => $text];
} else {
$external[] = ['url' => $href, 'text' => $text];
}
} else {
// 相對路徑 -> 轉絕對
$absolute = rtrim($baseUrl, '/') . '/' . ltrim($href, '/');
$internal[] = ['url' => $absolute, 'text' => $text];
}
// 特殊協定
if (str_starts_with($href, 'mailto:')) $mailto[] = $href;
if (str_starts_with($href, 'tel:')) $tel[] = $href;
if (preg_match('/\.(css|js|png|jpg|jpeg|gif|svg|woff2?)$/i', $href)) $assets[] = $href;
}
return compact('internal', 'external', 'assets', 'mailto', 'tel');
}
// 使用
$links = extractLinks($html, 'https://www.example.com');
echo "站內連結: " . count($links['internal']) . "\n";
echo "外部連結: " . count($links['external']) . "\n";
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD) 減少記憶體| 問題 | 原因 | 解決 |
|---|---|---|
| 中文亂碼 | 網頁編碼非 UTF-8 | $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'auto'); 再 loadHTML |
| 載入極慢/記憶體爆炸 | 頁面過大 (數 MB) | 改用 XMLReader 或先用 regex 粗略擷取 <a 區塊 |
| 相對路徑轉換錯誤 | base href、./ ../ 處理 | 使用 League\Uri 或手動實作 RFC 3986 解析 |
| JavaScript 動態產生的連結抓不到 | DOMDocument 不執行 JS | 改用 Headless Chrome (Puppeteer/PHP-WebDriver) |
robots.txt 與網站服務條款User-Agent、Rate Limit (如 1 req/sec)掌握 DOMDocument + XPath,即可可靠地從任何靜態網頁提取結構化連結資料。