返回

Selenium 属性名前缀后缀查找元素(_ngcontent--63)

java

如何在 Selenium 中通过属性名的前缀和后缀查找元素

遇到了一个棘手的问题:需要通过属性名来定位元素,而不是属性值。更具体点,需要查找那些属性名以前缀 "_ngcontent-" 开头,并且以后缀 "-63" 结尾的 <td> 元素。直接用 By.tagName("td") 不行,因为页面上有很多其他的 <td> 元素。

最初尝试了用 XPath 的 starts-withends-with 函数,像这样:

By.xpath("//td[starts-with(@_ngcontent, '_ngcontent-') and ends-with(@_ngcontent, '-63')]");

但这个方法不对路,starts-withends-with 是用来匹配属性值的,而不是属性名。 问题出在如何匹配属性名本身。

问题根源

问题的核心在于 XPath 1.0 和 2.0 的差异。 Selenium 主要使用 XPath 1.0,它不支持直接对属性名进行 starts-withends-with 操作。 也就是说, 类似starts-with(name(@*)) 这种对全部属性名判断的方式是不支持的。XPath 2.0 虽然支持,但 Selenium 以及大多数浏览器内置的 XPath 引擎还没普及 XPath 2.0。

解决方案

下面提供了几种解决思路,可以根据实际情况选择最合适的方案:

方案一:组合使用 XPath 和 Java 代码过滤

这是最稳妥的方法, 也是性能和可控性的最佳选择. 也是最推荐的一种。

  1. 宽松定位: 先用一个比较宽松的 XPath 表达式选取所有潜在的 <td> 元素。
  2. 代码过滤: 获取这些元素后,在 Java 代码中遍历每个元素,获取其所有属性,然后判断属性名是否符合要求。

原理: 先大致筛选,再精细过滤,将 XPath 难以处理的部分交给更灵活的 Java 代码。

代码示例 (Java):

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import java.util.ArrayList;
import java.util.List;

public class AttributePrefixSuffix {

    public static void main(String[] args) {
        // 设置 ChromeDriver 路径(根据实际情况修改)
        System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");

        WebDriver driver = new ChromeDriver();
        driver.get("your_page_url"); // 替换成你的网页 URL

        List<WebElement> matchingElements = findElementsByAttributePrefixSuffix(driver, "td", "_ngcontent-", "-63");

        // 输出找到的元素内容(示例)
        for (WebElement element : matchingElements) {
            System.out.println(element.getText());
        }
		
		//关闭driver
        driver.quit();

    }

    public static List<WebElement> findElementsByAttributePrefixSuffix(WebDriver driver, String tagName, String prefix, String suffix) {
        List<WebElement> elements = driver.findElements(By.tagName(tagName));
        List<WebElement> matchingElements = new ArrayList<>();

        for (WebElement element : elements) {
            // 获取所有属性
           List<String> attributeNames = (List<String>)((JavascriptExecutor)driver).executeScript(
           "var items = []; for (index = 0; index < arguments[0].attributes.length; ++index) { items.push(arguments[0].attributes[index].name); } return items;", element);
		
            for (String attributeName : attributeNames)
			{
				 if (attributeName.startsWith(prefix) && attributeName.endsWith(suffix)) {
                    matchingElements.add(element);
                    break; // 找到一个符合条件的属性就够了,跳出内层循环
                 }
			}
           
        }

        return matchingElements;
    }
}

代码解释:

  • findElementsByAttributePrefixSuffix 方法接收 WebDriver 对象、标签名、属性名前缀和后缀作为参数。
  • 先用 driver.findElements(By.tagName(tagName)) 获取所有指定标签的元素。
  • 通过 JavascriptExecutor 获取元素的属性名.
  • 遍历这些元素,检查每个元素的属性名,如果找到符合条件的属性名,就将该元素添加到 matchingElements 列表。
  • 外层循环的判断里加入 break, 找到匹配项就不用继续判断这个元素的其余属性名了.

方案二:CSS 选择器 + Java 代码过滤 (类似方案一)

与方案一类似,只是把 XPath 换成了 CSS 选择器。如果对 CSS 选择器更熟悉,可以用这个方法。

  1. 使用 CSS 选择器选取所有 <td> 元素: driver.findElements(By.cssSelector("td"))
  2. Java 代码过滤: 和方案一一样,用 Java 代码检查每个元素的属性名是否符合要求。

代码示例 (Java): 代码和方案一基本一致, 除了这里获取元素列表这行.

//...其他代码和方案一完全一样, 除了获取元素列表的代码
  List<WebElement> elements = driver.findElements(By.cssSelector("td"));
//...其余和方案一一样

原理与方案一类似,只不过元素初筛的方式变成了 CSS 选择器.

方案三: 使用 JavaScript (终极方案,但不推荐)

如果实在不想在 Java 代码里做过滤, 还有一个“终极”方法:直接用 JavaScript。

  1. 构造 JavaScript 代码 :在 JavaScript 代码中完成所有的查找逻辑。
  2. 执行 JavaScript :通过 Selenium 的 JavascriptExecutor 执行这段 JavaScript 代码,并返回找到的元素。

原理: 利用 JavaScript 的灵活性直接在浏览器环境中操作 DOM,绕过 XPath 的限制。

代码示例 (Java):

import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

import java.util.List;

public class AttributePrefixSuffixJS {

    public static void main(String[] args) {
         // 设置 ChromeDriver 路径(根据实际情况修改)
        System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");
        WebDriver driver = new ChromeDriver();
        driver.get("your_page_url"); // 替换成你的网页 URL

        List<WebElement> matchingElements = findElementsByAttributePrefixSuffixJS(driver, "td", "_ngcontent-", "-63");

       // 输出找到的元素内容(示例)
        for (WebElement element : matchingElements) {
            System.out.println(element.getText());
        }
        driver.quit();

    }
	
	public static List<WebElement> findElementsByAttributePrefixSuffixJS(WebDriver driver, String tagName, String prefix, String suffix) {
        String script =
            "var elements = document.getElementsByTagName(arguments[0]);" +
            "var matchingElements = [];" +
            "for (var i = 0; i < elements.length; i++) {" +
            "  var attributes = elements[i].attributes;" +
            "  for (var j = 0; j < attributes.length; j++) {" +
            "    if (attributes[j].name.startsWith(arguments[1]) && attributes[j].name.endsWith(arguments[2])) {" +
            "      matchingElements.push(elements[i]);" +
            "      break;" + //找到即停
            "    }" +
            "  }" +
            "}" +
            "return matchingElements;";

        return (List<WebElement>) ((JavascriptExecutor) driver).executeScript(script, tagName, prefix, suffix);
    }
}

代码解释

  • 这段代码利用 Javascript 在页面中执行搜索逻辑.
  • executeScript 的参数里, 除了JS代码本身, 还依次传入了 tagName, prefix, suffix. 这些参数通过 arguments[0], arguments[1], arguments[2] 在JS代码中被访问.

安全建议:

  • 如果从不可信的来源获取 prefixsuffix 的值,务必进行严格的校验和转义,防止 JavaScript 注入攻击。
  • 尽管是终极方案,不过尽量考虑方案一,代码可维护性和性能更佳。

总结

处理这种需要根据属性名模式来查找元素的情况,最可靠的做法是结合使用定位器和代码过滤。直接使用XPath的局限性较大,不建议为了追求“纯 XPath” 方案而牺牲代码的可读性和可维护性。 如果必须要"纯"定位,考虑方案三。