应使用 html 解析器(如 cheerio 或 parse5)而非正则替换 src 属性,以避免误改注释、脚本、data-*、srcset 等非目标内容;需校验路径前缀并单独处理 srcset 和 picture 中的响应式路径。

直接用正则替换 src 属性会炸掉整个 HTML
很多用户一上来就写 sed -i 's/src="\.\/images\//src="https:\/\/cdn.com\//g',结果发现注释里、<script></script> 里的字符串也被替了,甚至 data-src、srcset 全中招。HTML 不是纯文本,硬搜硬替等于在雷区扫盲。
常见错误现象:
-
<!-- <img src="./old.jpg"> -->变成<!-- <img src="https://cdn.com/old.jpg?x-oss-process=image/resize,p_40"> -->,但注释不该动 -
console.log('src="./old.jpg"')被误改,JS 报错 -
<img>被改,懒加载逻辑失效
用 cheerio 批量改多文件最省心
如果你要一次处理几十个 HTML 文件(比如静态站生成后统一换 CDN),cheerio 的 jQuery 风格 API 最直观,而且能天然跳过注释、脚本和 style 标签里的内容。
实操建议:
- 安装:
npm install cheerio - 只改真实 DOM 中的
src和href,不碰data-*或srcset(除非你明确要) - 路径判断必须加前缀检查:
if (src.startsWith('./') || src.startsWith('../')),避免把https://或/api/也替了 - 读取文件时加
{ xmlMode: false },否则含<svg xmlns="http://www.w3.org/2000/svg"></svg>的页面可能漏匹配<img>
示例片段(改单个字符串):
const cheerio = require('cheerio');
const html = '<p>@@##@@</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2900" title="Markdown to HTML"><img
src="https://img.php.cn/upload/skill/000/000/081/178938727468962.jpg" alt="Markdown to HTML" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill2900" title="Markdown to HTML" class="overflowclass">Markdown to HTML</a>
<p class="overflowclass">{"answer":"将 Markdown 转换为内嵌 CSS、样式精美的独立 HTML。完美适用于新闻简报、文档、报告及邮件模板。"}</p>
</div>
<a rel="nofollow" href="/xiazai/skill2900" title="Markdown to HTML" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>';
const $ = cheerio.load(html, { xmlMode: false });
$('img[src]').each((i, el) => {
const src = $(el).attr('src');
if (src && (src.startsWith('./') || src.startsWith('../'))) {
$(el).attr('src', src.replace(/^\.\//, 'https://cdn.example.com/'));
}
});
console.log($.html()); // <p>@@##@@</p>
用 parse5 更可控,适合需要保留原始格式的场景
cheerio 默认会“美化”HTML(比如把 <img src="./images/logo.png"> 自动补成 <img src="https://cdn.example.com/images/logo.png?x-oss-process=image/resize,p_40">),而 parse5 序列化时严格按输入格式还原,适合对空格、换行、自闭合标签有洁癖的项目。
关键差异点:
-
parse5不自动识别data-src,也不帮你过滤上下文——你得自己遍历节点、判断node.tagName和attr.name - 它不会把
<img>改成<img>,原始怎么写,输出就怎么写 - 不支持 CSS 选择器,得手写递归遍历,代码略长但逻辑更透明
示例核心逻辑(只改 <img src="x">、<script></script>、<link> 的路径):
const { parse, serialize } = require('parse5');
function walk(node) {
if (node.tagName && node.attrs) {
for (const attr of node.attrs) {
if ((attr.name === 'src' || attr.name === 'href') &&
(attr.value.startsWith('./') || attr.value.startsWith('../'))) {
attr.value = attr.value.replace(/^\.\//, 'https://cdn.example.com/');
}
}
}
if (node.childNodes) node.childNodes.forEach(walk);
}
const doc = parse(htmlString);
walk(doc);
const newHtml = serialize(doc);
别忘了 srcset 和 picture 里的路径
响应式站点里,光改 src 不够。如果用了 <picture></picture> 或 srcset,里面每个 URL 都得单独处理,否则高 DPI 设备或小屏仍加载旧路径。
容易被忽略的地方:
-
<img src="x">—— 每个逗号分隔的 URL 都要检查是否为相对路径 -
<source srcset="img/hero.webp" type="image/webp"></source>——srcset在<source></source>里也要扫 - 正则根本不敢碰
srcset,因为语法允许空格、括号、描述符,只有 AST 能安全解析
用 cheerio 处理 srcset 的最小改动:
$('img[srcset], source[srcset]').each((i, el) => {
const srcset = $(el).attr('srcset');
if (srcset) {
const newSrcset = srcset.replace(/(?:^|\s+)([^,\s]+)(?=\s+[0-9w]+)/g, (_, url) => {
return url.startsWith('./') ? url.replace(/^\.\//, 'https://cdn.example.com/') : url;
});
$(el).attr('srcset', newSrcset);
}
});
真正麻烦的不是改一行 src,而是确认哪些路径该动、哪些不该动,以及改完后是否还符合浏览器加载规则。尤其当 HTML 来自富文本编辑器(如 KindEditor、Quill),里面混着 data- 属性、内联样式、甚至 JS 字符串,这时候 AST 就不是“可选”,而是必须。

前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










