JavaScript中使用.querySelector查找包含特定文本的<div>标签

248
如何找到包含特定文本的DIV?例如:
<div>
SomeText, text continues.
</div>

尝试使用类似这样的东西:

var text = document.querySelector('div[SomeText*]').innerTEXT;
alert(text);

但当然这不会起作用。我该怎么做?


即使你能做到,它也不会比获取所有div并通过innerText属性进行过滤更快。那么为什么不手动操作呢? - Redu
14个回答

222

OP的问题是关于纯粹的JavaScript而不是jQuery。 虽然有很多答案,我喜欢@Pawan Nogariya answer,但请尝试检查这个替代方案。

你可以在JavaScript中使用XPATH。更多信息请参见MDN文章here

document.evaluate()方法评估XPATH查询/表达式。因此,您可以通过该方法传递XPATH表达式,遍历HTML文档并定位所需元素。

在XPATH中,您可以选择一个元素,例如以下文本节点,以获取具有以下文本节点的div

//div[text()="Hello World"]

要获取包含某些文本的元素,请使用以下内容:
//div[contains(., 'Hello')]

XPATH中的contains()方法将节点作为第一个参数,要搜索的文本作为第二个参数。

查看这个 plunk 这里, 这是在JavaScript中使用XPATH的示例。

这是代码片段:

var headings = document.evaluate("//h1[contains(., 'Hello')]", document, null, XPathResult.ANY_TYPE, null );
var thisHeading = headings.iterateNext();

console.log(thisHeading); // Prints the html element in console
console.log(thisHeading.textContent); // prints the text content in console

thisHeading.innerHTML += "<br />Modified contents";  

如您所见,我可以获取HTML元素并按照自己的意愿进行修改。


谢谢!非常好用!但是如果我只需要从这段文本中获取一个单词,该如何使用“console.log”输出“thisHeading.textContent”?例如:'//div[contains(., '/You login (.*) times this session/')]',然后弹出(thisHeading.textContent.$1)。 - passwd
好的,我这样做:alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ; - passwd
@passwd,很抱歉你不能这样做。XPATH 1.0不支持正则表达式(.evaluate()使用的是它)。首先,你不能搜索与正则表达式匹配的内容。其次,.textContent属性返回元素的文本节点。如果你想从这个文本中获取一个值,你应该显式地处理它,可能通过创建某种函数来匹配正则表达式并返回匹配值组。为此,请在单独的线程上提出一个新问题。 - gdyrrahitis
Internet Explorer:不支持。但在Edge中支持。我不确定这意味着什么版本。 - Rolf
1
如果我要查找的元素不存在,应该如何处理错误? - nenito
如果没有找到元素,iterateNext()会返回null。 - Pietro Coelho

156
您可以使用这个非常简单的解决方案:
Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. Array.from 将会把 NodeList 转化为数组(还有多种方法可以做到这一点,比如展开运算符或者 slice 方法)

  2. 得到一个数组后,就可以使用 Array.find 方法,你可以输入任意条件来进行匹配。你也可以使用正则表达式或者其他方式来检查 textContent。

请注意,Array.fromArray.find 是 ES2015 的特性。如果要兼容旧版浏览器(例如 IE10)而不使用编译器,请注意:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];

13
如果您想找到多个元素,请用“filter”替换“find”。 - RubbelDieKatz
[].slice.call( ... ) 这是更简单的。 - Oleg Mihailik

69

由于您是用JavaScript提出的问题,所以您可以尝试类似这样的方法

function contains(selector, text) {
  var elements = document.querySelectorAll(selector);
  return Array.prototype.filter.call(elements, function(element){
    return RegExp(text).test(element.textContent);
  });
}

然后像这样调用它

contains('div', 'sometext'); // find "div" that contain "sometext"
contains('div', /^sometext/); // find "div" that start with "sometext"
contains('div', /sometext$/i); // find "div" that end with "sometext", case-insensitive

2
似乎这个可以工作,但是我只得到了这个:[object HTMLDivElement],[object HTMLDivElement] - passwd
是的,您将获得包含匹配文本的div,然后可以调用它们的innerText方法,就像这样foundDivs[0].innerText,非常简单。 - Pawan Nogariya

29

该解决方案执行以下操作:

  • 使用ES6展开运算符将所有div的NodeList转换为数组。

  • 如果div包含查询字符串,而不仅仅是完全等于查询字符串(这对其他答案来说是可能发生的情况),则提供输出。例如,它不仅应该为“SomeText”提供输出,还应该为“SomeText,text continues”提供输出。

  • 输出整个div内容,而不仅仅是查询字符串。例如,对于“SomeText,text continues”,应该输出整个字符串,而不仅仅是“SomeText”。

  • 允许多个div包含字符串,而不仅仅是单个div

[...document.querySelectorAll('div')]      // get all the divs in an array
  .map(div => div.innerHTML)               // get their contents
  .filter(txt => txt.includes('SomeText')) // keep only those containing the query
  .forEach(txt => console.log(txt));       // output the entire contents of those
<div>SomeText, text continues.</div>
<div>Not in this div.</div>
<div>Here is more SomeText.</div>


5
我喜欢这个。简洁明了又易懂 - 三者兼备。 - ba_ul
3
非常低效,你可以想象一下最上层的<div>元素内有多少内容,innerHTML会非常大。你应该先过滤掉包含子元素的<div>元素。同时,猜测使用document.getElementsByTagName('div')可能会更快,但最好进行基准测试来确定。 - Timmmm
这对我来说非常好,因为我已经知道它只能在表格中,所以我可以在开头设置一个很好的选择器,太棒了,谢谢。 - gsalgadotoledo

19

当我在2021年遇到这个问题时,我发现使用XPATH太过复杂(需要学习其他知识),而这个问题本应该很简单。

于是我想出了以下解决方案:

function querySelectorIncludesText (selector, text){
  return Array.from(document.querySelectorAll(selector))
    .find(el => el.textContent.includes(text));
}

使用方法:

querySelectorIncludesText('button', 'Send')

请注意,我决定使用 includes 而不是严格比较,因为那正是我所需要的,您可以随意进行调整。

如果您想支持所有浏览器,则可能需要这些polyfills:

  /**
   * String.prototype.includes() polyfill
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
   * @see https://vanillajstoolkit.com/polyfills/stringincludes/
   */
  if (!String.prototype.includes) {
    String.prototype.includes = function (search, start) {
      'use strict';

      if (search instanceof RegExp) {
        throw TypeError('first argument must not be a RegExp');
      }
      if (start === undefined) {
        start = 0;
      }
      return this.indexOf(search, start) !== -1;
    };
  }

11

你最好查看一下你正在查询的 div 元素是否有一个父元素。如果是这样,请获取父元素并执行 element.querySelectorAll("div")。一旦你获得了 nodeList,就可以在其中应用 innerText 属性的筛选器。假设我们正在查询的 div 元素的父元素具有 idcontainer,通常情况下直接通过 id 可以访问 container,但让我们按照正确的方式来做。

var conty = document.getElementById("container"),
     divs = conty.querySelectorAll("div"),
    myDiv = [...divs].filter(e => e.innerText == "SomeText");

就是这样。


这对我有用,但是使用innerHTML而不是innerText。 - Chase Sandmann

5
如果您不想使用jQuery或类似的东西,那么您可以尝试以下方法:
function findByText(rootElement, text){
    var filter = {
        acceptNode: function(node){
            // look for nodes that are text_nodes and include the following string.
            if(node.nodeType === document.TEXT_NODE && node.nodeValue.includes(text)){
                 return NodeFilter.FILTER_ACCEPT;
            }
            return NodeFilter.FILTER_REJECT;
        }
    }
    var nodes = [];
    var walker = document.createTreeWalker(rootElement, NodeFilter.SHOW_TEXT, filter, false);
    while(walker.nextNode()){
       //give me the element containing the node
       nodes.push(walker.currentNode.parentNode);
    }
    return nodes;
}

//call it like
var nodes = findByText(document.body,'SomeText');
//then do what you will with nodes[];
for(var i = 0; i < nodes.length; i++){ 
    //do something with nodes[i]
} 

一旦你拥有包含文本的节点数组,你就可以对它们进行操作。比如弹出每一个节点或将其打印到控制台上。需要注意的是,这不一定会准确地抓取div元素,而是抓取包含所需文本的文本节点的父节点。


4
使用XPath和document.evaluate(),确保在contains()参数中使用text()而不是.,否则您将匹配整个HTML或最外层的div元素。
var headings = document.evaluate("//h1[contains(text(), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

或者忽略前导和尾随的空格。
var headings = document.evaluate("//h1[contains(normalize-space(text()), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

或匹配所有标签类型(div、h1、p等)。
var headings = document.evaluate("//*[contains(text(), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

然后迭代。
let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}

这个方法可以用来给一个元素添加类吗?例如thisheading.setAttribute('class', "esubject") - Matthew
一旦您获取到该元素,确实可以这样做。但是最好使用 element.classList.add("esubject") 来添加类名 :) - Steven Spungin

4
Google在“对于那些需要查找具有特定文本的节点”的搜索结果中将此作为顶部结果。更新一下,现代浏览器中的nodelist现在可以迭代而无需将其转换为数组。
解决方案可以像这样使用forEach。
var elList = document.querySelectorAll(".some .selector");
elList.forEach(function(el) {
    if (el.innerHTML.indexOf("needle") !== -1) {
        // Do what you like with el
        // The needle is case sensitive
    }
});

这对我非常有用,因为当普通选择器无法选择一个节点时,它可以在节点列表中执行查找/替换文本操作,因此我必须逐个过滤每个节点以检查其中是否包含需要查找的内容。

4

我在寻找使用正则表达式进行类似操作的方法时,决定构建自己的解决方案并分享出来,如果其他人也在寻找类似的解决方案,希望能对他们有所帮助。

function getElementsByTextContent(tag, regex) {
  const results = Array.from(document.querySelectorAll(tag))
        .reduce((acc, el) => {
          if (el.textContent && el.textContent.match(regex) !== null) {
            acc.push(el);
          }
          return acc;
        }, []);
  return results;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接