如何选择特定元素之后的所有文本节点

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

HTML:

<div class="someclass">
    <h3>First</h3> 
    <strong>Second</strong> 
    <hr>
    Third
    <br>
    Fourth
    <br>
    <em></em>
    ...
</div>

从上面的

div
节点我想获取
hr
之后的所有子文本节点(
"Third"
"Fourth"
,...可能还有更多)

如果我这样做

document.querySelectorAll('div.someclass>hr~*')

我得到

NodeList [ br, br, em, ... ]
- 没有文本节点

与下面

document.querySelector('div.someclass').textContent

我将所有文本节点作为单个字符串

我可以将每个文本节点获取为

var third = document.querySelector('div.someclass').childNodes[6].textContent
var fourth = document.querySelector('div.someclass').childNodes[8].textContent

所以我尝试了

document.querySelector('div.someclass').childNodes[5:]  # SyntaxError

slice()

document.querySelector('div.someclass').childNodes.slice(5)  # TypeError

有什么方法可以获取从

hr
节点开始的所有子文本节点吗?

更新

我忘了提这个问题是关于网络抓取,而不是网络开发......我无法更改 HTML 源代码

javascript web-scraping css-selectors
4个回答
3
投票

您可以获取内容并使用

hr
进行拆分来获取
hr
之后的 html,然后在
div
中替换此内容,您将能够操纵此
div
来获取您的内容:

var content = document.querySelector('.someclass').innerHTML;
content = content.split('<hr>');
content = content[1];

document.querySelector('.hide').innerHTML = content;
/**/

var nodes = document.querySelector('.hide').childNodes;
for (var i = 0; i < nodes.length; i++) {
  console.log(nodes[i].textContent);
}
.hide {
  display: none;
}
<div class="someclass">
  <h3>First</h3>
  <strong>Second</strong>
  <hr> Third
  <br> Fourth
  <br>
  <em></em> ...
</div>
<div class="hide"></div>


1
投票

.childNodes
包括文本和非文本节点。

你的语法错误是因为你不能像javascript中的

[5:]
那样进行数组切片。

而且 NodeList 类似于数组...但不是数组...这就是为什么

slice
不能直接在
childNodes
上工作。

1) 获取你的 NodeList

var nodeList = document.querySelector('.some-class').childNodes;

2)将NodeList转换为实际数组

nodes = Array.prototype.slice.call(nodes);

(注意在现代 ES6 浏览器中你可以这样做

nodes = Array.from(nodes);
此外,现代浏览器还添加了对 NodeList 对象的
.forEach
支持...因此您可以直接使用
.forEach
,而无需在现代浏览器中对 NodeList 进行数组转换)

3)迭代收集你想要的文本节点

这取决于你自己的逻辑。但是您可以迭代节点并测试是否

node.nodeType == Node.TEXT_NODE
来查看任何给定节点是否是文本节点。

var foundHr = false,
    results = [];
nodes.forEach(el => {
    if (el.tagName == 'HR') { foundHr = true; }
    else if (foundHr && el.nodeType == Node.TEXT_NODE) {
        results.push(el.textContent);
    }
});
console.log(results);

0
投票

您可以使用这段代码获取

node
下的所有文本节点:

var walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, null, false);
var textNode;
var result = [];
while (textNode = walker.nextNode()) {
    result.push(textNode);
}

并且您已经获得了

Array
文本节点,因此您可以根据需要
slice()
来使用它:

console.log(result.slice(5));

0
投票

另一种[递归]解决方案

const { childNodes } = <Element>document.querySelector('.someclass');  // throws an error if element doesn't exist
const siblings = sliceNodeList([], false, 'hr', ...childNodes);

function sliceNodeList(nodes: Node[], deep: boolean, selector: string, node?: Node, ...more: Node[]) {
    if (!selector) return nodes;
    if (!node) return nodes;
    const { nodeType } = node;
    const handle = {  // not concerned with Attr Nodes
        [Node.ELEMENT_NODE]: handleElement,
        [Node.TEXT_NODE]: handleOther,
        [Node.COMMENT_NODE]: handleOther,
    }[ nodeType ];
    
    if (handle) handle.call(this, nodes, deep, selector, node);
    if (more.length) sliceNodeList(nodes, deep, selector, ...more);
    return nodes;
}

function handleElement(nodes: Node[], deep: boolean, selector: string, node: Element) {
    const { childNodes } = node;  // not concerned with Attr Nodes
    const matches = node.matches(selector);
    
    if (nodes.length) nodes.push(node);  // assume we must have already matched
    else if (matches) nodes.push(node);  // we matched on an element
    
    if (deep) sliceNodeList(nodes, deep, selector, ...childNodes);  // keep diving into substructures
    return nodes;
}

function handleOther(nodes: Node[], deep: boolean, selector: string, node: Text|Comment) {
    if (nodes.length) return [ ...nodes, node ];  // assume we must have already matched
    if (node.data === selector) return [ ...nodes, node ];  // we matched on a Text or Comment value
    return nodes;
}

基本上,这只是通过检查在映射到正确的节点类型的处理程序时是否有更多同级来使用递归。它通过调用父函数来使用相互递归(如果

deep === true
)来深入嵌套结构。我们在这里并不关心
Attr
Node
,但您可以为此设置一个处理程序。

因为

Text
Comment
共享足够多的相同接口 (
node.data
),我们能够重用相同的函数。该函数与
handleElement
类似,假设
if (nodes.length)
那么我们已经匹配,并且可以安全可靠地将当前节点添加到集合中。

handleElement
不同,
Text
Comment
无法呼叫
node.matches
。但是,如果节点的值等于
selector
,我们仍然可以在节点上进行匹配 - 使我们能够通过文本进行选择。我们可以更进一步,我们可以使用
===
String.prototype.includes
之类的东西,而不是使用
RegExp
对整个文本(或注释)值使用严格相等。

我们可以使用

for
while
或其他迭代方法。然而,在我看来,递归是遍历 DOM 的更好方法,因为 DOM 是集成的(
Node
实现了复合模式),因此是递归结构。我也只是很快地在 VSCode 中完成了这个,甚至没有在浏览器控制台中运行它,但我一直在编写这个模式,所以它应该非常接近 good-to-go。

希望有帮助。

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