首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 正則表達式實戰指南:從基礎語法到網頁爬蟲郵箱提取完整案例

     編輯:Java 程式設計入門

前言

正則表達式是文本處理的瑞士軍刀。Java 通過 java.util.regex 包(Pattern、Matcher)提供強大支持。本文系統講解核心語法、String 常用方法、Pattern/Matcher 進階用法,並以「網頁郵箱爬蟲」為實戰案例,展示從網絡流讀取、正則匹配、文件持久化的完整鏈路。

一、核心語法速查表

元字符含義示例
.任意單字符(除換行)a.c 匹配 "abc", "a@c"
\d數字 [0-9]\d{3} 三位數
\D非數字
\w單詞字符 [a-zA-Z0-9_]\w+ 匹配 "user_123"
\W非單詞字符
\s空白字符(空格、制表、換行)
\S非空白字符
[]字符集合[aeiou] 元音
[^]排除字符集合[^0-9] 非數字
*0 次或多次(貪婪)ab*c 匹配 "ac", "abc", "abbc"
+1 次或多次\d+ 至少一位數字
?0 次或 1 次colou?r 匹配 "color" "colour"
{n}恰好 n 次\d{4} 四位年份
{n,}至少 n 次\d{2,} 至少兩位數
{n,m}n 到 m 次[1-9]\d{4,14} QQ 號
()分組/捕獲(\d{4})-(\d{2}) 年-月
|或cat|dog
^行首^https://
$行尾\.jpg$
\b單詞邊界\bJava\b

二、String 便捷方法

String regex = "[1-9]\\d{4,14}"; // QQ 號:首位非0,5-15位
"12345".matches(regex);          // true
"1234".matches(regex);           // false(長度不足)

String[] parts = "a,b;c|d".split("[,;|]"); // [a, b, c, d]

String masked = "phone: 13812345678".replaceAll("\\d{11}", "***********");
// "phone: ***********"

// 分組引用替換:將 yyyy-MM-dd 轉為 MM/dd/yyyy
"2024-01-15".replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$2/$3/$1");
// "01/15/2024"

三、Pattern / Matcher 進階用法

1. 編譯複用與標誌位

// 忽略大小寫、多行模式、Unix行尾
Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

2. 三種匹配模式

Matcher m = p.matcher(input);

m.matches();    // 整串完全匹配
m.lookingAt();  // 從開頭匹配
m.find();       // 掃描查找下一個匹配(最常用)

3. 分組提取與操作

String html = "<a href=\"https://example.com\">Link</a>";
Pattern p = Pattern.compile("href=\"([^\"]+)\"");
Matcher m = p.matcher(html);
while (m.find()) {
    System.out.println("Group 0 (整匹配): " + m.group(0)); // href="..."
    System.out.println("Group 1 (第一分組): " + m.group(1)); // https://example.com
    System.out.println("Start: " + m.start(1) + ", End: " + m.end(1));
}

4. 替換操作

// 簡單替換
String r = m.replaceAll("***");

// 函數式替換(Java 9+)
String r = m.replaceAll(mr -> "[" + mr.group(1).toUpperCase() + "]");

四、實戰案例:網頁郵箱爬蟲

需求

從指定 URL 下載 HTML,提取所有符合郵箱格式的字符串,去重後寫入文件,每行一個。

郵箱正則設計

// RFC 5322 簡化版:本地部分@域名
// 本地:字母數字._%+-  1-64字符
// 域名:標籤由字母數字-組成,標籤間用.分隔,頂級域至少2字母
String EMAIL_REGEX = "\\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\\b";
// \b 單詞邊界防止匹配到 "abc@def.com.xyz" 中的 "def.com"
Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX, Pattern.CASE_INSENSITIVE);

完整爬蟲代碼

package com.example.crawler;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EmailCrawler {

    private static final String TARGET_URL = "https://example.com/forum/page.html";
    private static final String OUTPUT_FILE = "emails.txt";
    private static final Pattern EMAIL_PATTERN = Pattern.compile(
        "\\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\\b",
        Pattern.CASE_INSENSITIVE
    );

    public static void main(String[] args) {
        Set uniqueEmails = new LinkedHashSet<>(); // 保持順序去重
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(fetchContent(TARGET_URL), StandardCharsets.UTF_8));
             BufferedWriter writer = Files.newBufferedWriter(Paths.get(OUTPUT_FILE))) {

            String line;
            while ((line = reader.readLine()) != null) {
                Matcher m = EMAIL_PATTERN.matcher(line);
                while (m.find()) {
                    String email = m.group().toLowerCase();
                    if (uniqueEmails.add(email)) { // 去重
                        writer.write(email);
                        writer.newLine();
                    }
                }
            }
            writer.flush();
            System.out.println("提取完成,共 " + uniqueEmails.size() + " 個唯一郵箱。");
        } catch (IOException e) {
            System.err.println("爬蟲異常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private static InputStream fetchContent(String urlStr) throws IOException {
        URL url = new URL(urlStr);
        URLConnection conn = url.openConnection();
        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; EmailBot/1.0)");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(10000);
        return conn.getInputStream();
    }
}

關鍵點解析

  • LinkedHashSet:保持首次出現順序同時去重。
  • 流式處理:逐行讀取、即時匹配、即時寫入,常數內存,支援大文件。
  • 字符集顯式指定 UTF-8:避免中文頁面亂碼導致匹配失效。
  • User-Agent 偽裝:防止簡單反爬屏蔽。
  • 異常處理:網絡超時、HTTP 錯誤碼、IO 異常均捕獲記錄。

五、常見陷阱與優化

問題原因解決
正則性能災難(ReDoS)嵌套量詞 (a+)+、回溯爆炸使用佔有優先量詞 ++、原子組 (?>...)、避免嵌套量詞、設置匹配超時
預編譯 Pattern 重複創建循環中 Pattern.compile()靜態常量預編譯,線程安全可共享
匹配跨行文本失效. 不匹配換行啟用 Pattern.DOTALL 或用 [\\s\\S]
轉義字符混淆Java 字符串與正則雙重轉義Java 11+ 使用 \"\"\"...\"\"\" 文本塊;或工具自動轉義
郵箱正則過嚴/過鬆RFC 5322 極其複雜業務場景通常用簡化版;極高準確度需求用專用庫

六、總結

正則表達式是文本處理的利器,但可讀性與性能往往對立。工程實踐原則:

  1. 能用 String 方法(startsWith, contains, split)解決的不用正則。
  2. 正則預編譯為靜態常量,複用 Pattern 對象。
  3. 優先用 Matcher.find() 掃描,配合分組提取。
  4. 複雜正則寫單元測試覆蓋邊界案例,防止 ReDoS。
  5. 爬蟲場景注意編碼、超時、反爬、去重、流式處理大數據。

掌握這些核心模式,你就能應對 90% 的文本解析與提取需求。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved