如何解决克隆网站内容时CSS/JS资源路径加载失败的问题
嘿,这个问题我太熟了!之前帮朋友做类似的网站镜像工具时就踩过这个坑——根相对路径的资源会默认指向你的域名www.example.com,而不是目标网站www.google.com,所以肯定加载失败。下面给你几个实用的解决思路,从简单到可靠都有:
方案1:直接替换根路径为目标网站的绝对路径
这是最省心的办法,拿到目标网站的HTML后,把所有以/开头的资源路径(CSS、JS、图片、甚至链接)前面拼接上目标网站的完整域名。
给你改好的代码参考:
$targetDomain = 'https://www.google.com'; $url = $_GET['url']; if(isset($url) && $url !== '') { // 模拟浏览器请求,避免被目标网站反爬拦截 $opts = [ 'http' => [ 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\r\n" ] ]; $context = stream_context_create($opts); $html = file_get_contents($url, false, $context); // 替换href和src属性里的根路径 $pattern = '/(href|src)="\/(.*?)"/i'; $replacement = '$1="' . $targetDomain . '/$2"'; $html = preg_replace($pattern, $replacement, $html); // 处理CSS样式里的url('/xxx.png')这种情况(单引号、双引号、无引号三种情况) $html = preg_replace('/url\(\'\/(.*?)\'\)/i', 'url(\'' . $targetDomain . '/$1\')', $html); $html = preg_replace('/url\("\/(.*?)"\)/i', 'url("' . $targetDomain . '/$1")', $html); $html = preg_replace('/url\(\/(.*?)\)/i', 'url(' . $targetDomain . '/$1)', $html); echo $html; }
这个方案的好处是不用在自己服务器存任何资源,所有资源直接从目标网站加载。不过要注意正则可能会有遗漏,比如某些特殊写法的路径,但大部分场景下足够用了。
方案2:做资源代理,让所有请求走你的域名
如果不想让用户直接请求目标网站(比如怕被反爬封IP,或者想加缓存),可以在你的服务器上写一个代理脚本,把根路径的请求转发到目标网站。
步骤1:先写代理脚本proxy.php:
$targetDomain = 'https://www.google.com'; $resourcePath = $_GET['path']; // 安全校验,防止恶意请求(比如遍历服务器文件) if(strpos($resourcePath, '..') !== false || empty($resourcePath)) { exit('Invalid resource request'); } $fullUrl = $targetDomain . '/' . $resourcePath; // 获取目标资源的响应头,比如Content-Type,保证浏览器能正确解析 $headers = get_headers($fullUrl, 1); if(isset($headers['Content-Type'])) { header('Content-Type: ' . $headers['Content-Type']); } // 模拟浏览器请求资源 $opts = [ 'http' => [ 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\r\n" ] ]; $context = stream_context_create($opts); echo file_get_contents($fullUrl, false, $context);
步骤2:修改主脚本的路径替换逻辑:
$url = $_GET['url']; if(isset($url) && $url !== '') { $opts = [ 'http' => [ 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\r\n" ] ]; $context = stream_context_create($opts); $html = file_get_contents($url, false, $context); // 把根路径替换成代理脚本的路径 $pattern = '/(href|src)="\/(.*?)"/i'; $replacement = '$1="/proxy.php?path=$2"'; $html = preg_replace($pattern, $replacement, $html); // 同样处理CSS里的url() $html = preg_replace('/url\(\'\/(.*?)\'\)/i', 'url(\'/proxy.php?path=$1\')', $html); $html = preg_replace('/url\("\/(.*?)"\)/i', 'url("/proxy.php?path=$1")', $html); $html = preg_replace('/url\(\/(.*?)\)/i', 'url(/proxy.php?path=$1)', $html); echo $html; }
这个方案的优势是所有资源请求都走你的域名,避免跨域问题,还能在代理脚本里加缓存逻辑,重复请求的资源不用每次都去目标网站拉取。
方案3:用DOM解析替换路径(最可靠)
如果目标网站的HTML结构比较复杂,正则容易漏替换,那用PHP的DOMDocument来解析HTML会更准确,能精准找到所有带路径的元素和属性。
代码参考:
$targetDomain = 'https://www.google.com'; $url = $_GET['url']; if(isset($url) && $url !== '') { $opts = [ 'http' => [ 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\r\n" ] ]; $context = stream_context_create($opts); $html = file_get_contents($url, false, $context); // 初始化DOM解析器,忽略HTML语法错误(很多网站的HTML不规范) $dom = new DOMDocument(); libxml_use_internal_errors(true); $dom->loadHTML($html); libxml_clear_errors(); // 处理所有带src和href的元素(img、script、link、a等) foreach($dom->getElementsByTagName('*') as $elem) { // 替换src属性 if($elem->hasAttribute('src')) { $src = $elem->getAttribute('src'); // 判断是不是根相对路径(以/开头,但不是//开头的绝对路径) if(strpos($src, '/') === 0 && strpos($src, '//') !== 0) { $elem->setAttribute('src', $targetDomain . $src); } } // 替换href属性 if($elem->hasAttribute('href')) { $href = $elem->getAttribute('href'); if(strpos($href, '/') === 0 && strpos($href, '//') !== 0) { $elem->setAttribute('href', $targetDomain . $href); } } } // 处理style标签里的url() foreach($dom->getElementsByTagName('style') as $styleTag) { $content = $styleTag->nodeValue; $content = preg_replace('/url\(\'\/(.*?)\'\)/i', 'url(\'' . $targetDomain . '/$1\')', $content); $content = preg_replace('/url\("\/(.*?)"\)/i', 'url("' . $targetDomain . '/$1")', $content); $content = preg_replace('/url\(\/(.*?)\)/i', 'url(' . $targetDomain . '/$1)', $content); $styleTag->nodeValue = $content; } // 处理内联style属性里的url() foreach($dom->getElementsByTagName('*') as $elem) { if($elem->hasAttribute('style')) { $style = $elem->getAttribute('style'); $style = preg_replace('/url\(\'\/(.*?)\'\)/i', 'url(\'' . $targetDomain . '/$1\')', $style); $style = preg_replace('/url\("\/(.*?)"\)/i', 'url("' . $targetDomain . '/$1")', $style); $style = preg_replace('/url\(\/(.*?)\)/i', 'url(' . $targetDomain . '/$1)', $style); $elem->setAttribute('style', $style); } } echo $dom->saveHTML(); }
这种方式虽然代码多一点,但几乎不会漏替换任何路径,适合复杂的网站。
最后提醒
- 目标网站可能有反爬机制,一定要加
User-Agent模拟浏览器请求,不然很容易被封IP; - 如果要克隆大型网站,建议加缓存机制,避免频繁请求目标网站;
- 注意版权问题,克隆别人的网站可能涉及侵权,一定要确保合法合规。
内容的提问来源于stack exchange,提问作者Sigit Purnomo
相关产品推荐
相关产品推荐

