如何将 HTML 解析为具有我选择的源位置的 DOM 树?

问题描述 投票:0回答:3

我正在编写一个在

https://example.net
上运行的用户脚本,并从
fetch
发出
https://example.com
请求 HTML 文档,我想将其解析为 HTML DOM 树。

fetch
API 只给我原始的 HTML 源代码。我可以使用
DOMParser
自己解析它,但是我遇到了相关链接的问题。假设来自
https://example.com
的文档包含如下内容:

<!DOCTYPE html>
<html>
  <head>
  <body>
    <p> <a href="/foo">hello!</a>

如果我获得该

body > p > a
元素的 DOM 节点并读取它的
href
属性,我获得的值将是
https://example.net/foo
。这是因为
DOMParser
将环境文档的源位置分配给解析结果。我想为它分配文档的实际来源,以便相关链接正确解析。

现在我能想到的唯一解决方法是:

  • 在 DOM 树中注入一个
    <base>
    元素,这可能会干扰实际 HTML 源代码中存在的
    <base>
    标签
  • 使用
    document.implementation.createHTMLDocument()
    然后
    .write()
    ,这给了我一个空白源位置的文档,其中相关链接至少没有被错误地解析(但根本不会被解析)。除了这在用户脚本中不起作用:它抛出一个
    SecurityError
    .
  • 使用
    Proxy
    拦截对
    href
    属性的访问,这似乎太重量级以至于无法轻松地适应用户脚本
  • 包括用户态 HTML 解析器和 DOM 实现,这又显得太繁琐

我也意识到从

.text()
获得的Unicode文本解析HTML会绕过HTML编码检测算法。我自己可以忍受,因为我感兴趣的网站专门使用标头中正确表示的 UTF-8,但这也是一个应该注意的缺陷。理想情况下,应该直接从
Blob
甚至
ReadableStream
.

解析 HTML 文档

是否有更好的方法来完成我想要的?

javascript html domparser
3个回答
2
投票

而不是使用

fetch
,使用
XMLHttpRequest
,它具有将HTML解析为Document
内置功能。

您必须在调用

"document"
之后但在调用
responseType
之前,通过将字符串
XMLHttpRequest
分配给
open()
对象的
send()
属性来显式请求文档。

const xhr = new XMLHttpRequest();
xhr.onload = () => {
  console.log(
    Array.from(xhr.responseXML.links).map(({ href }) => href)
  );
}
xhr.open("GET", "https://example.com");
xhr.responseType = "document";
xhr.send();

在我的测试中,相对 URL 会根据源文档转换为绝对 URL。


0
投票

如果您可以将基本元素注入到 DOM 树中,那将是最简单的方法。
但是,如果想到另一种可能的方法,您可以使用 URL 对象基于文档的基本 URL 构造一个新的绝对 URL。例如-

const base = new URL('https://example.com');
const html = '<!DOCTYPE html><html><body><p><a href="/foo">hello!</a></p></body></html>';
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const link = doc.querySelector('a');
const href = link.href;
const absUrl = new URL(href, base).href;
console.log(absUrl); // output: "https://example.com/foo"

通过这种方式,您可以确保正确设置相关链接,而无需将基本元素暗示到 DOM 树中或使用用户级 HTML 解析器和 DOM 实现。


-1
投票

一个解决方案可能是使用

fetch
API 来检索 HTML 文档,然后使用
DOMImplementation.createHTMLDocument()
方法创建一个具有指定基本 URL 的新 HTML 文档对象。

这是此解决方案的示例实现:

async function fetchAndParseHTML(url) {
  const response = await fetch(url);
  const text = await response.text();

  const parser = new DOMParser();
  const htmlDoc = parser.parseFromString(text, 'text/html');

  const base = htmlDoc.createElement('base');
  base.href = url;
  htmlDoc.head.appendChild(base);

  return htmlDoc;
}

此函数从指定的 URL 获取 HTML 文档,创建一个新的

DOMParser
对象并使用它将文档解析为
HTMLDocument
对象。然后,它创建一个新的
base
元素并将其附加到文档的
head
元素,并将 href 属性设置为原始文档的 URL。

这样,文档中的任何相关链接都将相对于原始文档的 URL 进行解析,而不是环境文档的 URL。

然后您可以使用此函数来获取和解析 HTML 文档,如下所示:

const htmlDoc = await fetchAndParseHTML('https://example.com/some-page.html');

然后像这样访问链接的 href 属性:

const link = htmlDoc.querySelector('a');
const href = link.href; // will be https://example.com/foo, not https://example.net/foo

这个解决方案应该在用户脚本中工作,因为它不需要对 DOM 或其他资源的任何特权访问。

© www.soinside.com 2019 - 2024. All rights reserved.