如何使用jsoup解析HTML表格?

39

我正在尝试使用jsoup解析HTML。这是我第一次使用jsoup,并且我也阅读了一些关于它的教程。下面是我正在尝试解析的HTML表格 -

如果你看到我的下表,它现在有三个 tr (我缩短了它只为了理解目的,但通常会更多)。现在我想从下面的表中提取 Cluster Name 和相应的 host name,例如 - 我将提取 Titan 作为群集名称以及其状态为 down 的所有主机名。

如您所见,对于 Titan 群集名称,我有两个主机名 machineA.abc.commachineB.abc.com,其中 machineA 的状态为 up,但 machineB 的状态为 down

因此,我将打印出 Titan 作为群集名称,并打印出 machineB.abc.com 作为主机名,因为它是 down。是否可以使用jsoup实现这一点?

<table border=1>
   <tr>
      <td>&nbsp;</td>
      <td>&nbsp;</td>
      <td>Alert</td>
      <td>Cluster Name</td>
      <td>IP addr</td>
      <td>Host Name</td>
      <td>Type</td>
      <td>Status</td>
      <td>Free</td>
      <td>Version</td>
      <td>Restart Time</td>
      <td>UpTime(Days)</td>
      <td>Last probed</td>
      <td>Last up</td>
   </tr>
   <tr bgcolor="ffffff">
      <td><a href=showlog?ip_addr=127.0.0.1>Hist</a></td>
      <td><a href=http://127.0.0.1:8080/test?full=y>VI</a></td>
      <td bgcolor="ffffff">&nbsp</td>
      <td>Titan</td>
      <td>10.100.111.77</td>
      <td>machineA.abc.com</td>
      <td></td>
      <td bgcolor="ffffff">up</td>
      <td bgcolor="ffffff" align=right>88%</td>
      <td bgcolor="ffffff">2.0.5-SNAPSHOT</td>
      <td bgcolor="ffffff">2014-07-04 01:49:08,220</td>
      <td bgcolor="ffffff" align=right>381</td>
      <td>07-14 20:01:59</td>
      <td>07-14 20:01:59</td>
   </tr>
   <tr bgcolor="ffffff">
      <td><a href=showlog?ip_addr=127.0.0.1>Hist</a></td>
      <td><a href=http://127.0.0.1:8080/test?full=y>VI</a></td>
      <td bgcolor="ffffff">&nbsp</td>
      <td></td>
      <td>10.200.192.99</td>
      <td>machineB.abc.com</td>
      <td></td>
      <td bgcolor="ffffff">down</td>
      <td bgcolor="ffffff" align=right>85%</td>
      <td bgcolor="ffffff">2.0.5-SNAPSHOT</td>
      <td bgcolor="ffffff">2014-07-04 01:52:20,613</td>
      <td bgcolor="ffffff" align=right>103</td>
      <td>07-14 20:01:59</td>
      <td>07-14 20:01:59</td>
   </tr>
</table>

目前,我能够使用jsoup提取整个HTML表格,但不确定如何提取集群名称和宕机的主机名 -

URL url = new URL("url_name");
Document doc = Jsoup.parse(url, 3000);

更新:

我在下面的表格中可能会有两个群集名称 -

<table border=1>
   <tr>
      <td>&nbsp;</td>
      <td>&nbsp;</td>
      <td>Alert</td>
      <td>Cluster Name</td>
      <td>IP addr</td>
      <td>Host Name</td>
      <td>Type</td>
      <td>Status</td>
      <td>Free</td>
      <td>Version</td>
      <td>Restart Time</td>
      <td>UpTime(Days)</td>
      <td>Last probed</td>
      <td>Last up</td>
   </tr>
   <tr bgcolor="ffffff">
      <td><a href=showlog?ip_addr=127.0.0.1>Hist</a></td>
      <td><a href=http://127.0.0.1:8080/test?full=y>VI</a></td>
      <td bgcolor="ffffff">&nbsp</td>
      <td>Titan</td>
      <td>10.100.111.77</td>
      <td>machineA.abc.com</td>
      <td></td>
      <td bgcolor="ffffff">up</td>
      <td bgcolor="ffffff" align=right>88%</td>
      <td bgcolor="ffffff">2.0.5-SNAPSHOT</td>
      <td bgcolor="ffffff">2014-07-04 01:49:08,220</td>
      <td bgcolor="ffffff" align=right>381</td>
      <td>07-14 20:01:59</td>
      <td>07-14 20:01:59</td>
   </tr>
   <tr bgcolor="ffffff">
      <td><a href=showlog?ip_addr=127.0.0.1>Hist</a></td>
      <td><a href=http://127.0.0.1:8080/test?full=y>VI</a></td>
      <td bgcolor="ffffff">&nbsp</td>
      <td></td>
      <td>10.200.192.99</td>
      <td>machineB.abc.com</td>
      <td></td>
      <td bgcolor="ffffff">down</td>
      <td bgcolor="ffffff" align=right>85%</td>
      <td bgcolor="ffffff">2.0.5-SNAPSHOT</td>
      <td bgcolor="ffffff">2014-07-04 01:52:20,613</td>
      <td bgcolor="ffffff" align=right>103</td>
      <td>07-14 20:01:59</td>
      <td>07-14 20:01:59</td>
   </tr>
   <tr bgcolor="ffffff">
      <td><a href=showlog?ip_addr=127.0.0.1>Hist</a></td>
      <td><a href=http://127.0.0.1:8080/test?full=y>VI</a></td>
      <td bgcolor="ffffff">&nbsp</td>
      <td>Goldy</td>
      <td>10.100.111.77</td>
      <td>machineH.pqr.com</td>
      <td></td>
      <td bgcolor="ffffff">up</td>
      <td bgcolor="ffffff" align=right>88%</td>
      <td bgcolor="ffffff">2.0.5-SNAPSHOT</td>
      <td bgcolor="ffffff">2014-07-04 01:49:08,220</td>
      <td bgcolor="ffffff" align=right>381</td>
      <td>07-14 20:01:59</td>
      <td>07-14 20:01:59</td>
   </tr>       
</table>

现在,如果你看到上面,我有两个群集名称——一个是Titan,另一个是Goldy,因此我想找出所有仅针对Titan群集名称宕机的机器。


对于任何处理 HTML 片段的人,需要注意一点:JSoup 无法解析没有 <table><tr>。最简单的解决方案是在解析之前用 <table></table> 包装字符串。 - Nand
3个回答

54
是的,使用JSoup可以实现。首先,选择表格,然后选择<tr>标记用于行。您可以从第二个索引开始,因为第一行仅包含列名称。然后循环遍历<th>标记并获取特定索引。在您的情况下,索引7和5很重要(索引7:状态,索引5:主机名)。检查状态是否等于down,如果是,则将主机名添加到列表中。就这些。
ArrayList<String> downServers = new ArrayList<>();
Element table = doc.select("table").get(0); //select the first table.
Elements rows = table.select("tr");

for (int i = 1; i < rows.size(); i++) { //first row is the col names so skip it.
    Element row = rows.get(i);
    Elements cols = row.select("td");

    if (cols.get(7).text().equals("down")) {
        downServers.add(cols.get(5).text());
    }
}

更新:当您找到单词Titan时,您可以创建另一个循环并查看集群名称是否为空。

编辑:我将while循环更改为do while循环。

    ArrayList<String> downServers = new ArrayList<>();
    Element table = doc.select("table").get(0); //select the first table.
    Elements rows = table.select("tr");

    for (int i = 1; i < rows.size(); i++) { //first row is the col names so skip it.
        Element row = rows.get(i);
        Elements cols = row.select("td");

        if (cols.get(3).text().equals("Titan")) {
            if (cols.get(7).text().equals("down"))
                downServers.add(cols.get(5).text());

            do {
                if(i < rows.size() - 1)
                   i++;
                row = rows.get(i);
                cols = row.select("td");
                if (cols.get(7).text().equals("down") && cols.get(3).text().equals("")) {
                    downServers.add(cols.get(5).text());
                }
                if(i == rows.size() - 1)
                    break;
            }
            while (cols.get(3).text().equals(""));
            i--; //if there is two Titan names consecutively.
        }
    }

downServers ArrayList将包含宕机服务器主机名的列表。


是的,它运行良好。但是有一个问题。我刚刚更新了这个问题。我需要仅查找Titan群集名称下线的服务器。这也可能吗? - john
是的,您可以更改if条件。如果(cols.get(7).text()。equals(“down”)&& cols.get(3).text()。equals(“Titan”)) - user2640782
由于群集名称仅在第一行中出现,然后是与该群集相关的主机名称,然后是另一个群集名称和其主机名称,因此某种原因它无法工作。 - john
看到更新了,我没有测试过,但我认为它会适用于你的情况。 - user2640782
我刚试了一下,但不知为什么它不起作用。有什么想法是哪里出了问题吗? - john
糟糕,我的错。请将“if(i > rows.size() - 1)”更改为“if(i < rows.size() - 1)”。 - user2640782

7
在您的情况下,我会首先创建一个具有所有适当属性的机器对象。然后使用Jsoup提取数据并创建一个ArrayList,最后使用逻辑从ArrayList中获取数据。
我跳过了对象创建(因为这不是这里的问题),并将对象命名为Machine
然后,使用Jsoup可以像这样获取行数据:
ArrayList<Machine> list = new ArrayList();
Document doc = Jsoup.parse(url, 3000);
for (Element table : doc.select("table")) { //this will work if your doc contains only one table element
  for (Element row : table.select("tr")) {
    Machine tmp = new Machine();
    Elements tds = row.select("td");
    tmp.setClusterName(tds.get(3).text());
    tmp.setIp(tds.get(4).text());
    tmp.setStatus(tds.get(7).text());
    //.... and so on for the rest of attributes
    list.add(tmp);
  }
}

然后使用循环从列表中获取所需的值:
for(Machine x:list){
  if(x.getStatus().equalsIgnoreCase("up")){
    //machine with UP status found
    System.out.println("The Machine with up status is:"+x.getHostName());
  }
}

就这些。请注意,这段代码没有经过测试,可能包含一些语法错误,因为它是直接在编辑器中编写的,而不是在IDE中编写的。


这不将第一行视为标题,因此请使用:https://dev59.com/kW025IYBdhLWcg3w76lq#5710085 - Moshisho

1
下面是一个干净的通用函数,用于将HTML表格提取到简单的列表映射结构中。
将文档传递给此函数,并按照表格顺序请求HTML页面中的第n个表格。
如果表格使用了rowspan或colspan,则该函数将不返回准确的数据。
public static List<Map<String,String>> parseTable(Document doc, int tableOrder) {
    Element table = doc.select("table").get(tableOrder);
    Elements rows = table.select("tr");
    Elements first = rows.get(0).select("th,td");

    List<String> headers = new ArrayList<String>();
    for(Element header : first)
        headers.add(header.text());

    List<Map<String,String>> listMap = new ArrayList<Map<String,String>>();
    for(int row=1;row<rows.size();row++) {
        Elements colVals = rows.get(row).select("th,td");
        //check column size here

        int colCount = 0;
        Map<String,String> tuple = new HashMap<String,String>();
        for(Element colVal : colVals)
            tuple.put(headers.get(colCount++), colVal.text());
        System.out.println(tuple.toString());
        listMap.add(tuple);
    }
    return listMap;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接