Java 正則表達式從入門到實戰:爬蟲郵箱提取完整案例
正則表達式是字串模式匹配利器。Java 提供 String 便利方法與 Pattern/Matcher 核心 API。本文系統整理語法規則、API 用法、常見陷阱,並以網頁郵箱爬蟲為例示範完整實作流程。
String 便利方法 (底層委派 Pattern/Matcher)
| 方法 | 用途 | 範例 |
matches(regex) | 整串匹配 (隱含 ^$) | "12345".matches("\\d+") → true |
split(regex) | 分割字串 | "a,b,c".split(",") → ["a","b","c"] |
replaceAll(regex, repl) | 全域替換 | "ab12cd".replaceAll("\\d+", "#") → "ab#cd" |
replaceFirst(regex, repl) | 首次替換 | "ab12cd34".replaceFirst("\\d+", "#") → "ab#cd34" |
核心 API:Pattern + Matcher (高效能、功能完整)
// 1. 編譯模式 (可複用、線程安全)
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
// 2. 建立匹配器 (非線程安全、輸入特定字串)
Matcher matcher = pattern.matcher(inputString);
// 3. 三種匹配模式
// 3.1 整串匹配 (等同 String.matches)
boolean fullMatch = matcher.matches();
// 3.2 尋找子串 (可重複呼叫,配合群組提取)
while (matcher.find()) {
String full = matcher.group(); // 整個匹配
String group1 = matcher.group(1); // 第 1 個捕獲組
int start = matcher.start(); // 匹配起始索引
int end = matcher.end(); // 匹配結束索引
}
// 3.3 尋找指定位置開頭匹配
boolean lookingAt = matcher.lookingAt();
正則語法速查表
| 語法 | 含義 | 範例匹配 |
. | 任意字元 (除換行,DOTALL 含換行) | a.c → "abc", "axc" |
[abc] | 字元類別 (a 或 b 或 c) | [aeiou] → 元音 |
[^abc] | 排除字元類別 | [^0-9] → 非數字 |
[a-z] | 範圍 | [A-Z0-9] → 大寫字母數字 |
\d | 數字 [0-9] | |
\D | 非數字 | |
\w | 單字字元 [a-zA-Z0-9_] | |
\W | 非單字字元 | |
\s | 空白字元 | |
\S | 非空白字元 | |
* | 0 次或多次 (貪婪) | ab*c → "ac", "abc", "abbc" |
+ | 1 次或多次 | ab+c → "abc", "abbc" |
? | 0 次或 1 次 | ab?c → "ac", "abc" |
{n} | 恰好 n 次 | \\d{4} → 4 位數字 |
{n,} | 至少 n 次 | \\d{2,} → 2 位以上數字 |
{n,m} | n 到 m 次 | \\d{3,5} → 3-5 位數字 |
() | 捕獲組 (可反向引用 \\1, \\2) | (\\w+)\\s+\\1 → 重複單字 |
(?:) | 非捕獲組 | (?:\\d{3}-)?\\d{4} → 區碼可選 |
| | 或 | cat|dog → "cat" 或 "dog" |
^ | 行首 (MULTILINE 模式) | |
$ | 行尾 (MULTILINE 模式) | |
\\b | 單字邊界 | \\bcat\\b → 整字 "cat" |
Java 字串中雙重轉義陷阱
// 目標正則: \. (匹配字面點號)
// Java 字串: "\\." (第一層: \ → \\, 第二層: . → \.)
String regex = "\\.";
// 目標正則: \\ (匹配字面反斜線)
// Java 字串: "\\\\" (第一層: \ → \\, 第二層: \ → \\)
String regex = "\\\\";
// 目標正則: \d+ (匹配數字)
// Java 字串: "\\d+" (僅需轉義反斜線)
String regex = "\\d+";
實戰案例:網頁郵箱爬蟲
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class EmailCrawler {
// 郵箱正則: 用戶名@域名 (支援多級子域名)
// 用戶名: 字母數字下劃線 6-12 位
// 域名: 字母數字 + 多個 .字母 結構
private static final String EMAIL_REGEX = "[a-zA-Z0-9_]{6,12}@[a-zA-Z0-9]+(\\.[a-zA-Z]+)+";
private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
public static void main(String[] args) {
String urlStr = "https://bbs.example.com/topics/390148495"; // 目標頁面
String outputFile = "emails.txt";
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new URL(urlStr).openStream(), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
String line;
int count = 0;
while ((line = reader.readLine()) != null) {
Matcher matcher = EMAIL_PATTERN.matcher(line);
while (matcher.find()) {
String email = matcher.group();
writer.write(email);
writer.newLine();
writer.flush(); // 即時寫入
count++;
System.out.println("發現郵箱 [" + count + "]: " + email);
}
}
System.out.println("爬取完成,共提取 " + count + " 個郵箱,儲存至 " + outputFile);
} catch (MalformedURLException e) {
System.err.println("無效 URL: " + urlStr);
} catch (IOException e) {
System.err.println("網路/IO 錯誤: " + e.getMessage());
}
}
}
進階優化與最佳實務
| 場景 | 優化策略 |
| 高效能匹配 | 預編譯 Pattern 靜態常數複用;避免在迴圈中 Pattern.compile |
| 大量文本 | 使用 Matcher.find() 迭代而非 matches();考慮分塊處理 |
| 複雜正則 | 啟用 Pattern.COMMENTS 撰寫可讀正則;拆解為多個簡單正則 |
| 效能災難 | 避免嵌套量詞 (a+)+、過度回溯;使用佔有優先量詞 ++、*+、?+ |
| Unicode 支援 | Pattern.UNICODE_CHARACTER_CLASS 讓 \w/\d/\s 支援 Unicode |
| 多行模式 | Pattern.MULTILINE 讓 ^$ 匹配行首行尾;DOTALL 讓 . 匹配換行 |
常見郵箱正則進化
// 簡易版 (教學用)
[a-zA-Z0-9_]+@[a-zA-Z0-9]+\\.[a-zA-Z]+
// 實用版 (RFC 5322 簡化)
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}
// 生產版 (嚴格、長度限制、國際化域名)
^(?=.{1,64}@)[a-zA-Z0-9._%+-]+@(?=.{1,255}$)[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$
// Java 內建驗證 (Jakarta Bean Validation)
@Email // 需依賴 validation-api + hibernate-validator
掌握正則核心語法、Java API 正確用法、效能陷阱規避,配合實戰爬蟲經驗,即可高效解決文本提取、驗證、轉換等核心需求。