杰瑞科技汇

Java、HTML、正则表达式如何高效协同?

第一部分:Java 中的正则表达式

Java 提供了 java.util.regex 包来支持正则表达式,主要包括两个核心类:

Java、HTML、正则表达式如何高效协同?-图1
(图片来源网络,侵删)
  1. Pattern: 编译后的正则表达式模式对象,它是一个不可变的类,代表了一个编译好的正则表达式。
  2. Matcher: 对输入字符串进行匹配操作的引擎,它使用 Pattern 对象来解释和执行匹配操作。

核心概念和常用方法

编译模式 Pattern 类的 compile() 方法用于将一个正则表达式字符串编译成一个 Pattern 对象。

String regex = "\\d+"; // 匹配一个或多个数字
Pattern pattern = Pattern.compile(regex);

创建匹配器 使用 Pattern 对象的 matcher() 方法,可以创建一个 Matcher 对象,用于对具体的输入字符串进行操作。

String input = "我的电话是 123456,还有一部是 98765。";
Matcher matcher = pattern.matcher(input);

执行匹配

  • boolean matches(): 尝试将整个输入序列与模式匹配,如果匹配成功,返回 true

    Java、HTML、正则表达式如何高效协同?-图2
    (图片来源网络,侵删)
    System.out.println(matcher.matches()); // false,因为输入字符串不全是数字
  • boolean find(): 尝试在输入字符串中查找下一个匹配的子序列,这是最常用的方法。

    while (matcher.find()) {
        System.out.println("找到数字: " + matcher.group());
    }
    // 输出:
    // 找到数字: 123456
    // 找到数字: 98765
  • boolean lookingAt(): 尝试从输入字符串的起始位置开始匹配模式。

    matcher.reset("123456 is a number.");
    System.out.println(matcher.lookingAt()); // true

获取匹配结果

  • String group(): 返回最近一次匹配的子序列。
  • int start(): 返回最近一次匹配的子序列的起始索引。
  • int end(): 返回最近一次匹配的子序列的结束索引(最后一个字符的索引 + 1)。
matcher.reset("我的电话是 123456,还有一部是 98765。");
while (matcher.find()) {
    System.out.println("找到的数字: " + matcher.group());
    System.out.println("起始位置: " + matcher.start());
    System.out.println("结束位置: " + matcher.end());
    System.out.println("--------------------");
}

常用正则表达式元字符(Java 语法)

元字符 描述 示例
匹配除换行符外的任何单个字符 a.c 匹配 "abc", "aac"
匹配前面的子表达式零次或多次 a*b 匹配 "b", "aab", "aaab"
匹配前面的子表达式一次或多次 a+b 匹配 "ab", "aab",但不匹配 "b"
匹配前面的子表达式零次或一次 a?b 匹配 "b", "ab"
{n} 精确匹配 n 次 a{3} 匹配 "aaa"
{n,} 至少匹配 n 次 a{3,} 匹配 "aaa", "aaaa"
{n,m} 匹配 n 到 m 次 a{2,4} 匹配 "aa", "aaa", "aaaa"
, , 非贪婪匹配,匹配尽可能少的字符 <.*?> 会匹配 <a> 而不是 <a>b</a>
[] 字符集,匹配其中的任意一个字符 [abc] 匹配 "a", "b", "c"
[^] 反向字符集,匹配不在其中的任意一个字符 [^abc] 匹配 "d", "e", "1"
选择,匹配 两边的任意一个表达式 a\|b 匹配 "a" 或 "b"
分组,可以捕获匹配的文本 (a)(b) 可以用 group(1) 和 group(2) 获取 "a" 和 "b"
\d 匹配数字,等同于 [0-9] \d+ 匹配一个或多个数字
\D 匹配非数字,等同于 [^0-9]
\w 匹配单词字符(字母、数字、下划线),等同于 [a-zA-Z0-9_]
\W 匹配非单词字符
\s 匹配空白字符(空格、制表符、换行等)
\S 匹配非空白字符
^ 匹配输入字符串的开始位置 ^abc 匹配 "abc123" 的开头
匹配输入字符串的结束位置 123$ 匹配 "abc123" 的结尾
\ 转义字符,用于匹配特殊字符本身 \. 匹配 "."

特别注意: 在 Java 字符串中,\ 是一个转义字符,在正则表达式中表示 \ 的元字符(如 \d, \.)在 Java 字符串中需要写成 \\d, \\.


第二部分:使用 Java 正则表达式处理 HTML

使用正则表达式解析 HTML 是一个经典的话题。重要提示:正则表达式不适合用于解析复杂的、嵌套的 HTML/XML 文档。 HTML 的不规范性和复杂性(如标签属性、嵌套、注释等)很容易让正则表达式失效。

对于非常简单、结构固定的 HTML 片段,或者进行一些简单的文本提取,正则表达式仍然是一个快速有效的工具。

场景 1:提取所有 <a> 标签的 href 属性值

假设我们有如下 HTML 字符串:

<html>
<body>
    <a href="https://www.google.com">Google</a>
    <a href="/about.html" title="About Us">About</a>
    <p>这是一个段落。</p>
</body>
</html>

目标: 提取出 https://www.google.com/about.html

正则表达式分析:

  1. 我们需要匹配 <a 开头的标签。
  2. 后面可能跟着任意属性,直到 href="..."
  3. href 的值可能在单引号、双引号或无引号中,但为了安全,我们假设都在双引号中。
  4. 我们需要捕获 href 属性的值。

正则表达式: <a\\s+[^>]*?href="([^"]*)"

  • <a: 匹配 <a
  • \\s+: 匹配一个或多个空白字符(空格、制表符)。
  • [^>]*?: 匹配除 > 外的任意字符零次或多次, 使其非贪婪,匹配到第一个 > 即可。
  • href=": 匹配 href="
  • ([^"]*): 这是一个捕获组,匹配并捕获任意数量的非 字符,这就是我们想要的 href 值。
  • 匹配结尾的 。

Java 代码实现:

import java.util.regex.*;
public class HtmlRegexExample {
    public static void main(String[] args) {
        String html = "<html><body><a href=\"https://www.google.com\">Google</a><a href=\"/about.html\" title=\"About Us\">About</a><p>这是一个段落。</p></body></html>";
        // 正则表达式,注意 Java 字符串中的转义
        String regex = "<a\\s+[^>]*?href=\"([^\"]*)\"";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(html);
        System.out.println("找到的链接:");
        while (matcher.find()) {
            // group(0) 是整个匹配的字符串,group(1) 是第一个捕获组
            String href = matcher.group(1);
            System.out.println(href);
        }
    }
}

输出:

找到的链接:
https://www.google.com
/about.html

场景 2:提取所有 <img> 标签的 src 属性值

与场景 1 类似,只需将标签名和属性名修改即可。

HTML:

<img src="image1.png" alt="Image 1">
<img src="https://example.com/image2.jpg" width="200">

正则表达式: <img\\s+[^>]*?src="([^"]*)"

Java 代码:

// ... (代码框架同上)
String html = "<img src=\"image1.png\" alt=\"Image 1\"><img src=\"https://example.com/image2.jpg\" width=\"200\">";
String regex = "<img\\s+[^>]*?src=\"([^\"]*)\"";
// ... (使用 pattern 和 matcher 进行查找)

第三部分:为什么不推荐用正则解析 HTML?以及更好的替代方案

正则表达式的局限性

  1. 无法处理嵌套结构:HTML 的核心是嵌套标签(如 <div><p>...</p></div>),正则表达式很难正确处理这种嵌套关系,它会贪婪地匹配从第一个 <div> 到最后一个 </div>,中间包含所有内容,导致结构混乱。
  2. HTML 规范的复杂性
    • 属性值可能包含 > 或 ,这会破坏简单的正则表达式。
    • 标签可以大小写不敏感(<A><a> 都有效)。
    • 注释 <!-- ... --> 和脚本 <script>...</script> 中的内容会干扰匹配。
    • 属性的顺序是任意的。
  3. 维护困难:一个看似简单的 HTML 匹配正则表达式,可能在遇到一个微小的变化时就完全失效,编写和调试复杂的正则表达式非常耗时且容易出错。

更好的替代方案:HTML 解析器

对于任何严肃的 HTML 解析任务,都应该使用专门的 HTML 解析库,它们能构建一个 DOM(文档对象模型)树,让你可以像操作对象一样操作 HTML,从而完全避免正则表达式的所有问题。

推荐使用的 Java HTML 解析库:

  1. Jsoup (强烈推荐)

    • 优点:非常轻量、易用、功能强大,它提供了类似 jQuery 的 CSS 选择器 API,非常直观。
    • 官网https://jsoup.org/

    使用 Jsoup 重写上面的例子:

    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    import java.io.IOException;
    public class JsoupExample {
        public static void main(String[] args) {
            String html = "<html><body><a href=\"https://www.google.com\">Google</a><a href=\"/about.html\" title=\"About Us\">About</a><p>这是一个段落。</p></body></html>";
            // 解析 HTML 字符串
            Document doc = Jsoup.parse(html);
            // 使用 CSS 选择器查找所有 a 标签
            Elements links = doc.select("a");
            System.out.println("找到的链接:");
            for (Element link : links) {
                // 获取 href 属性
                String href = link.attr("href");
                // 获取标签内的文本
                String text = link.text();
                System.out.println("链接: " + href + ", 文本: " + text);
            }
            // 查找所有 img 标签的 src
            String imgHtml = "<img src=\"image1.png\" alt=\"Image 1\"><img src=\"https://example.com/image2.jpg\" width=\"200\">";
            Document imgDoc = Jsoup.parse(imgHtml);
            Elements images = imgDoc.select("img");
            System.out.println("\n找到的图片:");
            for (Element img : images) {
                System.out.println("图片源: " + img.attr("src"));
            }
        }
    }

    输出:

    找到的链接:
    链接: https://www.google.com, 文本: Google
    链接: /about.html, 文本: About
    找到的图片:
    图片源: image1.png
    图片源: https://example.com/image2.jpg
  2. HtmlUnit

    • 优点:一个“浏览器程序”,不仅能解析 HTML,还能模拟浏览器执行 JavaScript,适合进行 Web 自动化测试。
    • 缺点:比 Jsoup 重,更复杂。
  3. Apache NekoHTML

    • 优点:一个健壮的解析器,能修复很多 HTML 格式错误,生成一个干净的 DOM 树。
特性 Java 正则表达式 (java.util.regex) HTML 解析器 (如 Jsoup)
适用场景 简单的文本模式匹配、日志分析、数据清洗 所有 HTML/XML 解析、数据抓取、网页内容修改
处理嵌套 非常困难,不推荐 非常擅长,基于 DOM 树
健壮性 低,对 HTML 的不规范非常敏感 高,能处理各种“畸形” HTML
易用性 复杂的正则表达式难以编写和调试 API 直观,类似 CSS 选择器,学习成本低
性能 对于简单模式,速度很快 初始化稍慢,但操作 DOM 非常高效

最终建议:

  • 当你只需要从一小段固定的、简单的 HTML 中提取一两个值时,可以考虑使用正则表达式作为快速解决方案。
  • 当你需要处理任何真实的、复杂的、可能变化的网页时,或者需要进行复杂的 DOM 遍历和操作时,请务必使用 Jsoup 这样的专业 HTML 解析器。 这是正确、高效且可维护的做法。
分享:
扫描分享到社交APP
上一篇
下一篇