Selenium 属性名前缀后缀查找元素(_ngcontent--63)
2025-03-18 03:37:48
如何在 Selenium 中通过属性名的前缀和后缀查找元素
遇到了一个棘手的问题:需要通过属性名来定位元素,而不是属性值。更具体点,需要查找那些属性名以前缀 "_ngcontent-" 开头,并且以后缀 "-63" 结尾的 <td>
元素。直接用 By.tagName("td")
不行,因为页面上有很多其他的 <td>
元素。
最初尝试了用 XPath 的 starts-with
和 ends-with
函数,像这样:
By.xpath("//td[starts-with(@_ngcontent, '_ngcontent-') and ends-with(@_ngcontent, '-63')]");
但这个方法不对路,starts-with
和 ends-with
是用来匹配属性值的,而不是属性名。 问题出在如何匹配属性名本身。
问题根源
问题的核心在于 XPath 1.0 和 2.0 的差异。 Selenium 主要使用 XPath 1.0,它不支持直接对属性名进行 starts-with
和 ends-with
操作。 也就是说, 类似starts-with(name(@*))
这种对全部属性名判断的方式是不支持的。XPath 2.0 虽然支持,但 Selenium 以及大多数浏览器内置的 XPath 引擎还没普及 XPath 2.0。
解决方案
下面提供了几种解决思路,可以根据实际情况选择最合适的方案:
方案一:组合使用 XPath 和 Java 代码过滤
这是最稳妥的方法, 也是性能和可控性的最佳选择. 也是最推荐的一种。
- 宽松定位: 先用一个比较宽松的 XPath 表达式选取所有潜在的
<td>
元素。 - 代码过滤: 获取这些元素后,在 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 选择器更熟悉,可以用这个方法。
- 使用 CSS 选择器选取所有
<td>
元素:driver.findElements(By.cssSelector("td"))
- Java 代码过滤: 和方案一一样,用 Java 代码检查每个元素的属性名是否符合要求。
代码示例 (Java): 代码和方案一基本一致, 除了这里获取元素列表这行.
//...其他代码和方案一完全一样, 除了获取元素列表的代码
List<WebElement> elements = driver.findElements(By.cssSelector("td"));
//...其余和方案一一样
原理与方案一类似,只不过元素初筛的方式变成了 CSS 选择器.
方案三: 使用 JavaScript (终极方案,但不推荐)
如果实在不想在 Java 代码里做过滤, 还有一个“终极”方法:直接用 JavaScript。
- 构造 JavaScript 代码 :在 JavaScript 代码中完成所有的查找逻辑。
- 执行 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代码中被访问.
安全建议:
- 如果从不可信的来源获取
prefix
和suffix
的值,务必进行严格的校验和转义,防止 JavaScript 注入攻击。 - 尽管是终极方案,不过尽量考虑方案一,代码可维护性和性能更佳。
总结
处理这种需要根据属性名模式来查找元素的情况,最可靠的做法是结合使用定位器和代码过滤。直接使用XPath的局限性较大,不建议为了追求“纯 XPath” 方案而牺牲代码的可读性和可维护性。 如果必须要"纯"定位,考虑方案三。