HTML 与网页

导出的 HTML 换了域名,图片和链接为什么坏了

urljoin 的基准应该是原始页面 URL,不能随意补斜线把文件 URL 改成目录。此例只处理 a[href] 和 img[src],不覆盖 CSS、srcset 或 base 标签;重写 URL 也不是 HTML 安全净化。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 这篇先解决哪一个问题 urljoin 的基准应该是原始页面 URL,不能随意补斜线把文件 URL 改成目录。此例只处理 a[href] 和 img[src],不覆盖 CSS、srcset 或 base 标签;重写 URL 也不是 HTML 安全净化。 自包含的合成示例 依赖见导入列表。代码在内存中构造输入;运行记录单独提供,不代表对所有真实文件的保证。 from urllib.parse import urljoin, urlsplit from bs4 import BeautifulSoup def rewrite(markup, page_url): soup = BeautifulSoup(markup, "html.parser") for tag, attr in [("a", "href"), ("img", "src")]: for node in soup.find_all(tag): value = node.get(attr) if not value or value.startswith("#"): continue result = urljoin(page_url, value) if urlsplit(result).scheme not in {"https", "http"}: del node[attr] else: node[attr] = result return soup result = rewrite('<img src="logo.png"><a href="../guide/">Guide</a><a href="#top">Top</a><a href="javascript:alert(1)">Bad</a>', "https://example.com/docs/page.html") assert result.img["src"] == "https://example.com/docs/logo.png" assert result.find_all("a")[0]["href"] == "https://example.com/guide/" assert result.find_all("a")[1]["href"] == "#top" assert "href" not in result.find_all("a")[2].attrs 当企业将内部文档、Wiki 或旧版站点迁移至新域名时,最常遇到的“灵异事件”便是图片破损与链接跳转错误。这通常不是资源丢失,而是 解析上下文丢失 。浏览器解析相对路径(如 images/logo.png )时,严格遵循 RFC 3986 标准:优先查找 <base href="..."> 标签,若无则基于当前文档 URL 计算。若导出的静态 HTML 既未注入正确的 <base> 标签,又脱离了原始目录结构,所有相对引用都会指向新域名的根目录或当前页面路径,导致资源 404。 urljoin 与 base 标签的语义陷阱 urllib.parse.urljoin(base, url) 是处理链接重写的核心工具,但其行为与浏览器对 <base> 标签的解析存在微妙差异,这也是许多脚本“修了又坏”的根源。 urljoin 默认将 base 参数视为一个“文件路径”而非“目录”,除非它以 / 结尾。例如, urljoin("https://new.com/docs", "img.png") 的结果是 https://new.com/img.png (docs 被当作文件名丢弃),而非预期的 https://new.com/docs/img.png 。 相比之下,HTML <base> 标签在浏览器中的解析虽然也依赖尾部斜杠区分文件与目录,但对不规范输入的容错率略高。在程序化处理中,我们不能假设输入数据的规范性。因此,在调用 urljoin 前必须执行预处理:检查 scheme 合法性、强制追加目录后缀 / 、过滤空值。只有经过标准化的基准 URL,才能作为可靠输入。此外,对于已经是绝对路径(以 / 开头)或包含完整协议的链接, urljoin 会直接返回原值或仅替换主机名,这一特性使其比字符串拼接更安全,但也要求开发者明确区分“重写”与“保留”的边界。 合成示例与边界条件处理 以下代码展示如何安全地重写 HTML 中的资源链接。 注意:这是合成教学示例 ,旨在演示逻辑而非生产级实现。示例使用内存字符串模拟输入,避免了文件系统操作与网络请求,请在受控环境中验证逻辑。 验收标准与人工审查边界 自动化脚本能修复机械性的路径拼接问题,但无法替代对业务语义的判断。下表列出常见异常及其处理决策,帮助团队区分“代码可解”与“需人工介入”的场景: 不适用范围声明 :本方法仅适用于静态 HTML 属性重写。对于由 JavaScript 运行时生成的链接、Shadow DOM 内的引用、CSS background-image 中的 URL、或 Canvas/WebGL 纹理路径,BeautifulSoup 无法触及。这些情况必须在真实浏览器环境中验收,或使用 Playwright/Puppeteer 进行端到端视觉回归测试。代码演示不等于交付质量,最终验收必须以用户代理渲染结果为准。 参考资料与原厂来源 Beautiful Soup 官方文档 - Attributes: https://www.crummy.com/software/BeautifulSoup/bs4/doc/#attributes Python urllib.parse.urljoin 文档: https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urljoin RFC 3986 URI Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986 下一步行动建议:在你的 CI/CD 流水线中加入链接重写后的断言检查;对历史导出文档抽样验证资源可达性;若频繁遇到动态内容链接问题,评估引入无头浏览器进行视觉回归测试的必要性。记住,任何自动化修复都应被视为“辅助手段”,而非“银弹”。 具体异常的处理决定 观察 应做什么 不应推断 文件型页面 URL 保留原基准 不盲加末尾斜线 片段锚点 保持本页引用 不强制改成远端页面 脚本协议 示例中移除属性 完整 HTML 仍需安全净化 将这份检查清单用于文档翻译 网页文档的正文、链接、代码与结构要分别处理。把受保护的部分列入验收清单,再评估翻译后的可读性与页面展示。 OmniDoc(产品入口) 提供多格式文档翻译入口,官网介绍了 OCR、术语表与双语对照功能。对希望直接从文件开始处理的团队,这是一条值得用实际样本评估的路径。请使用有权上传的脱敏文件,并用本篇的检查项复核结果;这里不声称 OmniDoc 使用了本文的开源库或内部算法,也不以示例代替产品性能测试。 - 1 -
更新于 2026-10-08