使用 Jsoup 在 Java 中解析 HTML 表
1. 概述
Jsoup 是一個用於抓取 HTML 頁面的開源程式庫。它提供了一個使用 DOM API 方法進行資料解析、提取和操作的 API。
在本文中,我們將了解如何使用 Jsoup 解析 HTML 表。我們將使用 Jsoup 從 HTML 表中檢索和更新數據,並新增和刪除表中的行。
2. 依賴關係
若要使用 Jsoup 庫,請將以下相依性新增至專案:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
我們可以在 Maven 中央儲存庫中找到最新版本的Jsoup庫。
3. 表結構
為了說明如何透過 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 文件中的表格採用上述格式。
4. 解析表
首先,要從解析的文檔中選擇 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 th
。
下面是解析表的程式碼:
public List<Map<String, String>> parseTable(Document doc, int tableOrder) {
Element table = doc.select("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
。該Map
將列名儲存為鍵,並將該列的行值儲存為映射值。使用Maps
列表可以輕鬆存取檢索到的資料。
列表索引代表行號,我們可以透過其映射鍵來取得特定的單元格資料。
我們可以使用下面的測試案例來驗證table
資料是否正確檢索:
@Test
public void whenDocumentTableParsed_thenTableDataReturned() {
JsoupTableParser jsoParser = new JsoupTableParser();
Document doc = jsoParser.loadFromFile("Students.html");
List<Map<String, String>> tableData = jsoParser.parseTable(doc, 0);
assertEquals("90", tableData.get(0).get("Maths"));
}
從 JUnit 測試案例中,我們可以確認,由於我們已經解析了所有表格單元格的文字並將其儲存在HashMap
物件的ArrayList
中,因此清單中的每個元素代表表中的一個資料行。行由HashMap
表示,其中鍵作為列標題,單元格文字作為值。使用這個結構,我們可以輕鬆存取表格資料。
5. 更新解析表的元素
要在解析時插入或更新元素,我們可以在從行檢索的td
元素上使用以下程式碼:
colVals.get(colCount++).text(updateValue);
或者
colVals.get(colCount++).html(updateValue);
更新解析表中的值的函數如下所示:
public void updateTableData(Document doc, int tableOrder, String updateValue) {
Element table = doc.select("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
元素取得資料行。此函數遍歷table
的每個單元格並將其值設為參數值updatedValue
。它將所有單元格更新為相同的值,以演示可以使用 Jsoup 更新單元格值。我們可以透過指定資料行的行索引和列索引來更新各個單元格的值。
下面的測試驗證了更新功能:
@Test
public void whenTableUpdated_thenUpdatedDataReturned() {
JsoupTableParser jsoParser = new JsoupTableParser();
Document doc = jsoParser.loadFromFile("Students.html");
jsoParser.updateTableData(doc, 0, "50");
List<Map<String, String>> tableData = jsoParser.parseTable(doc, 0);
assertEquals("50", tableData.get(2).get("Maths"));
}
JUnit 測試案例確認更新操作將所有table
單元格值更新為 50。這裡我們驗證 Maths 欄位的第三個資料行的資料。
同樣,我們可以為表格的特定儲存格設定所需的值。
6.向表格中新增一行
我們可以使用以下函數在表中新增一行:
public void addRowToTable(Document doc, int tableOrder) {
Element table = doc.select("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("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。該操作在列表末尾添加了一個新行。
類似地,我們可以在將行新增至行元素集合時透過指定索引來在任意位置新增行。
7. 從表中刪除行
我們可以使用以下函數從表中刪除一行:
public void deleteRowFromTable(Document doc, int tableOrder, int rowNumber) {
Element table = doc.select("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,
我們刪除table
中rowNumber
位置的行。我們可以使用以下測試案例來驗證行刪除:
@Test
public void whenTableRowDeleted_thenRowCountDecreased() {
JsoupTableParser jsoParser = new JsoupTableParser();
Document doc = jsoParser.loadFromFile("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。
同樣,我們可以透過指定索引來刪除任意位置的行,同時將其從row
元素集合中刪除。
八、結論
在本文中,我們了解如何使用 jsoup 從 HTML 文件中解析 HTML 表。此外,我們可以更新表結構以及表單元格資料。與往常一樣,這些範例的原始程式碼可以在 GitHub 上取得。