使用jsoup提取其第`th`标题显示特定值的表

时间:2014-07-13 02:31:01

标签: java html parsing jsoup

当处理一堆乱七八糟的嵌套表时,我们如何使用jsoup提取特定的表?

以下面的HTML为例,一堆表格。在下半部分向下扫描两个关键表,每个关键表都有一个显示thDOG的{​​{1}}单元格。

有时我想要狗桌,有时候是猫桌。可能有十几个(" BIRD"," MOUSE"," HAMSTER"等等)。猫表可能比狗表更深嵌套。所以我不能使用任何关于"首先"或者"最后"。我必须查看CAT单元格的值,然后获取立即包含的表。

以下jsoup代码为我提供了两个元素:

th

使用该行我得到两个元素而不是一个:

  • 我想要的表格。
  • 包含我想要的表的外部表。

此时我的解决方法是检查长度,然后选择较短的长度。但必须有更好的方法。

HTML:

 Elements elements = document.select( "table:has(tbody > tr > th  > b:containsOwn(CAT))" );

我还尝试使用jsoup 1.7.3版的以下Java应用程序。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>title</title>
    </head>
    <body>
        <!-- page content -->
        <table>  <!--Outer table. Do not want this.-->
            <tbody>
                <tr>
                    <td>

                        <table>
                            <tbody>
                                <tr>
                                    <th><b>DOG</b></th> <!-- DOG in header -->
                                </tr>
                                <tr>
                                    <td>X</td>
                                    <td>7</td>
                                </tr>
                            </tbody>
                        </table>

                    </td>
                    <td>

                        <table> <!-- I want this table because it contains a header ("th") displaying the value "CAT". -->
                            <tbody>
                                <tr>
                                    <th><b>CAT</b></th>  <!-- CAT in header -->
                                </tr>
                                <tr>
                                    <td>A</td>
                                    <td>1</td>
                                </tr>
                            </tbody>
                        </table>

                    </td>
                </tr>
            </tbody>
        </table>
    </body>
</html>

但它返回两个元素而不是一个:

  1. 包含所需表格的外部表格。
  2. 理想的表格。
  3. 另一个问题是,虽然我并不完全理解eq selector的行为,但如果它只是在层次结构中同一点上彼此相邻的兄弟元素之间进行选择,那么这将是即使它在这个例子中起作用,也不是一个正确的答案。在我的问题的实际应用中,表可以在任意数量的其他表中任意嵌套。其他表与页面布局有关,没有与我想要的表的直接逻辑连接。

4 个答案:

答案 0 :(得分:1)

解决方法:查找目标值,上传层次结构

另一种解决方法。这不是一个真正的答案,因为它没有改进jsoup选择器。

我们通过其th标题单元格的值知道我们想要哪个表格。所以找到那个元素,然后向后工作。向上移动元素的层次结构(DOM树),超过trtbody,直到我们到达table。我们知道这是拥有目标th的直接表。我们避免使用外嵌套表。

密钥代码包括查找th单元格:

Elements elements = document.select( "th > b:containsOwn(CAT)" ); 

...并循环查找每个父母:

Element element = elements.first();
while (  ! ( ( element == null ) || ( element.tagName().equalsIgnoreCase( "table" ) ) ) ) {
    element = element.parent();
}

完整示例应用:

package com.example.jsoupexperiment;

import java.io.InputStream;
import java.util.Scanner;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class ParseNestedTables
{
    public static void main ( String[] args )
    {
        System.out.println( "Running main method of ParseNestedTables class." );
        InputStream stream = ParseNestedTables.class.getResourceAsStream( "/bogus.html" );
        Scanner scan = new Scanner( stream );
        StringBuilder sb = new StringBuilder();
        while ( scan.hasNextLine() ) {
            sb.append( scan.nextLine() + "\n" );
        }

        Document document = Jsoup.parse( sb.toString() );
        Elements elements = document.select( "th > b:containsOwn(CAT)" ); // Start by finding the desired table's target "th" element.
        int countElements = elements.size();
        switch ( countElements ) {
            case 0:
                System.out.println( "ERROR: Found no elements." );
                break;
            case 1:
                System.out.println( "GOOD: Found 1 element." );
                Element element = elements.first();

                // Loop up the hierarchy of elements (the DOM tree) until we find our desired "table" element or until we get a null.
                while (  ! ( ( element == null ) || ( element.tagName().equalsIgnoreCase( "table" ) ) ) ) {
                    element = element.parent();
                }

                System.out.println( "Found Element:\n" + element.toString() );
                break;
            default:
                System.out.println( "ERROR: Found multiple elements: " + countElements );
                break;
        }
    }
}

答案 1 :(得分:1)

基本上我使用了两个选择器。 document.select("table table");选择嵌套表,element.select("th b:contains(CAT)").size() > 0检查包含CAT的元素。

package com.example.jsoupexperiment;

import java.io.IOException;
import java.io.InputStream;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * PURPOSE To test parsing of nested tables using the "jsoup" library, as
 * discussed on this StackOverflow.com question:
 * http://stackoverflow.com/q/24719049/642706 Titled: Extract a table whose `th`
 * header displays a certain value, using jsoup
 */
public class ParseNestedTables2 {
    public static void main(String[] args) throws IOException {
        System.out.println("Running main method of ParseNestedTables class.");
        InputStream stream = ParseNestedTables2.class
                .getResourceAsStream("/bogus.html");
        Document document = Jsoup.parse(stream, "UTF-8", "http://example.com");
        Elements elements = document.select("table table");
        for (Element element : elements) {
            if (element.select("th b:contains(CAT)").size() > 0) {
                System.out
                        .println("table that have th contain selected text (CAT)");
                System.out.println(element);
            }
        }
    }

}

*我重构了一些关于如何从输入流使用JSOUP解析的代码。

答案 2 :(得分:0)

我不知道,因为它似乎对我很好:

import java.util.Scanner;    
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

public class ThHeaderTest {
   public static void main(String[] args) {
      String resource = "thHeader.txt";
      Scanner scan = new Scanner(ThHeaderTest.class.getResourceAsStream(resource));
      StringBuilder sb = new StringBuilder();
      while (scan.hasNextLine()) {
         sb.append(scan.nextLine() + "\n");
      }
      // System.out.println(sb.toString());
      Document document = Jsoup.parse(sb.toString());
      Elements elements = document.select("table:eq(0):has(th:contains(CAT))");
      System.out.println(elements);
   }
}

答案 3 :(得分:0)

解决方法:使用较短元素

不是真正的答案,因为它没有改进jsoup选择器。但这是一个实际的解决方法。

由于问题是所需的表也作为其外嵌套父表返回,从逻辑上我们知道所需表的HTML也将比外表短。

因此,解决方法是比较每个找到Element的长度。使用HTML最短的元素。