【问题标题】:Derive CSS selector based on Element instance基于 Element 实例派生 CSS 选择器
【发布时间】:2014-11-15 18:46:52
【问题描述】:

背景

许多问题询问如何在给定 CSS 选择器的情况下获取特定的 DOM element。这个问题是关于相反的方向。使用jsoup 解析文档,但可以轻松转换为:

用例

对于特定的问题领域(例如,化合物),成千上万的网页以类似的方式列出化学品,但不同网站的标记不同。例如:

<div id="chemical-list">
  <div class="compound">
    <span class="compound-name">water</span>
    <span class="compound-periodic">H2O</span>
  </div>
  <div class="compound">
    <span class="compound-name">sodium hypochlorite</span>
    <span class="compound-periodic">NaClO</span>
  </div>
</div>

另一个网站可能会以不同的方式列出它们:

<ul class="chemical-compound">
  <li class="chem-name">water, H2O</li>
  <li class="chem-name">sodium hypochlorite, NaClO</li>
</ul>

另一个网站可能再次使用不同的标记:

<table border="0" cellpadding="0" cellspacing="0">
  <tbody>
    <tr><td>water</td><td>H2O</td></tr>
    <tr><td>sodium hypochlorite</td><td>NaClO</td></tr>
  </tbody>
</table>

从数千个站点中的每个站点下载几个示例页面。然后,使用现有的化学品列表,检索候选网页元素列表相对简单。使用jsoup,就这么简单:

  Elements elements = chemicals.getElementsMatchingOwnText( chemicalNames );

这将允许对数千页进行高精度分析。 (该页面可以讨论水和次氯酸钠的应用,但仅分析列表。)了解 CSS 将大大简化分析并提高其准确性。

另一种方法是处理整个页面以查找化学品“组”,然后尝试提取列表。这两个问题都很困难,但使用 CSS 选择器跳转到页面中的确切位置要高效得多,而且可能要准确得多。这两个问题都需要一些手工制作,但我想尽可能地自动化。

问题

上述 API 似乎没有在给定 Element instance 的情况下生成 CSS 选择器的方法(越独特越好)。可以遍历父元素并手动生成选择器。这是demonstrated 在一些questions 中使用JavaScript。还有生成XPath 的答案,使用Selenium 可能也是可行的。

具体来说,你会怎么做:

String selector = element.getCSSPath();
Elements elements = document.select( selector );

这会:

  1. 返回给定元素的 CSS 选择器。
  2. 在文档中搜索给定的 CSS 选择器。
  3. 返回与选择器匹配的元素列表。

第二行不是问题;第一行有问题。

问题

什么 API 可以从 DOM 元素生成 CSS 选择器(尽可能唯一)?

如果没有现有的 API,那么很高兴知道。

【问题讨论】:

  • Jsoup 不提供此功能,但如果提供,最独特的选择器将是使用 &gt;:eq() 来模拟 XPath 表达式的选择器。目前尚不清楚它的用途是什么——它将精确地选择该元素,仅此而已,因此您的示例代码将毫无用处。您对此类 API 的实际用例是什么?

标签: java html css dom jsoup


【解决方案1】:

截至 2014-09-28 / 1.8.1 JSoup 通过方法 Element.cssSelector() 具有此功能(感谢 pull request)。

cssSelector

public String cssSelector() - 得到一个 CSS selector 它将 唯一选择此元素。如果元素有ID,则返回#id; 否则返回父级(如果有)CSS 选择器,后跟 '>', 后跟元素的唯一选择器 (tag.class.class:nth-child(n)).

ReturnsCSS 路径,可用于检索 a 中的元素 选择器。

这会返回选择器,如果存在则使用元素 ID 返回唯一元素,否则会创建 tag.class.class:nth-child(n) 形式的选择器。

例如:"html &gt; body &gt; h2.section:nth-child(3)"

【讨论】:

    【解决方案2】:

    只需使用 Java 的 actual JavaScript engine 并运行一些普通的 JavaScript?

    function getSelector(element) {
      var selector = element.id;
    
      // if we have an ID, that's all we need. IDs are unique. The end.
      if(selector.id) { return "#" + selector; }
    
      selector = [];
      var cl;
      while(element.parentNode) {
        cl = element.getAttribute("class");
        cl = cl ? "." + cl.trim().replace(/ +/g,'.') : '';
        selector.push(element.localName + cl);
        element = element.parentNode;
      }
      return selector.reverse().join(' ');
    }
    

    让我们验证一下

    <div class="main">
      <ul class=" list of things">
        <li><a href="moo" class="link">lol</a></li>
      </ul>
    </div>
    

    var a = document.querySelector("a");
    console.log(getSelector(a));
    

    http://jsfiddle.net/c8k6Lxtj/ -- 结果:html body div.main ul.list.of.things li a.link...黄金。

    【讨论】:

    • 谢谢,迈克。我用它创建了一个(递归)Java-only solution
    • 这在很多情况下不会返回唯一的选择器。例如,在此处查看getSelector2() 控制台输出:codepen.io/thdoan/pen/WjVRyG?editors=1111
    • 我不明白为什么只要 mapping 是稳定的就会出现问题。
    【解决方案3】:

    我使用 Mike 的答案进行了以下更改,以使返回的 css 选择器更短。

    更新:同样使用 name 属性来缩短 css 选择器并检查每次迭代是否选择器,到目前为止,返回页面上的单个元素

    更新:正如@10basetom 在 cmets 中指出的那样,在元素没有唯一 id 或唯一类名或唯一类名 + 名称属性的情况下,该方法可能会产生非唯一 css 路径,但在其他情况下它会产生最短的 css 选择器。所以,我建议使用 document.querySelectorAll(result).length === 1 检查 css 路径结果并使用其他方法 described here

    function getShortestSelector(element) {
        var selector = element.id;
    
        // if we have an ID, that's all we need. IDs are unique. The end.
        if(selector.id) {
            return "#" + selector;
        }
    
        selector = [];
        var cl, name;
        while(element.parentNode && (selector.length === 0 || document.querySelectorAll(selector.join(' ')).length !== 1)) {
    
            // if exist, add the first found id and finish building the selector
            var id = element.getAttribute("id");
            if (id) {
                selector.unshift("#" + id);
                break;
            }
    
            cl = element.getAttribute("class");
            cl = cl ? "." + cl.trim().replace(/ +/g,'.') : '';
            name = element.getAttribute("name");
            name = name ? ("[name=" + name.trim() + "]") : '';
            selector.unshift(element.localName + cl + name);
            element = element.parentNode;
        }
    
        var result = selector[0];
        if (selector.length > 1) {
            result += " " + selector.slice(1).join(" ").replace(/\[name=[^\]]*]/g, '');
        }
    
        return result;
    }
    

    【讨论】:

    【解决方案4】:

    据我所知,没有任何 API 提供此功能。以下似乎有效:

      /**
       * Returns the shortest CSS path identify a given element. Note that this
       * will not return a unique element, but can be used to obtain all elements
       * that match the selector returned.
       * 
       * @param cssElement The element that must be identified by its CSS selector.
       * @return The CSS selector for the given element, or the empty string if
       * no selector is found.
       */
      private String cssPath( Element cssElement ) {
        StringBuilder result = new StringBuilder( 256 );
    
        String id = cssElement.id();
    
        // If the element has an ID, then return it as the shortest path (IDs are
        // supposed to be unique).
        if( id.length() > 0 ) {
          // This will break the chain of recursion.
          result.append( '#' ).append( id );
        }
        else {
          Element parent = cssElement.parent();
    
          // If there is a parent node, then recurse to determine its CSS path.
          // Otherwise, the chain of recursion ends here.
          if( parent != null ) {
            result.append( cssPath( parent ) );
          }
    
          // Generate a CSS path using the element's tag name and classes.
          if( cssElement.className().length() > 0 ) {
            result.append( " > " ).append( cssElement.tagName() );
            Set<String> cssClasses = cssElement.classNames();
            cssClasses.forEach( c -> result.append( '.' ).append( c ) );
            result.append( ' ' );
          }
        }
    
        // Return the (possibly incomplete) CSS selector through recursion.          
        return result.toString();
      }
    

    【讨论】:

      猜你喜欢
      • 2013-02-12
      • 1970-01-01
      • 2014-04-27
      • 2011-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多