杰瑞科技汇

Java DOM如何高效解析HTML?

标准的 Java DOM 解析器(如 javax.xml.parsers.DocumentBuilder)是为设计良好、格式规范的 XML 文档设计的,而 HTML 通常结构松散、可能包含不闭合的标签、大小写不敏感等,直接使用标准的 XML DOM 解析器来解析 HTML 很容易失败或抛出异常。

Java DOM如何高效解析HTML?-图1
(图片来源网络,侵删)

我们需要使用专门为 HTML 设计的 DOM 解析器,目前最流行和推荐的库是 Jsoup,它非常强大、易用,并且专门处理了 HTML 的各种不规范情况。

下面我将分两部分介绍:

  1. 推荐方案:使用 Jsoup(强烈推荐)
  2. 不推荐方案:使用标准 Java DOM(仅用于理解原理)

使用 Jsoup(强烈推荐)

Jsoup 是一个开源的 Java HTML 解析库,它提供了非常直观的 API,类似于 jQuery 的 CSS 选择器,非常适合从网页中提取和操作数据。

添加 Jsoup 依赖

如果你使用 Maven,在 pom.xml 中添加以下依赖:

Java DOM如何高效解析HTML?-图2
(图片来源网络,侵删)
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.17.2</version> <!-- 请使用最新版本 -->
</dependency>

如果你使用 Gradle,在 build.gradle 中添加:

implementation 'org.jsoup:jsoup:1.17.2' // 请使用最新版本

Jsoup 核心功能与示例

假设我们有如下 HTML 内容:

<!DOCTYPE html>
<html>
<head>Jsoup 示例</title>
</head>
<body>
    <h1>欢迎来到我的网站</h1>
    <div class="content">
        <p>这是一个段落。</p>
        <p>这是另一个段落,包含一个 <a href="https://jsoup.org/" id="jsoup-link">链接</a>。</p>
    </div>
    <ul>
        <li>项目 1</li>
        <li>项目 2</li>
    </ul>
</body>
</html>

示例 1:解析 HTML 字符串并获取标题

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
public class JsoupParseFromString {
    public static void main(String[] args) {
        String html = "<html><head><title>Jsoup 示例</title></head><body><p>一个段落。</p></body></html>";
        // 1. 解析 HTML 字符串,返回一个 Document 对象
        Document doc = Jsoup.parse(html);
        // 2. 通过标签名获取元素 (获取 <title> 标签)
        Element titleElement = doc.title(); // 有便捷方法直接获取 title
        // 或者 Element titleElement = doc.select("title").first();
        System.out.println("网页标题是: " + titleElement.text()); // 输出: 网页标题是: Jsoup 示例
    }
}

示例 2:使用 CSS 选择器提取数据

这是 Jsoup 最强大的功能,选择器语法和 CSS 一样。

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.List;
public class JsoupSelectExample {
    public static void main(String[] args) {
        String html = "<html><head><title>Jsoup 示例</title></head>" +
                      "<body><h1>欢迎来到我的网站</h1>" +
                      "<div class='content'><p>这是一个段落。</p><p>这是另一个段落。</p></div>" +
                      "<ul><li>项目 1</li><li>项目 2</li></ul></body></html>";
        Document doc = Jsoup.parse(html);
        // a. 获取所有 <li> 元素
        Elements listItems = doc.select("li");
        System.out.println("--- 所有列表项 ---");
        for (Element li : listItems) {
            System.out.println("列表项: " + li.text());
        }
        // b. 获取 class 为 'content' 的 div 下的所有 <p> 元素
        Elements paragraphsInContent = doc.select("div.content p");
        System.out.println("\n--- content div 中的段落 ---");
        for (Element p : paragraphsInContent) {
            System.out.println("段落: " + p.text());
        }
        // c. 获取 id 为 'jsoup-link' 的 <a> 标签的 href 属性
        // 假设 HTML 中有这个链接
        String htmlWithLink = "<a href='https://jsoup.org/' id='jsoup-link'>链接</a>";
        Document docWithLink = Jsoup.parse(htmlWithLink);
        Element link = docWithLink.select("#jsoup-link").first(); // #id 选择器
        String linkUrl = link.attr("href"); // .attr() 方法获取属性
        System.out.println("\n--- 链接信息 ---");
        System.out.println("链接地址: " + linkUrl);
        System.out.println("链接文本: " + link.text());
        // d. 组合选择器:获取 div.content 中的第一个 <a> 标签
        // 假设 HTML 中有这个结构
        String complexHtml = "<div class='content'><p>文本和 <a href='#'>一个链接</a>。</p></div>";
        Document complexDoc = Jsoup.parse(complexHtml);
        Element firstLink = complexDoc.select("div.content a").first();
        System.out.println("\n--- 第一个链接 ---");
        System.out.println(firstLink);
    }
}

示例 3:从 URL 获取并解析 HTML

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
public class JsoupFromUrl {
    public static void main(String[] args) {
        try {
            // 从 URL 解析 HTML,可以设置超时时间和 User-Agent
            Document doc = Jsoup.connect("https://jsoup.org/")
                               .userAgent("Mozilla/5.0")
                               .timeout(10000)
                               .get();
            // 获取页面标题
            String title = doc.title();
            System.out.println("页面标题: " + title);
            // 获取页面的所有文本内容
            String text = doc.body().text();
            System.out.println("\n页面文本内容 (前100字符):");
            System.out.println(text.length() > 100 ? text.substring(0, 100) + "..." : text);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例 4:从文件加载 HTML

假设你有一个 index.html 文件。

Java DOM如何高效解析HTML?-图3
(图片来源网络,侵删)
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.File;
import java.io.IOException;
public class JsoupFromFile {
    public static void main(String[] args) {
        try {
            File input = new File("path/to/your/index.html");
            Document doc = Jsoup.parse(input, "UTF-8"); // 指定编码
            System.out.println("从文件解析的标题: " + doc.title());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用标准 Java DOM(不推荐,仅作了解)

如果你因为某些特殊原因(如不能引入第三方库)必须使用 Java 自带的 DOM 解析器,你需要先想办法将 HTML 转换为格式良好的 XML,这通常非常困难,因为 HTML 的不规范之处太多,一个折中的办法是移除或修复一些常见的“坏”标签(如 <br>, <img> 等),但这不是通用的解决方案。

以下是一个演示,它很可能失败,除非你的 HTML 文件格式非常完美。

准备工作

假设你有一个格式完美的 my_page.html 文件,它看起来像一个 XML 文件。

<!-- my_page.html -->
<html>
  <head>标准 DOM 示例</title>
  </head>
  <body>
    <h1>你好</h1>
    <p>这是一个段落。</p>
  </body>
</html>

Java 代码

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
public class JavaDomParser {
    public static void main(String[] args) {
        // 1. 创建 DocumentBuilderFactory 和 DocumentBuilder
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 2. 解析 XML (HTML) 文件,得到 Document 对象
            // 注意:HTML 不规范,这里会抛出 SAXException
            Document document = builder.parse(new File("my_page.html"));
            // 3. 获取文档的根元素
            Element rootElement = document.getDocumentElement();
            System.out.println("根元素是: " + rootElement.getNodeName());
            // 4. 获取所有 <p> 标签
            NodeList pList = document.getElementsByTagName("p");
            System.out.println("\n找到 " + pList.getLength() + " 个 <p> 标签:");
            for (int i = 0; i < pList.getLength(); i++) {
                Element pElement = (Element) pList.item(i);
                // 获取 <p> 标签内的文本内容
                // textContent 可以获取标签及其所有子标签的文本
                System.out.println("  段落 " + (i + 1) + ": " + pElement.getTextContent());
            }
            // 5. 获取 <title> 标签的文本
            NodeList titleList = document.getElementsByTagName("title");
            if (titleList.getLength() > 0) {
                Element titleElement = (Element) titleList.item(0);
                System.out.println("\n页面标题: " + titleElement.getTextContent());
            }
        } catch (ParserConfigurationException e) {
            System.err.println("解析器配置错误: " + e.getMessage());
        } catch (SAXException e) {
            System.err.println("解析 HTML 时发生 SAX 错误 (通常是 HTML 格式不正确): " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("读取文件时发生 IO 错误: " + e.getMessage());
        }
    }
}

总结与对比

特性 Jsoup (推荐) 标准 Java DOM (不推荐)
易用性 极高,类似 jQuery 的选择器,非常直观 ,需要遍历节点,代码冗长
健壮性 极高,能完美处理不规范、破损的 HTML 极低,对格式要求严格,HTML 稍有不规范就会报错
功能 强大,除了解析,还支持数据修改、输出 HTML、清理等 基本,仅支持读取和简单遍历
依赖 需要引入第三方库 (Maven/Gradle) 无需额外依赖,JDK 自带
适用场景 所有 HTML 解析需求,如网络爬虫、数据抓取 极少数情况,如解析一个已知格式完美且无法引入外部库的类 XML 文件

对于任何 Java 项目中需要解析 HTML 的场景,都应该毫不犹豫地选择 Jsoup,它解决了标准 DOM 解析器处理 HTML 时的一切痛点,提供了现代化、高效且强大的 API,能让你用最少的代码完成最多的工作。

分享:
扫描分享到社交APP
上一篇
下一篇