java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Jsoup解析HTML

Java如何使用Jsoup库轻松解析HTML网页数据

作者:西凉的悲伤

本文将详细介绍Java中Jsoup库的用法,包括HTML解析、CSS选择器提取数据、网页抓取、DOM修改以及富文本清洗防XSS,帮你轻松搞定HTML处理任务

一、什么是 Jsoup?

在 Java 后端开发中,我们经常会遇到处理 HTML 的需求,例如:

如果使用传统的字符串截取、正则表达式等方式处理 HTML,代码通常比较复杂,而且难以应对标签嵌套、属性顺序变化、HTML 格式不规范等情况。

Jsoup 就是一个专门用于处理 HTML 的 Java 开源库。

它能够将 HTML 字符串解析为结构化的 DOM 文档,提供类似 jQuery 的 CSS 选择器语法,让开发者方便地查询、遍历、修改 HTML 元素,还能直接请求网页并解析响应内容。

Jsoup 的主要能力包括:

  1. HTML 解析:将 HTML 字符串、文件或网络响应解析为 DOM 文档。
  2. DOM 操作:查询、遍历、添加、删除和修改 HTML 元素。
  3. CSS 选择器查询:通过标签、ID、class、属性等条件快速定位元素。
  4. 网页抓取:请求指定 URL,获取 HTML 页面并提取需要的数据。
  5. HTML 清洗:通过白名单策略过滤不允许的标签和属性,降低 XSS 风险。

可以简单理解为:Jsoup 相当于 Java 中的 HTML 解析器,加上类似 jQuery 的 DOM 查询与操作能力,同时提供了便捷的网页抓取功能。

需要注意,Jsoup 主要处理 HTML 文档,并不是完整的浏览器。它不会像浏览器一样执行页面中的 JavaScript,也不负责页面渲染。

二、Jsoup 能解决什么问题?

2.1 网页数据提取

假设需要从某个网页中提取商品名称、价格、文章标题或超链接。

如果直接使用字符串截取或正则表达式,可能需要考虑很多 HTML 格式细节。

使用 Jsoup,只需要通过 CSS 选择器定位目标元素,然后读取其文本或属性即可。

例如:

Document doc = Jsoup.parse(html);

// 获取所有 class 为 product-name 的元素
Elements elements = doc.select(".product-name");

for (Element element : elements) {
    System.out.println(element.text());
}

2.2 HTML 内容修改

假设后端接收到一段 HTML,需要修改其中的标题、替换文本,或者为指定元素增加 CSS class。

Jsoup 可以直接操作 DOM,而不需要自己拼接字符串。

Document doc = Jsoup.parse(html);

Element title = doc.selectFirst("h1");

if (title != null) {
    title.text("新的标题");
}

2.3 富文本清洗与 XSS 防护

用户提交的富文本可能包含不安全的标签和属性,例如:

<p>正常内容</p>
<script>alert('xss')</script>
<img src="x" onerror="alert('xss')">

如果将未经处理的内容直接输出到网页中,可能引发跨站脚本攻击(XSS)。

Jsoup 提供了 Jsoup.clean() 方法,可以按照指定的白名单规则清洗 HTML。

String dirtyHtml =
        "<p>正常内容</p>"
        + "<script>alert('xss')</script>"
        + "<img src='x' onerror='alert(1)'>";

String cleanHtml = Jsoup.clean(
        dirtyHtml,
        Safelist.basic()
);

System.out.println(cleanHtml);

清洗后,危险的 script 标签和不允许的事件属性会被过滤。

需要注意,具体输出取决于白名单规则。HTML 清洗是 XSS 防护的一部分,还需要结合输出上下文、前端渲染方式和整体安全策略使用。

三、如何引入 Jsoup?

Jsoup 可以通过 Maven 引入,也可以使用 Gradle 管理依赖。

3.1 Maven 依赖

在项目的 pom.xml 中添加:

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

这里使用的是示例版本 1.17.2。在实际项目中,建议根据 JDK 版本、项目依赖约束和安全更新情况选择合适的 Jsoup 版本。

添加依赖后,Maven 会自动下载相关 JAR 包。

3.2 Gradle 依赖

如果项目使用 Gradle,可以在 build.gradle 中添加:

dependencies {
    implementation 'org.jsoup:jsoup:1.17.2'
}

3.3 常用核心类

Jsoup 中有几个比较重要的类,学习时建议重点掌握。

类或对象作用
Jsoup提供解析 HTML、连接网页和清洗 HTML 的静态方法
Document表示完整的 HTML 文档
Element表示一个 HTML 元素,例如 div、a、p
Elements表示多个 HTML 元素组成的集合
Connection表示网页请求配置和执行过程
Safelist定义 HTML 清洗时允许保留的标签、属性等规则

它们之间的关系可以理解为:

HTML 字符串 / HTML 文件 / 网页响应
                 |
                 v
              Jsoup
                 |
                 v
             Document
                 |
                 v
       CSS 选择器 select()
                 |
                 v
        Elements / Element
                 |
          +------+------+
          |             |
          v             v
      读取内容       修改 DOM

四、HTML 解析与 CSS 选择器

这是 Jsoup 最核心、最常用的功能。

4.1 将 HTML 字符串解析为 Document

先准备一段 HTML:

String html =
        "<html>"
        + "<head><title>Jsoup 学习</title></head>"
        + "<body>"
        + "<div id='main' class='content'>"
        + "<h1>Jsoup 入门</h1>"
        + "<p>这是第一段内容</p>"
        + "<a href='https://example.com'>官方网站</a>"
        + "</div>"
        + "</body>"
        + "</html>";

通过 Jsoup.parse() 将其解析为 DOM 文档:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class JsoupParseDemo {

    public static void main(String[] args) {
        String html =
                "<html><head><title>Jsoup 学习</title></head>"
                + "<body><h1>Jsoup 入门</h1></body></html>";

        Document doc = Jsoup.parse(html);

        System.out.println(doc.title());
        System.out.println(doc.selectFirst("h1").text());
    }
}

输出:

Jsoup 学习
Jsoup 入门

其中:

注意: selectFirst() 可能返回 null。如果目标元素不一定存在,调用 .text() 前应进行判空。

4.2 使用 CSS 选择器查询元素

Jsoup 支持常见的 CSS 选择器语法。

假设有如下 HTML:

<div id="main" class="content">
    <h1>Jsoup 入门</h1>
    <a href="https://a.example.com" rel="external nofollow" >链接 A</a>
    <a href="https://b.example.com" rel="external nofollow" >链接 B</a>
</div>

常见选择器如下:

选择器含义
div查询所有 div 元素
#main查询 ID 为 main 的元素
.content查询 class 包含 content 的元素
div.content查询同时满足标签和 class 条件的 div
div a查询 div 内部的所有后代 a 元素
div > a查询 div 的直接子元素 a
a[href]查询具有 href 属性的 a 元素
a[href^=https]查询 href 以 https 开头的 a 元素
a[href$=.pdf]查询 href 以 .pdf 结尾的 a 元素
p:first-child查询作为父元素第一个子元素的 p

例如:

Document doc = Jsoup.parse(html);

// 查询所有链接
Elements links = doc.select("a[href]");

for (Element link : links) {
    System.out.println("文本:" + link.text());
    System.out.println("地址:" + link.attr("href"));
}

// 查询 ID 为 main 的元素
Element main = doc.selectFirst("#main");

// 查询第一个标题
Element title = doc.selectFirst("h1");

select() 返回 Elements,即多个元素的集合;selectFirst() 返回第一个匹配元素,找不到时返回 null。

对于需要反复提取多种信息的业务,可以组合使用 CSS 选择器,减少复杂的 DOM 遍历代码。

4.3 获取文本、属性和 HTML

定位元素后,通常需要读取三类信息。

Document doc = Jsoup.parse(
        "<div id='box'>"
        + "<p>欢迎学习 <b>Jsoup</b></p>"
        + "<a href='https://example.com'>点击访问</a>"
        + "</div>"
);

Element box = doc.selectFirst("#box");

System.out.println(box.text());
System.out.println(box.html());
System.out.println(box.outerHtml());

Element link = doc.selectFirst("a");

System.out.println(link.attr("href"));
System.out.println(link.text());

主要 API:

API说明
element.text()获取元素及其后代元素的文本,并规范化空白字符
element.html()获取元素内部的 HTML
element.outerHtml()获取元素自身及其内部 HTML
element.attr("href")获取指定属性值
element.hasAttr("href")判断是否存在指定属性
element.id()获取 ID 属性
element.className()获取 class 属性

例如,box.text() 获取的是纯文本,而 box.html() 会保留内部的 HTML 标签。

如果需要获取 HTML 片段,应使用 html();如果只需要展示文字或进行文本分析,通常使用 text() 更合适。

五、网页抓取:使用 Jsoup 请求 URL

除了解析已有的 HTML,Jsoup 还可以直接请求网页。

这也是它经常被用于轻量级爬虫的原因。

5.1 请求网页并获取标题

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class JsoupFetchDemo {

    public static void main(String[] args) throws Exception {

        Document doc = Jsoup.connect("https://example.com")
                .userAgent("Mozilla/5.0")
                .timeout(10000)
                .get();

        System.out.println("页面标题:" + doc.title());
    }
}

关键 API 说明:

这里的 example.com 是示例域名,实际使用时应替换为自己有权访问的网页。

5.2 提取网页中的所有链接

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class JsoupLinkDemo {

    public static void main(String[] args) throws Exception {

        Document doc = Jsoup.connect("https://example.com")
                .timeout(10000)
                .get();

        Elements links = doc.select("a[href]");

        for (Element link : links) {
            String text = link.text();
            String href = link.attr("abs:href");

            System.out.println(text + " -> " + href);
        }
    }
}

这里有一个值得注意的细节:

link.attr("href");

读取的是 HTML 中原始的 href 属性值,可能是相对路径。

而:

link.attr("abs:href");

会尝试根据文档的基础 URI,将链接解析为绝对地址。

例如:

<a href="/news/list" rel="external nofollow" >新闻列表</a>

如果页面地址为:https://example.com

那么读取 abs:href 可以得到:https://example.com/news/list

这在网页抓取场景中非常实用。

5.3 设置请求头、超时和响应状态

实际项目中,网页请求可能遇到超时、访问限制或 HTTP 错误。

可以通过 Connection 配置请求:

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class JsoupRequestDemo {

    public static void main(String[] args) throws Exception {

        Connection.Response response = Jsoup.connect(
                        "https://example.com")
                .userAgent("Mozilla/5.0")
                .header("Accept-Language", "zh-CN,zh;q=0.9")
                .timeout(10000)
                .ignoreHttpErrors(true)
                .execute();

        System.out.println("HTTP 状态码:" + response.statusCode());

        if (response.statusCode() >= 200
                && response.statusCode() < 300) {

            Document doc = response.parse();
            System.out.println("页面标题:" + doc.title());
        }
    }
}

ignoreHttpErrors(true) 表示即使服务器返回 HTTP 错误状态,也允许程序读取响应,而不是直接因 HTTP 错误抛出异常。网络连接失败等问题仍然可能抛出异常。

生产环境中还应处理异常、设置合理的超时、控制请求频率,并遵守网站的访问规则和相关法律要求。

另外,Jsoup 不会自动执行 JavaScript。如果目标页面的数据完全由前端 JavaScript 动态生成,直接请求 HTML 可能无法获取最终展示的数据,需要进一步分析页面的数据接口或采用适合的浏览器自动化方案。

六、DOM 操作:修改 HTML 内容

Jsoup 不仅能读取 HTML,也能修改 HTML。

6.1 修改元素文本

Document doc = Jsoup.parse(
        "<div id='main'><p>旧文本</p></div>"
);

Element main = doc.getElementById("main");

Element paragraph = main.selectFirst("p");

if (paragraph != null) {
    paragraph.text("新文本");
}

System.out.println(doc.body().html());

这里使用:

paragraph.text("新文本");

可以将元素的文本内容替换为指定值。

如果传入的字符串中包含类似 HTML 标签的内容,text() 会将其作为文本处理,而不是将其解析成 HTML 元素。

6.2 修改属性

例如,为元素增加 class:

Element main = doc.getElementById("main");

if (main != null) {
    main.attr("class", "highlight");
}

还可以读取、设置或删除属性:

String id = main.attr("id");

main.attr("data-type", "article");

main.removeAttr("data-type");

6.3 添加子元素

Document doc = Jsoup.parse(
        "<div id='main'><p>原有内容</p></div>"
);

Element main = doc.getElementById("main");

if (main != null) {
    main.appendElement("span")
            .text("追加内容");
}

System.out.println(doc.body().html());

appendElement() 会创建一个新的子元素,并返回该元素,之后可以继续设置文本或属性。

如果需要追加一段 HTML 字符串,也可以使用:

main.append("<span>追加内容</span>");

但需要注意:append() 会将输入内容按 HTML 片段解析。对于不可信的用户输入,不应直接将其拼接进 HTML。

6.4 删除元素

Element script = doc.selectFirst("script");

if (script != null) {
    script.remove();
}

还可以根据选择器批量删除元素:

doc.select(".advertisement").remove();

例如,在提取文章正文时,可以删除广告区域、导航栏等无关元素,再获取正文内容。

七、HTML 清洗:使用 Jsoup 过滤不安全内容

HTML 清洗是 Jsoup 一个非常重要的应用场景,尤其适用于评论、文章编辑器和富文本内容管理系统。

7.1 为什么需要 HTML 清洗?

假设用户提交如下内容:

<p>这是一篇正常的文章</p>
<script>alert('xss')</script>
<img src="x" onerror="alert('xss')">
<a href="javascript:alert('xss')" rel="external nofollow" >点击查看</a>

其中可能包含:

如果业务允许用户提交 HTML,又将这些内容直接输出到浏览器,就可能产生安全问题。

7.2 使用 Safelist.basic() 清洗

import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;

public class JsoupCleanDemo {

    public static void main(String[] args) {

        String dirtyHtml =
                "<p>正文内容</p>"
                + "<script>alert('xss')</script>"
                + "<img src='x' onerror='alert(1)'>"
                + "<a href='javascript:alert(1)'>危险链接</a>";

        String cleanHtml = Jsoup.clean(
                dirtyHtml,
                Safelist.basic()
        );

        System.out.println(cleanHtml);
    }
}

Safelist.basic() 是一个预定义的白名单,允许一组常见的基础文本格式和链接标签,同时过滤不在允许范围内的内容。

它并不意味着保留所有 HTML 标签。比如图片、样式、表格等标签或属性是否能够保留,需要根据对应的白名单规则判断。

7.3 常见的 Safelist 配置

配置用途
Safelist.none()不允许保留任何 HTML 标签,适合需要清除 HTML 标记的场景
Safelist.simpleText()允许有限的简单文本格式
Safelist.basic()允许常见基础文本格式和链接
Safelist.basicWithImages()在基础规则上允许图片相关内容
Safelist.relaxed()允许更丰富的 HTML 标签和属性

不同规则的允许范围不同,选择时应结合业务需求。

7.4 自定义白名单

例如,业务需要保留段落、粗体、链接和图片,可以在默认规则上进行扩展:

Safelist safelist = Safelist.basicWithImages()
        .addTags("h1", "h2", "table", "tr", "td")
        .addAttributes("a", "target");

String cleanHtml = Jsoup.clean(
        dirtyHtml,
        safelist
);

这里仅展示如何配置白名单。实际使用时,应进一步评估新增标签、属性和链接的安全影响。

尤其需要注意:

八、从文件或 HTML 片段解析

除了解析普通字符串,Jsoup 还支持读取文件和解析 HTML 片段。

8.1 从 HTML 文件解析

假设项目中存在一个 page.html 文件:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.File;
import java.nio.charset.StandardCharsets;

public class JsoupFileDemo {

    public static void main(String[] args) throws Exception {

        File file = new File("page.html");

        Document doc = Jsoup.parse(
                file,
                StandardCharsets.UTF_8.name()
        );

        System.out.println("标题:" + doc.title());
    }
}

这种方式适合处理本地保存的 HTML 文件。

需要确保文件路径正确,并根据文件的实际编码选择字符集。

8.2 解析 HTML 片段

有时后端拿到的并不是完整的 HTML 文档,而只是一个片段,例如:

<p>第一段</p>
<p>第二段</p>

可以使用:

Document doc = Jsoup.parseBodyFragment(
        "<p>第一段</p><p>第二段</p>"
);

System.out.println(doc.body().text());

parseBodyFragment() 会将传入的内容作为 body 部分的 HTML 片段进行解析。

对于服务端处理富文本、文章内容和用户提交的 HTML 片段,这种方式通常更加直观。

九、综合实战:抓取文章列表

前面的示例分别介绍了 HTML 解析、CSS 选择器、网页请求和属性读取。下面将这些知识组合起来,模拟一个简单的文章列表抓取程序。

假设网页结构如下:

<div class="article-list">
    <div class="article">
        <h2>
            <a href="/article/1" rel="external nofollow" >Java 入门</a>
        </h2>
        <p class="summary">Java 基础知识介绍</p>
    </div>
    <div class="article">
        <h2>
            <a href="/article/2" rel="external nofollow" >Spring Boot 入门</a>
        </h2>
        <p class="summary">Spring Boot 开发入门</p>
    </div>
</div>

我们希望提取每篇文章的标题、链接和摘要。

9.1 实现代码

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class ArticleCrawlerDemo {

    public static void main(String[] args) throws Exception {

        // 模拟网页 HTML
        String html =
                "<div class='article-list'>"
                + "<div class='article'>"
                + "<h2><a href='/article/1'>Java 入门</a></h2>"
                + "<p class='summary'>Java 基础知识介绍</p>"
                + "</div>"
                + "<div class='article'>"
                + "<h2><a href='/article/2'>Spring Boot 入门</a></h2>"
                + "<p class='summary'>Spring Boot 开发入门</p>"
                + "</div>"
                + "</div>";

        // 解析 HTML,并指定基础 URI
        Document doc = Jsoup.parse(
                html,
                "https://example.com"
        );

        // 查询所有文章
        Elements articles = doc.select(
                ".article-list .article"
        );

        for (Element article : articles) {

            // 获取标题元素
            Element link = article.selectFirst("h2 a");

            // 获取摘要元素
            Element summary = article.selectFirst(
                    ".summary"
            );

            String title = link != null
                    ? link.text()
                    : "";

            String url = link != null
                    ? link.attr("abs:href")
                    : "";

            String description = summary != null
                    ? summary.text()
                    : "";

            System.out.println("标题:" + title);
            System.out.println("链接:" + url);
            System.out.println("摘要:" + description);
            System.out.println("--------------------");
        }
    }
}

9.2 运行结果

标题:Java 入门
链接:https://example.com/article/1
摘要:Java 基础知识介绍
--------------------
标题:Spring Boot 入门
链接:https://example.com/article/2
摘要:Spring Boot 开发入门
--------------------

9.3 实战代码分析

这个示例体现了 Jsoup 的典型使用流程:

  1. 获取 HTML 内容。
  2. 调用 Jsoup.parse() 解析为 Document。
  3. 使用 CSS 选择器定位文章列表。
  4. 遍历 Elements,逐个处理文章元素。
  5. 提取标题、链接和摘要。
  6. 使用 abs:href 将相对链接转换为绝对链接。

实际抓取网页时,只需要将模拟 HTML 替换为请求获取的网页文档即可。但要注意,实际网站的 HTML 结构可能与示例不同,选择器也需要根据目标网页进行调整。

十、Jsoup 常用 API 速查

API作用
Jsoup.parse(html)解析 HTML 字符串
Jsoup.parseBodyFragment(html)解析 HTML 片段
Jsoup.parse(file, charset)解析本地 HTML 文件
Jsoup.connect(url).get()请求网页并解析响应
doc.select(selector)根据 CSS 选择器查询多个元素
doc.selectFirst(selector)查询第一个匹配元素
doc.getElementById(id)根据 ID 查询元素
element.text()获取或设置文本
element.html()获取或设置内部 HTML
element.outerHtml()获取元素自身及其内部 HTML
element.attr(name)获取或设置属性
element.hasAttr(name)判断属性是否存在
element.remove()删除元素
element.appendElement(tag)创建并追加子元素
Jsoup.clean(html, safelist)根据白名单清洗 HTML

十一、使用 Jsoup 时的注意事项

11.1 Jsoup 不是完整的浏览器

Jsoup 负责 HTML 解析,不会像浏览器一样执行 JavaScript。

如果页面内容通过 JavaScript 动态请求后才展示,Jsoup 获取的原始 HTML 可能不包含目标数据。

遇到这种情况,可以先检查网页是否存在可直接调用的数据接口。

11.2 不要把 Jsoup 当作通用的 HTML 模板引擎

Jsoup 擅长解析、查询和修改 HTML,但如果业务需求是生成复杂的动态 网页、维护大量模板或构建完整的前端界面,应考虑更合适的模板引擎或前端框架。

11.3 网页抓取需要处理异常

请求网络资源时,可能发生连接超时、DNS 解析失败、HTTP 错误等情况。

生产环境中应合理配置超时、处理异常,并根据业务需求实现重试、限流和日志记录。

11.4 谨慎处理不可信 HTML

如果 HTML 来自用户输入或外部来源,不要默认它是安全的。

需要输出富文本时,应根据业务需求使用合适的 Safelist 规则进行清洗,并结合其他安全措施。

11.5 注意网页抓取的合规性

使用 Jsoup 抓取第三方网站时,应遵守网站访问规则、服务条款及相关法律要求。避免高频请求、未经授权访问受保护内容或采集不应获取的数据。

十二、总结

Jsoup 是 Java 生态中一个实用的 HTML 处理库,尤其适合后端开发中涉及 HTML 解析、网页数据抽取、DOM 修改和富文本清洗的场景。

学习 Jsoup 时,建议重点掌握以下内容:

  1. HTML 解析:理解 Document、Element 和 Elements。
  2. CSS 选择器:熟练使用 select()、selectFirst() 定位元素。
  3. 内容提取:掌握 text()、html()、attr() 等常用 API。
  4. 网页抓取:掌握 Jsoup.connect()、请求超时和响应处理。
  5. DOM 修改:学会修改文本、属性以及添加和删除元素。
  6. HTML 清洗:理解 Jsoup.clean() 和 Safelist 的白名单机制。

Jsoup 的学习成本较低,在数据采集、HTML 内容处理和富文本安全等场景中非常实用。掌握这些 API 后,就能够以较少的代码完成许多原本需要复杂字符串处理才能实现的工作。

以上就是Java如何使用Jsoup库轻松解析HTML网页数据的详细内容,更多关于Java Jsoup解析HTML的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文