我正在编写一个在
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
将环境文档的源位置分配给解析结果。我想为它分配文档的实际来源,以便相关链接正确解析。
现在我能想到的唯一解决方法是:
<base>
元素,这可能会干扰实际 HTML 源代码中存在的 <base>
标签document.implementation.createHTMLDocument()
然后.write()
,这给了我一个空白源位置的文档,其中相关链接至少没有被错误地解析(但根本不会被解析)。除了这在用户脚本中不起作用:它抛出一个SecurityError
.Proxy
拦截对 href
属性的访问,这似乎太重量级以至于无法轻松地适应用户脚本我也意识到从
.text()
获得的Unicode文本解析HTML会绕过HTML编码检测算法。我自己可以忍受,因为我感兴趣的网站专门使用标头中正确表示的 UTF-8,但这也是一个应该注意的缺陷。理想情况下,应该直接从 Blob
甚至 ReadableStream
. 解析 HTML 文档
是否有更好的方法来完成我想要的?
而不是使用
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。
如果您可以将基本元素注入到 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 实现。
一个解决方案可能是使用
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 或其他资源的任何特权访问。