直接str_replace会出错,因其无法区分url上下文,易误替子域名、js/css中的链接或破坏查询参数;应优先用domdocument精准修改href属性,并注意编码、选项设置及js动态内容的边界匹配。

为什么直接 str_replace 会出错
因为友情链接通常混在 HTML 的 <a></a> 标签里,而旧链接可能出现在 href、data-url、注释甚至 JS 字符串中。用 str_replace 硬替,容易把 https://old.com/abc 替成 https://new.com/abc,结果把 https://old.com/abc123 也一起错了——它根本不是你要换的那个友链。
更常见的是:替换后 href="https://new.com" 缺少末尾斜杠或协议不一致,导致相对路径解析异常;或者旧链接带查询参数(如 ?ref=friend),新链接没保留,被当成不同 URL 处理。
- 优先用 DOM 解析,而非正则或字符串替换
- 只改明确标记为友情链接的
<a></a>(比如父容器有class="friend-links"或固定 ID) - 保留原
href中的路径、查询参数、锚点,只换域名和协议部分
用 DOMDocument 安全批量修改 href 属性
DOMDocument 能准确识别标签结构,避免误伤。关键是别用 loadHTML() 直接加载含中文或不规范 HTML 的页面——它会静默修正、丢节点。必须设置选项:
$dom = new DOMDocument(); libxml_use_internal_errors(true); // 忽略警告 $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
然后定位友情链接区域(例如所有在 <div id="links"> 内的 <code><a></a>):
$xpath = new DOMXPath($dom);
$links = $xpath->query('//div[@id="links"]//a[@href]');
foreach ($links as $link) {
$href = $link->getAttribute('href');
if (preg_match('#^https?://old-site\.com(/.*)?$#i', $href, $m)) {
$newHref = 'https://new-site.com' . ($m[1] ?? '');
$link->setAttribute('href', $newHref);
}
}
$html = $dom->saveHTML();
- 用
preg_match而非strpos判断域名,防止匹配到子域名(如blog.old-site.com) -
LIBXML_HTML_NOIMPLIED阻止自动补,避免污染原始结构 - 替换前检查
$link->hasAttribute('href'),有些<a name="xxx"></a>没 href,硬取会报 Notice
处理 JS 中的链接或动态生成内容
如果友情链接是通过 JS 注入的(比如 document.write('<a href="...">')</a> 或 Vue/React 渲染),DOMDocument 无法解析。这时得用正则,但必须加边界保护:
错误写法:str_replace('old-site.com', 'new-site.com', $html) —— 会把 CSS 里的 background: url(old-site.com/icon.png) 也换了。
正确做法:只匹配 JS 字符串中明确用于跳转的链接,例如:
$html = preg_replace_callback(
'/href\s*=\s*[\'"]([^\'"]*old-site\.com[^\'"]*)[\'"]/i',
function ($m) {
return 'href="' . preg_replace('#^https?://old-site\.com#i', 'https://new-site.com', $m[1]) . '"';
},
$html
);
- 这个正则只抓
href=后紧跟引号包裹的 URL,避开 JS 变量名、注释、CSS - 若网站用了单引号、双引号、反引号混用,需分别写三组
preg_replace_callback - JS 中用模板字符串(
`https://old-site.com${path}`)的情况,正则失效,必须改源码或加构建时替换
上线前必须验证的三个细节
批量替换后,最容易漏掉的是:
-
rel属性是否被意外清空(如原为rel="nofollow",替换后变成rel="")——检查setAttribute是否覆盖了全部属性 - HTTPS 协议强制升级后,页面里若有 HTTP 的图片或脚本,会触发混合内容警告,浏览器可能拦截
- 部分友情链接带 UTM 参数(
?utm_source=friend&utm_medium=link),替换时若用parse_url拆解再拼,要注意http_build_query对特殊字符的编码差异
最稳妥的方式:先对一个典型页面跑完整流程,用 diff 工具对比替换前后 HTML,重点盯 <a></a> 标签块——肉眼比正则更容易发现多替、少替、属性错位。











