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

如何解析HTML为DOM树并正确关联其原始源地址?

解决用户脚本中跨域HTML解析的相对链接与编码问题

核心问题

在运行于https://example.net的用户脚本中,通过fetch获取https://example.com的HTML并使用DOMParser解析时,DOM元素的相对链接会被解析为当前页面(example.net)的绝对路径;同时直接用.text()获取内容会绕过HTML编码检测逻辑。

现有方案的不足

  • 注入<base>标签:可能与原HTML中已存在的<base>冲突
  • document.implementation.createHTMLDocument()配合.write():用户脚本中会抛出SecurityError
  • Proxy拦截属性访问:实现繁琐,维护成本高
  • 引入第三方解析库:体积大,没必要

推荐解决方案

方案1:手动修正所有相对链接(轻量高效)

利用URL API遍历解析后的DOM中所有带href/src属性的元素,手动将相对链接转换为目标域名的绝对路径。同时通过arrayBuffer()+Blob+FileReader组合处理编码,避免绕过浏览器的编码检测。

fetch('https://example.com')
  .then(response => {
    // 获取响应的Content-Type,用于正确解析编码
    const contentType = response.headers.get('Content-Type');
    return response.arrayBuffer().then(buffer => {
      const blob = new Blob([buffer], { type: contentType });
      // 用FileReader根据Content-Type指定的编码读取内容
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsText(blob);
      });
    });
  })
  .then(html => {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const baseUrl = new URL('https://example.com');

    // 批量修正所有带href的元素(a、link等)
    doc.querySelectorAll('[href]').forEach(elem => {
      const relativeHref = elem.getAttribute('href');
      try {
        elem.href = new URL(relativeHref, baseUrl).toString();
      } catch (e) {
        // 无效链接,跳过处理
      }
    });

    // 批量修正所有带src的元素(img、script等)
    doc.querySelectorAll('[src]').forEach(elem => {
      const relativeSrc = elem.getAttribute('src');
      try {
        elem.src = new URL(relativeSrc, baseUrl).toString();
      } catch (e) {
        // 无效链接,跳过处理
      }
    });

    // 此时doc中的所有链接均已正确解析为example.com的绝对路径
    const targetLink = doc.querySelector('body > p > a');
    console.log(targetLink.href); // 输出:https://example.com/foo
  })
  .catch(err => console.error('处理失败:', err));

方案2:利用隐藏iframe的独立文档环境(彻底解决)

创建隐藏iframe,在其独立文档中设置目标域名的<base>标签后写入HTML,让浏览器自动处理相对链接解析,同时天然支持编码检测。

// 创建隐藏iframe
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);

// 等待iframe加载空白文档
iframe.onload = () => {
  const iframeDoc = iframe.contentDocument;
  // 设置目标域名作为base,确保相对链接解析正确
  const baseTag = iframeDoc.createElement('base');
  baseTag.href = 'https://example.com';
  iframeDoc.head.appendChild(baseTag);

  fetch('https://example.com')
    .then(response => response.blob())
    .then(blob => {
      // 读取blob内容(自动处理编码)
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsText(blob);
      });
    })
    .then(html => {
      // 写入HTML到iframe文档
      iframeDoc.open();
      iframeDoc.write(html);
      iframeDoc.close();

      // 此时iframe文档中的链接已正确解析
      const targetLink = iframeDoc.querySelector('body > p > a');
      console.log(targetLink.href); // 输出:https://example.com/foo

      // 可将iframe中的DOM导入到当前文档使用
      const importedDom = document.importNode(iframeDoc.documentElement, true);
      // 处理完成后移除iframe
      document.body.removeChild(iframe);
    })
    .catch(err => {
      console.error('处理失败:', err);
      document.body.removeChild(iframe);
    });
};

// 加载空白文档初始化iframe
iframe.src = 'about:blank';

编码处理说明

两种方案均通过arrayBuffer()获取原始响应数据,再用Blob+FileReader组合读取内容,能自动根据响应头的Content-Type指定的编码解析HTML,避免直接用.text()时的编码检测绕过问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 19:00:19