使用 JS 解析 HTML 字符串

Parse an HTML string with JS

提问人:stage 提问时间:5/14/2012 最后编辑:isherwoodstage 更新时间:10/25/2023 访问量:754839

问:

我想解析一个包含 HTML 文本的字符串。我想在 JavaScript 中做到这一点。

我尝试了纯 JavaScript HTML 解析器库,但它似乎解析了我当前页面的 HTML,而不是从字符串中解析。因为当我尝试下面的代码时,它会更改我页面的标题:

var parser = new HTMLtoDOM("<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>", document);

我的目标是从我像字符串一样阅读的 HTML 外部页面中提取链接。

你知道一个API来做到这一点吗?

javascript dom html 解析

评论

2赞 Rob W 5/14/2012
JavaScript DOMParser 访问、innerHTML 和其他属性的可能副本
1赞 Rob W 5/14/2012
链接副本上的方法从给定字符串创建 HTML 文档。然后,您可以使用阅读链接(甚至doc.links)。doc.getElementsByTagName('a')
0赞 Mike Lyons 3/28/2015
值得一提的是,如果你使用的是像 React 这样的框架.js那么可能有一些特定于框架的方法,例如:stackoverflow.com/questions/23616226/......
0赞 Leif Arne Storset 3/11/2020
这回答了你的问题吗?从文本 JavaScript 中剥离 HTML

答:

498赞 Florian Margaine 5/14/2012 #1

创建一个虚拟 DOM 元素并将字符串添加到其中。然后,您可以像任何 DOM 元素一样操作它。

var el = document.createElement( 'html' );
el.innerHTML = "<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>";

el.getElementsByTagName( 'a' ); // Live NodeList of your anchor elements

编辑:添加一个jQuery答案来取悦粉丝!

var el = $( '<div></div>' );
el.html("<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>");

$('a', el) // All the anchor elements

评论

12赞 stage 5/14/2012
请注意:使用此解决方案,如果我执行“alert(el.innerHTML)”,我将丢失<html>,<body>和<head>标签。
5赞 omninonsense 5/21/2015
@stage 我来晚了一点,但你应该能够用来保留和标签。document.createElement('html');<head><body>
5赞 symbiont 8/16/2017
看起来您正在将 HTML 元素放在 HTML 元素中
17赞 Justin 3/8/2019
我担心被投票作为最佳答案。下面的 parse() 解决方案更可重用且更优雅。
7赞 Leif Arne Storset 3/11/2020
安全说明:这将执行输入中的任何脚本,因此不适合不受信任的输入。
4赞 jmar777 5/14/2012 #2

如果你愿意使用jQuery,它有一些很好的工具,可以从HTML字符串创建分离的DOM元素。然后可以通过通常的方式查询这些,例如:

var html = "<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>";
var anchors = $('<div/>').append(html).find('a').get();

编辑 - 刚刚看到@Florian的答案是正确的。这基本上就是他所说的,但使用jQuery。

30赞 Mathieu 5/14/2012 #3
var doc = new DOMParser().parseFromString(html, "text/html");
var links = doc.querySelectorAll("a");

评论

4赞 Rob W 5/15/2012
为什么要加前缀?此外,正如链接的副本中提到的,它没有得到很好的支持,必须使用 polyfill 来实现。$text/html
2赞 Mathieu 5/15/2012
我从一个项目中复制了这一行,我习惯于在 javascript 应用程序(而不是库中)中用 $ 作为变量的前缀。只是为了与图书馆发生冲突。这不是很有用,因为几乎每个变量都有作用域,但它曾经是有用的。它还(也许)有助于轻松识别变量。
1赞 Jokester 4/25/2013
可悲的是,两者都不能在 chrome 中工作,这个 MDN 页面提供了解决方法。DOMParsertext/html
1赞 Leif Arne Storset 3/11/2020
安全说明:这将在没有任何浏览器上下文的情况下执行,因此不会运行任何脚本。它应该适用于不受信任的输入。
7赞 John Slegers 12/9/2013 #4

以下函数将返回:parseHTML


代码:

function parseHTML(markup) {
    if (markup.toLowerCase().trim().indexOf('<!doctype') === 0) {
        var doc = document.implementation.createHTMLDocument("");
        doc.documentElement.innerHTML = markup;
        return doc;
    } else if ('content' in document.createElement('template')) {
       // Template tag exists!
       var el = document.createElement('template');
       el.innerHTML = markup;
       return el.content;
    } else {
       // Template tag doesn't exist!
       var docfrag = document.createDocumentFragment();
       var el = document.createElement('body');
       el.innerHTML = markup;
       for (i = 0; 0 < el.childNodes.length;) {
           docfrag.appendChild(el.childNodes[i]);
       }
       return docfrag;
    }
}

如何使用 :

var links = parseHTML('<!doctype html><html><head></head><body><a>Link 1</a><a>Link 2</a></body></html>').getElementsByTagName('a');

评论

0赞 Sebastian Carroll 1/10/2014
我无法让它在 IE8 上工作。我在函数的第一行收到错误“对象不支持此属性或方法”。我不认为createHTMLDocument函数存在
0赞 John Slegers 1/14/2014
您的用例到底是什么?如果你只想解析 HTML,并且你的 HTML 是用于文档正文的,你可以执行以下操作:(1) var div=document.createElement(“DIV”);(2) div.innerHTML = 标记;(3) 结果 = div.childNodes;--- 这为您提供了子节点的集合,不仅可以在 IE8 中工作,甚至可以在 IE6-7 中工作。
0赞 Sebastian Carroll 1/23/2014
感谢您的替代选项,如果我需要再次执行此操作,我会尝试一下。不过现在,我使用了上面的 JQuery 解决方案。
0赞 Toothbrush 12/25/2016
@SebastianCarroll 请注意,IE8 不支持字符串上的方法。请参见 stackoverflow.com/q/2308134/3210837trim
3赞 John Slegers 12/29/2016
@Toothbrush : 在2017年初,IE8支持是否仍然适用?
414赞 Cilan 2/19/2014 #5

这很简单:

const parser = new DOMParser();
const htmlDoc = parser.parseFromString(txt, 'text/html');
// do whatever you want with htmlDoc.getElementsByTagName('a');

根据 MDN 的说法,要在 chrome 中执行此操作,您需要像这样解析为 XML:

const parser = new DOMParser();
const htmlDoc = parser.parseFromString(txt, 'text/xml');
// do whatever you want with htmlDoc.getElementsByTagName('a');

webkit 目前不支持它,您必须遵循 Florian 的答案,并且在大多数情况下,它无法在移动浏览器上运行。

编辑:现在得到广泛支持

评论

51赞 aendra 3/9/2016
值得一提的是,在2016年,DOMParser得到了广泛的支持。caniuse.com/#feat=xml-serializer
7赞 ceving 11/3/2017
值得注意的是,创建的文档中的所有相对链接都已断开,因为文档是通过继承 of 来创建的,这很可能与字符串的 URL 不同。documentURLwindow
3赞 Jack G 5/20/2018
值得注意的是,您应该调用一次,然后在脚本的其余部分重用相同的对象。new DOMParser
1赞 Justin 3/8/2019
下面的 parse() 解决方案更具可重用性,并且特定于 HTML。但是,如果您需要 XML 文档,这很好。
0赞 Shariq Musharaf 6/20/2019
如何在对话框或其他内容上显示此解析的网页?我无法找到解决方案
7赞 Joel 2/8/2015 #6

在 Chrome 和 Firefox 中解析 HTML 的最快方法是 Range#createContextualFragment:

var range = document.createRange();
range.selectNode(document.body); // required in Safari
var fragment = range.createContextualFragment('<h1>html...</h1>');
var firstNode = fragment.firstChild;

我建议创建一个帮助程序函数,该函数使用 createContextualFragment(如果可用),否则回退到 innerHTML。

基准:http://jsperf.com/domparser-vs-createelement-innerhtml/3

评论

0赞 Ry- 8/29/2015
请注意,像 (simple) 一样,这将执行 的 .innerHTML<img>onerror
1赞 Munawwar 10/6/2015
这样做的一个问题是,像 '<td>test</td>' 这样的 html 会忽略 document.body 上下文中的 td(并且只创建 'test' 文本节点)。OTOH,如果它在模板引擎内部使用,那么正确的上下文将可用。
0赞 Munawwar 10/6/2015
顺便说一句,IE 11 支持 createContextualFragment。
0赞 sea26.2 4/19/2019
问题是如何用JS解析 - 而不是Chrome或Firefox
5赞 Leif Arne Storset 3/11/2020
安全说明:这将执行输入中的任何脚本,因此不适合不受信任的输入。
42赞 Munawwar 10/25/2015 #7

编辑:下面的解决方案仅适用于HTML“片段”,因为html,head和body已被删除。我想这个问题的解决方案是 DOMParser 的 parseFromString() 方法:

const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");

对于 HTML 片段,此处列出的解决方案适用于大多数 HTML,但在某些情况下,它不起作用。

例如,尝试解析 。这不适用于 div.innerHTML 解决方案、DOMParser.prototype.parseFromString 或 range.createContextualFragment 解决方案。td 标签丢失,只剩下文本。<td>Test</td>

只有jQuery可以很好地处理这种情况。

所以未来的解决方案(MS Edge 13+)是使用模板标签:

function parseHTML(html) {
    var t = document.createElement('template');
    t.innerHTML = html;
    return t.content;
}

var documentFragment = parseHTML('<td>Test</td>');

对于较旧的浏览器,我已将 jQuery 的 parseHTML() 方法提取到一个独立的要点 - https://gist.github.com/Munawwar/6e6362dbdf77c7865a99

评论

0赞 Jeff Laughlin 9/30/2017
如果你想编写也适用于旧浏览器的向前兼容代码,你可以填充 <template> 标签。这取决于您可能还需要填充的自定义元素。事实上,您可能只想使用 Web 组件.js一次性填充自定义元素、模板、shadow dom、promise 和其他一些东西。
0赞 Luis Lobo 11/18/2022
哇。效率极高!
8赞 anthumchris 3/7/2019 #8
const parse = Range.prototype.createContextualFragment.bind(document.createRange());

document.body.appendChild( parse('<p><strong>Today is:</strong></p>') ),
document.body.appendChild( parse(`<p style="background: #eee">${new Date()}</p>`) );


只有父级(开头)中的有效子级 s 才会被解析。否则,可能会出现意外结果:NodeNodeRange

// <body> is "parent" Node, start of Range
const parseRange = document.createRange();
const parse = Range.prototype.createContextualFragment.bind(parseRange);

// Returns Text "1 2" because td, tr, tbody are not valid children of <body>
parse('<td>1</td> <td>2</td>');
parse('<tr><td>1</td> <td>2</td></tr>');
parse('<tbody><tr><td>1</td> <td>2</td></tr></tbody>');

// Returns <table>, which is a valid child of <body>
parse('<table> <td>1</td> <td>2</td> </table>');
parse('<table> <tr> <td>1</td> <td>2</td> </tr> </table>');
parse('<table> <tbody> <td>1</td> <td>2</td> </tbody> </table>');

// <tr> is parent Node, start of Range
parseRange.setStart(document.createElement('tr'), 0);

// Returns [<td>, <td>] element array
parse('<td>1</td> <td>2</td>');
parse('<tr> <td>1</td> <td>2</td> </tr>');
parse('<tbody> <td>1</td> <td>2</td> </tbody>');
parse('<table> <td>1</td> <td>2</td> </table>');

评论

11赞 Leif Arne Storset 3/11/2020
安全说明:这将执行输入中的任何脚本,因此不适合不受信任的输入。
-1赞 Den Nikitin 10/7/2020 #9
let content = "<center><h1>404 Not Found</h1></center>"
let result = $("<div/>").html(content).text()

内容: ,
结果:
<center><h1>404 Not Found</h1></center>"404 Not Found"

评论

0赞 Rene Koch 10/7/2020
这并不能回答任务。OP 想要提取链接。
5赞 Юрий Светлов 11/29/2020 #10

1 程

document.cloneNode()

性能是:

调用需要 ~0.22499999977299012 毫秒。document.cloneNode()

也许会更多。

var t0, t1, html;

t0 = performance.now();
   html = document.cloneNode(true);
t1 = performance.now();

console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.")

html.documentElement.innerHTML = '<!DOCTYPE html><html><head><title>Test</title></head><body><div id="test1">test1</div></body></html>';

console.log(html.getElementById("test1"));

2 路

document.implementation.createHTMLDocument()

性能是:

调用需要 ~0.14000000010128133 毫秒。document.implementation.createHTMLDocument()

var t0, t1, html;

t0 = performance.now();
html = document.implementation.createHTMLDocument("test");
t1 = performance.now();

console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.")

html.documentElement.innerHTML = '<!DOCTYPE html><html><head><title>Test</title></head><body><div id="test1">test1</div></body></html>';

console.log(html.getElementById("test1"));

3路

document.implementation.createDocument()

性能是:

调用需要 ~0.14000000010128133 毫秒。document.implementation.createHTMLDocument()

var t0 = performance.now();
  html = document.implementation.createDocument('', 'html', 
             document.implementation.createDocumentType('html', '', '')
         );
var t1 = performance.now();

console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.")

html.documentElement.innerHTML = '<html><head><title>Test</title></head><body><div id="test1">test</div></body></html>';

console.log(html.getElementById("test1"));

4 路

new Document()

性能是:

调用需要 ~0.13499999840860255 毫秒。document.implementation.createHTMLDocument()

  • 注意

ParentNode.append是2020年的实验技术。

var t0, t1, html;

t0 = performance.now();
//---------------
html = new Document();

html.append(
  html.implementation.createDocumentType('html', '', '')
);
    
html.append(
  html.createElement('html')
);
//---------------
t1 = performance.now();

console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.")

html.documentElement.innerHTML = '<html><head><title>Test</title></head><body><div id="test1">test1</div></body></html>';

console.log(html.getElementById("test1"));
0赞 danish ali 5/29/2021 #11

我不得不使用在 Angular NGX Bootstrap popover 的弹出窗口中解析的元素的 innerHTML。这是对我有用的解决方案。

public htmlContainer = document.createElement( 'html' );

in 构造函数

this.htmlContainer.innerHTML = ''; setTimeout(() => { this.convertToArray(); });

 convertToArray() {
    const shapesHC = document.getElementsByClassName('weekPopUpDummy');
    const shapesArrHCSpread = [...(shapesHC as any)];
    this.htmlContainer = shapesArrHCSpread[0];
    this.htmlContainer.innerHTML = shapesArrHCSpread[0].textContent;
  }

在 HTML 中

<div class="weekPopUpDummy" [popover]="htmlContainer.innerHTML" [adaptivePosition]="false" placement="top" [outsideClick]="true" #popOverHide="bs-popover" [delay]="150" (onHidden)="onHidden(weekEvent)" (onShown)="onShown()">
5赞 Daniel Kaplan 9/16/2021 #12

要在 node.js 中执行此操作,您可以使用 HTML 解析器,如 node-html-parser。语法如下所示:

import { parse } from 'node-html-parser';

const root = parse('<ul id="list"><li>Hello World</li></ul>');

console.log(root.firstChild.structure);
// ul#list
//   li
//     #text

console.log(root.querySelector('#list'));
// { tagName: 'ul',
//   rawAttrs: 'id="list"',
//   childNodes:
//    [ { tagName: 'li',
//        rawAttrs: '',
//        childNodes: [Object],
//        classNames: [] } ],
//   id: 'list',
//   classNames: [] }
console.log(root.toString());
// <ul id="list"><li>Hello World</li></ul>
root.set_content('<li>Hello World</li>');
root.toString();    // <li>Hello World</li>

评论

1赞 Rainb 7/26/2022
如果您不想依赖浏览器实现,即使在浏览器上,这也是最好的解决方案。无论您使用哪种浏览器,此实现的行为始终相同(现在并不重要),而且解析是用 javascript 本身而不是 c/c++ 完成的!
0赞 Daniel Kaplan 7/27/2022
谢谢@Rainb。但是,您如何在浏览器中使用该解决方案?
1赞 Rainb 7/28/2022
喜欢这个(await import("https://cdn.skypack.dev/node-html-parser")).default('<ul id="list"><li>Hello World</li></ul>').firstChild.structure
0赞 Daniel Kaplan 7/29/2022
我从来不知道这是一种选择。你能用任何节点库做到这一点吗,还是因为这个库不使用任何仅限节点的代码?
1赞 Rainb 7/29/2022
如果它需要来自节点的任何内容,如 TLS、HTTP、NET、FS,那么它可能无法在浏览器中工作。但它在 deno 中也行不通。因此,只需寻找 deno 兼容的软件包即可。
6赞 Rafael Mori 12/10/2021 #13

我认为最好的方法是像这样使用这个 API:

//Table string in HTML format
const htmlString = '<table><tbody><tr><td>Cell 1</td><td>Cell 2</td></tr></tbody></table>';

//Parse using DOMParser native way
const parser = new DOMParser();
const $newTable = parser.parseFromString(htmlString, 'text/html');

//Here you can select parts of your parsed html and work with it
const $row = $newTable.querySelector('table > tbody > tr');

//Here i'm printing the number of columns (2)
const $containerHtml = document.getElementById('containerHtml');
$containerHtml.innerHTML = ['Your parsed table have ', $row.cells.length, 'columns.'].join(' ');
<div id="containerHtml"></div>

0赞 Weilory 12/29/2021 #14
function parseElement(raw){
    let el = document.createElement('div');
    el.innerHTML = raw;
    let res = el.querySelector('*');
    res.remove();
    return res;
}

注意:原始字符串不应超过 1 个元素

1赞 Khalil Medjahed 10/15/2022 #15
const html =
`<script>
    alert('👋 there ! Wanna grab a 🍺'); 
</script>`;

const scriptEl = document.createRange().createContextualFragment(html);
parent.append(scriptEl);

我找到了这个解决方案,我认为这是最好的解决方案,它解析 HTML 并在其中执行脚本。

0赞 Shankar Balaji U 10/25/2023 #16

我使用了我从这个博客中引用的 DOMParser 类。

这将返回一个 HTMLCollection 对象,我们可以在其中将元素作为 DOM 元素本身访问。也很容易在HTML文档中插入代码document.body.append(...parseHTML(html_string));

const parseHTML = (htmlString) => {
    const parser = new DOMParser();
    const page = parser.parseFromString(htmlString, 'text/html');
    return page.body.children;
};

稍后谢谢我。