<?php
function extractLinks(string $url, array $options = []): array {
$opts = array_merge([
'timeout' => 10,
'user_agent' => 'Mozilla/5.0 (compatible; LinkBot/1.0)',
'max_redirects' => 5,
'follow_location' => true,
], $options);
$ctx = stream_context_create([
'http' => [
'timeout' => $opts['timeout'],
'user_agent' => $opts['user_agent'],
'max_redirects' => $opts['max_redirects'],
'follow_location' => $opts['follow_location'],
'ignore_errors' => true, // 取得錯誤頁面內容
],
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false], // 開發測試用,生產請驗證
]);
$html = @file_get_contents($url, false, $ctx);
if ($html === false) {
throw new RuntimeException("無法取得內容: $url");
}
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML('' . $html); // 強制 UTF-8
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$links = $xpath->query('//a[@href]');
$baseUrl = $url; // 可改用 標籤或最終 URL
$result = [];
foreach ($links as $a) {
$href = $a->getAttribute('href');
$text = trim($a->textContent);
// 過濾空、錨點、javascript:, mailto:, tel:
if ($href === '' || $href[0] === '#' || preg_match('/^(javascript|mailto|tel):/i', $href)) continue;
// 相對轉絕對
$absolute = resolveUrl($baseUrl, $href);
$result[] = [
'url' => $absolute,
'text' => $text,
'title' => $a->getAttribute('title'),
'rel' => $a->getAttribute('rel'),
];
}
return $result;
}
function resolveUrl(string $base, string $relative): string {
$baseParts = parse_url($base);
if (strpos($relative, '://') !== false) return $relative; // 已是絕對
if ($relative[0] === '/') {
return $baseParts['scheme'] . '://' . $baseParts['host'] . $relative;
}
// 相對路徑
$basePath = dirname($baseParts['path'] ?? '/');
return $baseParts['scheme'] . '://' . $baseParts['host'] . $basePath . '/' . $relative;
}
?>
<?php
// Swoole 協程並發 (需 Swoole 擴展)
use Swoole\Coroutine;
use function Swoole\Coroutine\run;
$urls = ['https://a.com', 'https://b.com', 'https://c.com'];
run(function () use ($urls) {
$chan = new Swoole\Coroutine\Channel(count($urls));
foreach ($urls as $url) {
Coroutine::create(function () use ($url, $chan) {
try {
$links = extractLinks($url);
$chan->push(['url'=>$url, 'links'=>$links, 'error'=>null]);
} catch (Throwable $e) {
$chan->push(['url'=>$url, 'links'=>[], 'error'=>$e->getMessage()]);
}
});
}
$results = [];
for ($i=0; $ipop();
}
print_r($results);
});
?>
JS 渲染頁面、SPA、無限滾動、動態載入需瀏覽器引擎。PHP 可透過 `php-webdriver` 驅動 Chrome/Firefox Headless,或呼叫 Node.js 微服務。
擷取超連結核心:**DOMDocument 解析 HTML (非正則)、相對轉絕對 URL、過濾無效連結**。採集系統需具備**並發控制、頻率限制、錯誤重試、法律合規**。JS 重度頁面改用 Headless Browser。