Jsoup:has()选择器没有按预期工作

时间:2016-03-08 12:29:18

标签: java html web-scraping css-selectors jsoup

我正在尝试解析包含多个具有以下结构的单元格的HTML表格:

<td id="topic1234">
    <a name='1234'></a>
    <b><a href='/url'>Title</a><b>
    <span class='s'>Details</span>
</td>
<td id="topic2345">
    <a name='2345'></a>
    <b><a href='/url'>Title</a><b>
    <span class='s'>Details</span>
</td>
...

'id'属性,带有'href'属性的'a'元素和'span'元素是重要的细节,这两个元素是直接嵌套的。 我尝试使用

select("[id^=topic]"
          + ":has(> b > a[href])"
          + ":has(> span.s)")

但结果列表为空。当我将其更改为:

时,它可以正常工作
select("td[id^=topic]"
          + ":has(td > b > a[href])"
          + ":has(td > span.s)")

但我不希望选择器依赖于根元素是'td'这一事实,并且根据文档判断,前者也应该有效。以下内容也不起作用:

select("[id^=topic]"
          + ":has(:root > b > a[href])"
          + ":has(:root > span.s)")

我在这里做错了吗?顺便使用Jsoup 1.8.3。

2 个答案:

答案 0 :(得分:1)

:has(selector)中的选择器包含父元素。我不认为>b是JSoup中的有效选择器,但*>b应该没问题且它允许任何父元素。所以这应该有效:

select("[id^=topic]"
      + ":has(* > b > a[href])"
      + ":has(* > span.s)")

编辑1回应评论:

为了使:has(selector)的选择器更有可能是[id^=topic]的直接孩子,您也可以这样做:

select("[id^=topic]"
      + ":has([id^=topic] > b > a[href])"
      + ":has([id^=topic] > span.s)")

这当然不是保证,因为父母的内在子女也可能带有以topic开头的身份证。

<强> EDIT2

solution of user2340612类似,您可以通过将选择器分为两部分来确保。首先,我们匹配以topic开头的所有元素。然后我们循环这些并构造一个包含特定id的新选择器。只要所有元素的ID都是单独的,这就可以工作。

String html = "<table><tr><td id=\"topic1234\">" +
        "<a name='1234'></a>" +
        "<div><b><a href='/url'>Title</a></b></div>" +
        "<span class='s'>Details</span></td>\n" +
        "<td id=\"topic2345\">\n" +
        "    <a name='2345'></a>\n" +
        "    <b><a href='/url'>Title</a></b>\n" +
        "    <span class='s'>Details</span>\n" +
        "</td>"+
        "<td id=\"topic3456\">\n" +
        "    <div id=\"topic4567\"><a name='3456'></a>\n" +
        "    <b><a href='/url'>Title</a></b>\n" +
        "    <span class='s'>Details</span>\n" +
        "    </div>" +
        "</td></tr></table>";

Document doc = Jsoup.parse(html);
Elements selected = doc.select("[id^=topic]");
for (Element elem : selected) {
  String idStr = elem.attr("id");
  Element el = elem.select(":has(#"+idStr+" > b > a[href]):has(#"+idStr+" > span.s)").first();
  if (el != null){
      System.out.println("found matching element: "+el);
  }
  if (el != null){
      System.out.println("does not really match: "+el);
  }
}

答案 1 :(得分:1)

我认为由于JSoup不支持:has(> tag)之类的语法,因此无法为您所需的内容编写单个选择器。

但是,我认为您可以将选择器拆分为多个部分:

String html = "<table><td id=\"topic1234\">" +
                  "<a name='1234'></a>" +
                  "<div><b><a href='/url'>Title</a></b></div>" +
                  "<span class='s'>Details</span></td>\n" +
                  "<td id=\"topic2345\">\n" +
                  "    <a name='2345'></a>\n" +
                  "    <b><a href='/url'>Title</a></b>\n" +
                  "    <span class='s'>Details</span>\n" +
                  "</td></table>"

Document doc = Jsoup.parse(html);
Elements selected = doc.select("[id^=topic]");
for (Element elem : selected) {
    // Check if "b > a[href]" is a direct child of "td"
    if (elem.select(":root > b > a[href]").size() > 0) {
        System.out.println("Found: "+elem);
    } else {
        System.out.println("Not found:"+elem);
    }
}

即。 html代码如下:

<table>
    <td id="topic1234">
        <a name="1234"></a>
        <div>
            <b><a href="/url">Title</a></b>
        </div>
    <span class="s">Details</span></td>
    <!-- second line -->    
    <td id="topic2345">
        <a name="2345"></a>
        <b><a href="/url">Title</a></b>
        <span class="s">Details</span>
    </td>
</table>

返回:

Not found:<td id="topic1234"><a name="1234"></a>
 <div>
  <b><a href="/url">Title</a></b>
 </div><span class="s">Details</span></td>
Found: <td id="topic2345"> <a name="2345"></a> <b><a href="/url">Title</a></b> <span class="s">Details</span> </td>

显然,同样适用于第二个条件(即span.s

请注意,在这种情况下,:root选择器有效,因为elem的根元素是td而不是table