You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PHP爬虫获取网页加载时关联的外部域名?

优化PHP页面关联域名检测爬虫的实现方案

你的现有实现只覆盖了<link>标签里的CSS资源,漏了很多其他类型的外部资源,而且路径拼接、去重逻辑都有问题,下面是更完善的实现思路和代码示例:

现有代码的核心问题

  • 仅处理<link>标签的CSS,忽略了<script>、<img>、<iframe>等标签的外部资源
  • 相对路径拼接错误:直接用$url."/".$attribute会导致重复斜杠(比如https://example.com//css/style.css)
  • 没有去重机制,会重复爬取相同资源,浪费资源
  • 未解析CSS/JS内部引用的外部域名(比如CSS里background-image: url(https://cdn.example.com/img.jpg))

更优实现思路

  • 全面提取资源:遍历所有可能加载外部资源的HTML标签,同时解析内联CSS/JS里的资源引用
  • 正确处理URL:用parse_url拆解原URL,结合路径逻辑生成绝对路径,避免路径错误
  • 去重机制:维护已爬取URL集合和已检测域名集合,避免重复操作
  • 高效爬取:静态资源先用HEAD请求确认可用性,再发起GET;可考虑多进程/异步请求提升速度
  • 深度解析:递归解析CSS里的url(),以及JS里的动态加载逻辑(比如fetch、import)

改进后的代码示例

<?php
// 已爬取URL集合,避免重复爬取
$crawledUrls = [];
// 已检测到的域名集合
$detectedDomains = [];

// 获取页面内容(支持HEAD/GET)
function fetchUrl($url, $method = 'GET') {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 生产环境建议开启验证
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
}

// 将相对URL转为绝对URL
function resolveAbsoluteUrl($baseUrl, $relativeUrl) {
    $base = parse_url($baseUrl);
    $relative = parse_url($relativeUrl);

    // 如果已经是绝对URL,直接返回
    if (!empty($relative['scheme'])) {
        return $relativeUrl;
    }

    // 处理基础路径
    $basePath = $base['path'] ?? '/';
    if (substr($basePath, -1) !== '/') {
        $basePath = dirname($basePath) . '/';
    }
    // 拼接相对路径并清理冗余
    $absolutePath = $basePath . ($relative['path'] ?? '');
    $absolutePath = preg_replace('#(/\.?/)#', '/', $absolutePath);
    $absolutePath = preg_replace('#/(?!\.\./)[^/]+/\.\./#', '/', $absolutePath);

    // 构建完整URL
    return $base['scheme'] . '://' . $base['host'] . $absolutePath . (isset($relative['query']) ? '?' . $relative['query'] : '');
}

// 提取HTML中的所有资源URL
function extractHtmlResources($html, $baseUrl) {
    $dom = new DOMDocument();
    libxml_use_internal_errors(true);
    $dom->loadHTML($html);
    libxml_clear_errors();

    $resources = [];
    // 处理各类资源标签
    $tagAttrMap = [
        'script' => 'src',
        'link' => 'href',
        'img' => 'src',
        'iframe' => 'src',
        'video' => 'src',
        'audio' => 'src'
    ];

    foreach ($tagAttrMap as $tag => $attr) {
        $elements = $dom->getElementsByTagName($tag);
        foreach ($elements as $elem) {
            $url = $elem->getAttribute($attr);
            if (!empty($url)) {
                $resources[] = resolveAbsoluteUrl($baseUrl, $url);
            }
        }
    }

    // 处理内联CSS中的资源
    $styleElements = $dom->getElementsByTagName('style');
    foreach ($styleElements as $style) {
        $cssResources = extractCssResources($style->nodeValue, $baseUrl);
        $resources = array_merge($resources, $cssResources);
    }

    return $resources;
}

// 提取CSS中的资源URL
function extractCssResources($css, $baseUrl) {
    $resources = [];
    preg_match_all('/url\(["\']?(.*?)["\']?\)/i', $css, $matches);
    foreach ($matches[1] as $url) {
        if (!empty($url) && !str_starts_with($url, 'data:')) {
            $resources[] = resolveAbsoluteUrl($baseUrl, $url);
        }
    }
    return $resources;
}

// 提取URL对应的域名
function getDomainFromUrl($url) {
    $parsed = parse_url($url);
    return $parsed['host'] ?? '';
}

// 递归爬取资源并检测域名
function crawlResource($url) {
    global $crawledUrls, $detectedDomains;

    // 跳过已爬取的URL
    if (in_array($url, $crawledUrls)) {
        return;
    }
    $crawledUrls[] = $url;

    echo "爬取中: {$url}\n";
    $content = fetchUrl($url);
    if (empty($content)) {
        return;
    }

    // 记录当前URL的域名
    $domain = getDomainFromUrl($url);
    if (!empty($domain) && !in_array($domain, $detectedDomains)) {
        $detectedDomains[] = $domain;
    }

    // 根据内容类型处理后续爬取
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_exec($ch);
    $mime = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);

    if (str_contains($mime, 'text/html')) {
        // HTML页面,提取所有资源继续爬取
        $resources = extractHtmlResources($content, $url);
        foreach ($resources as $resource) {
            crawlResource($resource);
        }
    } elseif (str_contains($mime, 'text/css')) {
        // CSS文件,提取内部资源继续爬取
        $cssResources = extractCssResources($content, $url);
        foreach ($cssResources as $resource) {
            crawlResource($resource);
        }
    }
    // 可扩展:解析JS中的动态资源加载逻辑
}

// 主入口函数
function startCrawl($domain) {
    global $detectedDomains;
    // 统一处理输入的域名,补全协议
    $baseUrl = 'https://' . ltrim($domain, 'https://http://');
    crawlResource($baseUrl);

    echo "\n检测到的关联域名:\n";
    foreach ($detectedDomains as $domain) {
        echo "- {$domain}\n";
    }
}

// 示例调用
startCrawl('example.com');
?>

额外优化建议

  • 增加JS解析:可以借助PHP的V8扩展或第三方库,解析JS中的fetch、import等动态资源加载逻辑
  • 限制爬取深度:设置最大递归深度参数,避免无限爬取
  • 缓存机制:对已爬取的资源进行本地缓存,重复检测时直接复用结果
  • 并发爬取:使用curl_multi或多进程实现并发请求,大幅提升爬取速度

内容的提问来源于stack exchange,提问作者Cyberduck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 19:31:26