Java中使用Jsoup解析HTML表格教程

banq發表於2024-03-17

Jsoup是一個用於抓取 HTML 頁面的開源庫。它提供了一個使用 DOM API 方法進行資料解析、提取和操作的 API。

在本文中,我們將瞭解如何使用 Jsoup 解析 HTML 表。我們將使用 Jsoup 從 HTML 表中檢索和更新資料,並新增和刪除表中的行。

要使用 Jsoup 庫,請將以下依賴項新增到專案中:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.17.2</version>
</dependency>

我們可以在 Maven 中央儲存庫中找到最新版本的Jsoup庫。

表結構
為了說明如何透過 jsoup 解析 HTML 表,我們將使用一個示例 HTML 結構。完整的 HTML 結構可在本文末尾提到的 GitHub 儲存庫中提供的程式碼庫中找到。在這裡,我們顯示一個僅包含兩行資料的表格,用於代表性目的:

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Maths</th>
            <th>English</th>
            <th>Science</th>
         </tr>
    </thead>
    <tbody>
        <tr>
            <td>Student 1</td>
            <td>90</td>
            <td>85</td>
            <td>92</td>
        </tr>
     </tbody>
</table>

正如我們所看到的,我們正在解析帶有帶有thead標記的標題行,後跟tbody標記中的資料行的表。我們假設 HTML 文件中的表格採用上述格式。

解析表
首先,要從解析的文件中選擇 HTML 表,我們可以使用下面的程式碼片段:

Element table = doc.select("table");
Elements rows = table.select("tr"); 
Elements first = rows.get(0).select("th,td");
正如我們所看到的,從文件中選擇表元素,然後,為了獲取行元素,從表元素中選擇tr 。由於表中有多行,因此我們選擇了第一行中的th或td元素。透過使用這些函式,我們可以編寫以下函式來解析表資料。

在這裡,我們假設表中沒有使用colspan或rowspan元素,並且第一行帶有 header標籤。

下面是解析表的程式碼:

public List<Map<String, String>> parseTable(Document doc, int tableOrder) {
    Element table = doc.select(<font>"table").get(tableOrder);
    Element tbody = table.select(
"tbody").get(0);
    Elements dataRows = tbody.select(
"tr");
    Elements headerRow = table.select(
"tr")
      .get(0)
      .select(
"th,td");
    List<String> headers = new ArrayList<String>();
    for (Element header : headerRow) {
        headers.add(header.text());
    }
    List<Map<String, String>> parsedDataRows = new ArrayList<Map<String, String>>();
    for (int row = 0; row < dataRows.size(); row++) {
        Elements colVals = dataRows.get(row).select(
"th,td");
        int colCount = 0;
        Map<String, String> dataRow = new HashMap<String, String>();
        for (Element colVal : colVals) {
            dataRow.put(headers.get(colCount++), colVal.text());
        }
        parsedDataRows.add(dataRow);
    }
    return parsedDataRows;
}

在此函式中,引數doc是從檔案載入的 HTML 文件,tableOrder是文件中的第 n 個表格元素。我們使用List<Map<String, String>>在tbody元素下的表中儲存dataRows 列表。列表的每個元素都是一個代表dataRow的Map。該對映將列名儲存為鍵,並將該列的行值儲存為對映值。使用地圖列表可以輕鬆訪問檢索到的資料。

列表索引代表行號,我們可以透過其對映鍵獲取特定的單元格資料。

我們可以使用下面的測試用例驗證表資料是否正確檢索:

@Test
public void whenDocumentTableParsed_thenTableDataReturned() {
    JsoupTableParser jsoParser = new JsoupTableParser();
    Document doc = jsoParser.loadFromFile(<font>"Students.html");
    List<Map<String, String>> tableData = jsoParser.parseTable(doc, 0);
    assertEquals(
"90", tableData.get(0).get("Maths")); 
}

從 JUnit 測試用例中,我們可以確認,由於我們已經解析了所有表格單元格的文字並將其儲存在HashMap物件的ArrayList中,因此列表中的每個元素代表表中的一個資料行。行由 HashMap 表示,其中鍵作為列標題,單元格文字作為值。使用這個結構,我們可以輕鬆訪問表資料。

更新解析表的元素
要在解析時插入或更新元素,我們可以在從行檢索的td元素上使用以下程式碼:

colVals.get(colCount++).text(updateValue);
或者

colVals.get(colCount++).html(updateValue);
更新解析表中的值的函式如下所示:

public void updateTableData(Document doc, int tableOrder, String updateValue) {
    Element table = doc.select(<font>"table").get(tableOrder);
    Element tbody = table.select(
"tbody").get(0);
    Elements dataRows = tbody.select(
"tr");
    for (int row = 0; row < dataRows.size(); row++) {
        Elements colVals = dataRows.get(row).select(
"th,td");
        for (int colCount = 0; colCount < colVals.size(); colCount++) {
            colVals.get(colCount).text(updateValue);
        }
    }
}

在上面的函式中,我們從表的tbody元素獲取資料行。該函式遍歷表的每個單元格並將其值設定為引數值UpdatedValue。它將所有單元格更新為相同的值,以演示可以使用 Jsoup 更新單元格值。我們可以透過指定資料行的行索引和列索引來更新各個單元格的值。

下面的測試驗證了更新功能:

@Test
public void whenTableUpdated_thenUpdatedDataReturned() {
    JsoupTableParser jsoParser = new JsoupTableParser();
    Document doc = jsoParser.loadFromFile(<font>"Students.html");
    jsoParser.updateTableData(doc, 0,
"50");
    List<Map<String, String>> tableData = jsoParser.parseTable(doc, 0);
    assertEquals(
"50", tableData.get(2).get("Maths"));
}

JUnit 測試用例確認更新操作將所有表格單元格值更新為 50。這裡我們驗證 Maths 列的第三個資料行的資料。

同樣,我們可以為表格的特定單元格設定所需的值。

向表中新增行
我們可以使用以下函式向表中新增一行:

public void addRowToTable(Document doc, int tableOrder) {
    Element table = doc.select(<font>"table").get(tableOrder);
    Element tbody = table.select(
"tbody").get(0);
    Elements rows = table.select(
"tr");
    Elements headerCols = rows.get(0).select(
"th,td");
    int numCols = headerCols.size();
    Elements colVals = new Elements(numCols);
    for (int colCount = 0; colCount < numCols; colCount++) {
        Element colVal = new Element(
"td");
        colVal.text(
"11");
        colVals.add(colVal);
    }
    Elements dataRows = tbody.select(
"tr");
    Element newDataRow = new Element(
"tr");
    newDataRow.appendChildren(colVals);
    dataRows.add(newDataRow);
    tbody.html(dataRows.toString());
}

在上面的函式中,我們從表的標題行獲取列數,從表的tbody元素獲取資料行數。將新行新增到dataRows列表後,我們使用dataRows更新tbody HTML 內容。

我們可以使用以下測試用例驗證行新增:

@Test
public void whenTableRowAdded_thenRowCountIncreased() {
    JsoupTableParser jsoParser = new JsoupTableParser();
    Document doc = jsoParser.loadFromFile(<font>"Students.html");
    List<Map<String, String>> tableData = jsoParser.parseTable(doc, 0);
    int countBeforeAdd = tableData.size();
    jsoParser.addRowToTable(doc, 0);
    tableData = jsoParser.parseTable(doc, 0);
    assertEquals(countBeforeAdd + 1, tableData.size());
}

從JUnit測試用例中我們可以確認,對錶進行addRowToTable操作將表中的行數增加了1。該操作在列表末尾新增了一個新行。

類似地,我們可以在將行新增到行元素集合時透過指定索引來在任意位置新增行。

從表中刪除行
我們可以使用以下函式從表中刪除一行:

public void deleteRowFromTable(Document doc, int tableOrder, int rowNumber) {
    Element table = doc.select(<font>"table").get(tableOrder);
    Element tbody = table.select(
"tbody").get(0);
    Elements dataRows = tbody.select(
"tr");
    if (rowNumber < dataRows.size()) {
        dataRows.remove(rowNumber);
    }
}

在上面的函式中,我們獲取表的tbody元素。從tbody中,我們獲得了dataRows列表。從 dataRows 列表中,我們刪除表中 rowNumber 位置的行。我們可以使用以下測試用例來驗證行刪除:

@Test
public void whenTableRowDeleted_thenRowCountDecreased() {
    JsoupTableParser jsoParser = new JsoupTableParser();
    Document doc = jsoParser.loadFromFile(<font>"Students.html");
    List<Map<String, String>> tableData = jsoParser.parseTable(doc, 0);
    int countBeforeDel = tableData.size();
    jsoParser.deleteRowFromTable(doc, 0, 2);
    tableData = jsoParser.parseTable(doc, 0);
    assertEquals(countBeforeDel - 1, tableData.size());
}

JUnit 測試用例確認對錶執行deleteRowFromTable操作將表中的行數減少了1。

同樣,我們可以透過指定索引來刪除任意位置的行,同時將其從行 元素集合中刪除。
 

相關文章