在 Java 中移除 HTML 標籤是常見需求 (如摘要生成、XSS 防禦、文本提取)。最直觀的方法是正則表達式替換,但 **HTML 不是正則語言**,複雜巢狀結構、屬性值包含 `>`、註釋、CDATA、腳本樣式標籤等場景極易導致正則失效或災難性回溯。本文對比正則方案與業界標準庫 **Jsoup** 的優劣,給出生產環境建議。
public class RegexHtmlStripper {
// 簡單移除標籤:<...> 替換為空格/換行
private static final Pattern TAG_PATTERN = Pattern.compile("<[^>]*>", Pattern.DOTALL);
// 移除 HTML 實體: < 等
private static final Pattern ENTITY_PATTERN = Pattern.compile("&[a-zA-Z]{1,10};|\\d{1,5};|[0-9a-fA-F]{1,4};");
public static String stripTags(String html) {
if (html == null) return "";
String text = TAG_PATTERN.matcher(html).replaceAll(" "); // 標籤轉空格保留詞邊界
text = ENTITY_PATTERN.matcher(text).replaceAll(""); // 移除實體
// 壓縮多餘空白
return text.replaceAll("\\s+", " ").trim();
}
public static void main(String[] args) {
String html = "ID Name 1 Alice
";
System.out.println(stripTags(html));
// 輸出: ID Name 1 Alice
}
}
| 場景 | 正則行為 | 風險 |
|---|---|---|
| 屬性值含 `>` | ` ` 被錯誤截斷 | 殘留標籤碎片 |
| 註釋 `` | 正則 `<[^>]*>` 無法跨行匹配 `` | 註釋內容洩露 |
| 腳本/樣式內容 | `` 內部 `<` `>` 干擾匹配 | JS/CSS 代碼洩露 |
| CDATA 區段 | `]]>` 被誤當標籤 | 文本損壞 |
| 惡意輸入 | `<<<` 繞過簡單過濾 | XSS 攻擊 |
| 災難性回溯 | `<[^>]*>` 遇超長無 `>` 字符串 | CPU 100% 拒絕服務 |
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist;
public class JsoupHtmlProcessor {
// 1. 純文本提取 (保留可見文本結構)
public static String extractText(String html) {
if (html == null) return "";
// parse() 自動修補不完整 HTML,body() 獲取 body 內容
return Jsoup.parse(html).body().text();
}
// 2. 安全清洗:只保留白名單標籤/屬性 (防 XSS)
public static String cleanHtml(String html, Safelist safelist) {
if (html == null) return "";
return Jsoup.clean(html, safelist);
}
// 3. 移除所有標籤但保留換行結構 (模擬瀏覽器渲染文本)
public static String stripTagsKeepStructure(String html) {
if (html == null) return "";
Document doc = Jsoup.parse(html);
// 移除 script, style, noscript 等非可見標籤及其內容
doc.select("script, style, noscript, iframe, object, embed").remove();
// 將塊級標籤轉為換行
doc.select("div, p, br, h1, h2, h3, h4, h5, h6, li, tr, td, th, table, ul, ol").append("\\n");
return doc.body().text().replaceAll("\\n+", "\\n").trim();
}
public static void main(String[] args) {
String dirty = "Hello World
Link";
System.out.println("--- 純文本 ---");
System.out.println(extractText(dirty));
// 輸出: Hello World Link
System.out.println("--- 安全清洗 (基本標籤) ---");
System.out.println(cleanHtml(dirty, Safelist.basic()));
// 輸出: Hello World
Link (移除 script, 注釋, 危險 href)
System.out.println("--- 保留結構文本 ---");
System.out.println(stripTagsKeepStructure(dirty));
// 輸出帶換行的純文本
}
}
| 需求場景 | 推薦方案 | 理由 |
|---|---|---|
| 用戶輸入富文本存儲/展示 (CMS、評論、郵件) | Jsoup.clean(html, Safelist.relaxed()) | 安全第一,保留排版能力 |
| 搜索摘要、關鍵字提取、純文本索引 | Jsoup.parse(html).text() | 完整提取可見文本,忽略腳本樣式 |
| 極簡模板、固定格式、高效能、無用戶輸入 | 預編譯正則 | 零依賴、極快,但需嚴格測試輸入範圍 |
| 移除特定標籤 (如所有 ``) | Jsoup: doc.select("font").unwrap() | 結構化操作,不破壞巢狀 |
**正則表達式無法可靠解析 HTML。** 除非輸入極其簡單且完全可控 (如系統生成的固定模板),**生產環境請無腦使用 Jsoup**。它不僅能提取文本,更是防禦 XSS、清洗富文本的標準利器。引入一個 300KB 的 JAR,換來的是安全、正確、可維護的代碼,極具性價比。