【问题标题】:Extract a table whose `th` header displays a certain value, using jsoup使用 jsoup 提取 `th` 标题显示特定值的表
【发布时间】:2014-07-14 16:24:31
【问题描述】:

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

以下面的 HTML 为例,一堆表格。在后半部分向下扫描两个关键表,每个表都有一个th 单元格,显示DOGCAT

有时我想要狗桌,有时我想要猫桌。可能有十几个(“BIRD”、“MOUSE”、“HAMSTER”等)。 cat-table 可能比 dog-table 嵌套得更深。所以我不能使用任何关于“第一”或“最后”的技巧。我必须查看th 单元格的值,然后获取直接包含表。

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

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

通过该行,我得到两个元素而不是一个:

  • 我想要的桌子。
  • 包含我想要的表的外部表。

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

HTML:

<!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>

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

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;

/**
 * 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 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" );
        }
        // System.out.println(sb.toString());
        Document document = Jsoup.parse( sb.toString() );
        Elements elements = document.select( "table:eq(0):has(th:contains(CAT))" );
        int countElements = elements.size(); // Hoping for 1, but getting 2.
        System.out.println( "Found " + countElements + " elements. Dumping… \n\n" );

        for ( Element element : elements ) {
            System.out.println( "Element…\n" + element.toString() + "\n\n" );
        }

    }
}

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

  1. 包含所需表的外部表。
  2. 所需的表。

另一个问题是,虽然我不完全理解 eq selector 的行为,但如果它只是在层次结构中同一点上彼此相邻的兄弟元素中进行挑选,那么这将是不正确的回答,即使它在这个例子中有效。在我的问题的实际应用中,表可以任意嵌套在任意数量的其他表中。其他表格与页面布局相关,与我想要的表格没有直接的逻辑联系。

【问题讨论】:

  • document.select("table:eq(0):has(th:contains(CAT))"); 也许?
  • @HovercraftFullOfEels 不,该行给出了相同的结果(两个元素,外部表和所需表)。感谢您的尝试。
  • 没问题。请注意,我确实编辑了我的评论。
  • @HovercraftFullOfEels 如此指出。我尝试了"table:lt(1):has(th:contains(CAT))""table:eq(0):has(th:contains(CAT))"。那些应该有正确的含义?无论如何,两者都有相同的结果,外部和内部表都作为一对元素返回。
  • 您的要求是必须解决使用选择器吗?如果是,我认为您可能无法得到答案 - 请参阅 stackoverflow.com/questions/1520429/css-3-content-selector 。没有可以根据文本进行选择的 css 选择器。 (基于那个答案)。

标签: java html parsing jsoup


【解决方案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;
        }
    }
}

【讨论】:

    【解决方案2】:

    基本上我使用了两个选择器。 document.select("table table"); 选择嵌套表,element.select("th b:contains(CAT)").size() &gt; 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 解析的代码。

    【讨论】:

    • 感谢您的尝试,但您对我的示例 HTML 的理解也太过分了。问题源于使用 HTML tables for layout 内容,包括所需的数据表。所以嵌套是任意的和反复无常的。您的查询“表表”假定所需的表是嵌套的,但可能并非如此。请参阅my own workaround answer,它与您的相似,但对页面的假设较少:找到所需表格的所需单元格元素,然后使用对 .parent 的调用来查找第一个包络表。但是很好的答案; SO 需要更多 jsoup 示例。
    • 是的。你说的对。你的回答更有活力。但我会选择为每个站点(或页面)实现 jsoup。据我所知,在现实世界的网站中,页面结构会非常一致。 (*我广泛使用 jsoup 作为转换器(代理)来整合来自不同站点的搜索结果)。
    【解决方案3】:

    我不知道,因为它似乎对我有用:

    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);
       }
    }
    

    【讨论】:

    • 我几乎完全尝试了这段代码。对我来说失败了,返回两个元素而不是一个。我在问题中发布了我的代码。我还在问题中发布了我在实验中使用此代码的 HTML 的精确副本。在语义上应该和以前一样,但为了彻底,我替换了以前的 HTML。
    【解决方案4】:

    解决方法:使用较短的元素

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

    由于问题是期望的表也作为其外部嵌套父表返回,那么逻辑上我们知道期望表的 HTML 也将比外部表短。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-16
      • 1970-01-01
      • 2013-12-04
      • 1970-01-01
      • 2013-08-19
      • 2018-09-17
      • 2013-02-09
      相关资源
      最近更新 更多