Go語言爬蟲系列教程4:使用正則表達式解析HTML內容
正則表達式(Regular Expression,簡稱RegEx)是處理文本數據的利器。在網絡爬蟲中,我們經常需要從HTML頁面中提取特定的信息,正則表達式就像一個智能的"文本篩子",能夠精確地找到我們需要的內容。
想象你在一本厚厚的電話簿中找所有以"138"開頭的手機號碼。如果用人工查找會很累,但如果有一個魔法篩子,你告訴它"找所有138開頭的11位數字",它就能快速找出所有符合條件的號碼。正則表達式就是這樣的"魔法篩子"。
一、簡單演示
package mainimport ("fmt""regexp"
)// 演示基礎正則表達式語法
func main() {// 1\. 字面量匹配(最簡單的情況)text1 := "Hello World"pattern1 := "Hello"matched, _ := regexp.MatchString(pattern1, text1)fmt.Printf("文本: %s\n模式: %s\n匹配結果: %t\n\n", text1, pattern1, matched)
輸出結果:
文本: Hello World
模式: Hello
匹配結果: true
這個代碼演示的是最簡單的字面量匹配
二、基礎語法
正則表達式由兩種基本元素組成:
- 字面量:直接匹配文本中的字符
- 元字符:具有特殊含義的字符,用于描述模式
2.1 字面量匹配 - 最簡單的精確匹配
text1 := "Hello World"pattern1 := "Hello"matched, _ := regexp.MatchString(pattern1, text1)fmt.Printf("文本: %s\n模式: %s\n匹配結果: %t\n\n", text1, pattern1, matched)
運行結果:
文本: Hello World
模式: Hello
匹配結果: true
作用:直接匹配指定的字符串,不使用任何特殊符號
2.2 點號(.) - 萬能字符匹配器
text2 := "cat bat hat"pattern2 := ".at" // 匹配任意字符+atre2 := regexp.MustCompile(pattern2)matches2 := re2.FindAllString(text2, -1)fmt.Printf("文本: %s\n模式: %s (點號匹配任意字符)\n匹配結果: %v\n\n", text2, pattern2, matches2)
運行結果:
文本: cat bat hat
模式: .at (點號匹配任意字符)
匹配結果: [cat bat hat]
作用:點號.
是一個特殊字符,可以匹配除換行符外的任意單個字符
.at
匹配"任意字符+at"的模式- 在文本中找到了:
cat
(c+at)、bat
(b+at)、hat
(h+at) - 所有三個單詞都符合模式
2.3 星號(*) - 貪婪的重復匹配
text3 := "goooooogle"pattern3 := "go*gle" // g + 0個或多個o + glematched3, _ := regexp.MatchString(pattern3, text3)fmt.Printf("文本: %s\n模式: %s (星號表示0次或多次)\n匹配結果: %t\n\n", text3, pattern3, matched3)
運行結果:
文本: goooooogle
模式: go*gle (星號表示0次或多次)
匹配結果: true
作用:星號*
表示前面的字符可以出現0次或多次
go*gle
意思是:g + (0個或多個o) + gle- "goooooogle"中有g + 5個o + gle,符合模式
- 這個模式也能匹配"ggle"(0個o)、“google”(2個o)等
2.4 加號(+) - 至少一次的重復匹配
text4 := "ggle google gooogle"pattern4 := "go+gle" // g + 1個或多個o + glere4 := regexp.MustCompile(pattern4)matches4 := re4.FindAllString(text4, -1)fmt.Printf("文本: %s\n模式: %s (加號表示1次或多次)\n匹配結果: %v\n\n", text4, pattern4, matches4)
運行結果:
文本: ggle google gooogle
模式: go+gle (加號表示1次或多次)
匹配結果: [google gooogle]
作用:加號+
表示前面的字符必須出現1次或多次
go+gle
意思是:g + (1個或多個o) + gle- "ggle"不匹配(沒有o)
- "google"匹配(2個o)
- "gooogle"匹配(3個o)
2.5 問號(?) - 可選字符匹配
text5 := "color colour"pattern5 := "colou?r" // colou + 0個或1個rre5 := regexp.MustCompile(pattern5)matches5 := re5.FindAllString(text5, -1)fmt.Printf("文本: %s\n模式: %s (問號表示0次或1次)\n匹配結果: %v\n\n", text5, pattern5, matches5)
運行結果:
文本: color colour
模式: colou?r (問號表示0次或1次)
匹配結果: [color colour]
作用:問號?
表示前面的字符可以出現0次或1次(可選)
colou?r
意思是:colou + (0個或1個u) + r- "color"匹配(0個u)
- "colour"匹配(1個u)
2.6 方括號[] - 字符集合匹配
text1 := "cat bat rat mat"
pattern1 := "[cbr]at" // 匹配cat、bat或rat
運行結果:
文本: cat bat rat mat
模式: [cbr]at ([cbr]匹配c、b或r中的一個)
匹配結果: [cat bat rat]
作用:方括號[]
定義一個字符集合,匹配其中任意一個字符
[cbr]at
表示:(c或b或r) + at- 匹配到:
cat
、bat
、rat
- 不匹配:
mat
(因為m不在[cbr]中)
2.7 范圍表示法 - 連續字符的簡寫
text2 := "a1 b2 c3 d4"
pattern2 := "[a-c][1-3]" // 匹配a-c中的一個字母 + 1-3中的一個數字
運行結果:
文本: a1 b2 c3 d4
模式: [a-c][1-3] ([a-c]匹配a到c,[1-3]匹配1到3)
匹配結果: [a1 b2 c3]
作用:用連字符-
表示字符范圍,避免逐個列舉
[a-c]
等同于[abc]
[1-3]
等同于[123]
- 匹配到:
a1
、b2
、c3
- 不匹配:
d4
(d不在a-c范圍,4不在1-3范圍)
常用范圍:
[a-z]
:小寫字母[A-Z]
:大寫字母[0-9]
:數字[a-zA-Z0-9]
:字母和數字
2.8 否定字符類[^] - 排除匹配
text3 := "cat bat rat mat"
pattern3 := "[^m]at" // 匹配不是m開頭的at
運行結果:
文本: cat bat rat mat
模式: [^m]at ([^m]匹配除了m以外的字符)
匹配結果: [cat bat rat]
作用:^
在方括號內表示否定,匹配除了指定字符外的任意字符
[^m]at
表示:(除了m以外的任意字符) + at- 匹配到:
cat
、bat
、rat
- 不匹配:
mat
(因為開頭是m)
2.9 預定義字符類 - 常用模式的簡寫
text4 := "abc 123 XYZ !@#"
patterns := map[string]string{`\d`: "匹配數字 [0-9]",`\w`: "匹配單詞字符 [a-zA-Z0-9_]",`\s`: "匹配空白字符",`\D`: "匹配非數字字符",`\W`: "匹配非單詞字符",`\S`: "匹配非空白字符",
}
2.10 精確量詞{n} - 確切次數匹配
text1 := "1 12 123 1234"
pattern1 := `\d{3}` // 匹配恰好3個數字
運行結果:
文本: 1 12 123 1234
模式: \d{3} (恰好3個數字)
匹配結果: [123 123]
作用:{n}
表示前面的字符或模式必須出現恰好n次
\d{3}
匹配恰好3個連續數字- 在"1234"中找到"123"(前3位)
- 注意:會優先匹配較長的數字串中的前3位
2.11 范圍量詞{n,m} - 區間次數匹配
text2 := "1 12 123 1234 12345"
pattern2 := `\d{2,4}` // 匹配2到4個數字
運行結果:
文本: 1 12 123 1234 12345
模式: \d{2,4} (2到4個數字)
匹配結果: [12 123 1234 1234]
作用:{n,m}
表示前面的字符出現n到m次(包含n和m)
- 匹配2-4個連續數字
- "1"不匹配(只有1個數字)
- “12345"中匹配前4個"1234”(貪婪匹配,取最大值)
2.12 最少量詞{n,} - 至少n次匹配
text3 := "a aa aaa aaaa"
pattern3 := `a{3,}` // 匹配3個或更多的a
運行結果:
文本: a aa aaa aaaa
模式: a{3,} (3個或更多的a)
匹配結果: [aaa aaaa]
作用:{n,}
表示前面的字符至少出現n次,沒有上限
- "a"和"aa"不匹配(少于3個)
- "aaa"和"aaaa"匹配(3個或更多)
2.13 行邊界^和$ - 位置匹配
text4 := "hello world\nworld hello\nhello"// 匹配行開始的hello
pattern4a := `^hello`
// 運行結果: [hello hello]// 匹配行結束的hello
pattern4b := `hello$`
// 運行結果: [hello hello]
作用:
^
:匹配行的開始位置 ,^hello
匹配每行開頭的"hello"$
:匹配行的結束位置 ,hello$
匹配每行結尾的"hello"
2.14 單詞邊界\b - 完整單詞匹配
text5 := "cat catch catch22"
pattern5 := `\bcat\b` // 完整的單詞cat
運行結果:
文本: cat catch catch22
模式: \bcat\b (完整單詞cat)
匹配結果: [cat]
作用:\b
表示單詞邊界,確保匹配完整的單詞
\bcat\b
只匹配作為獨立單詞的"cat"- "catch"中的"cat"不匹配(后面還有字母ch)
- "catch22"中的"cat"也不匹配
三、regexp包常用方法介紹
3.1 regexp包的核心結構
package mainimport ("fmt""regexp""strings"
)// RegexpDemo 正則表達式演示結構體
type RegexpDemo struct {name stringpattern stringtext string
}// 演示regexp包的主要方法
func demonstrateRegexpPackage() {fmt.Println("=== regexp包方法演示 ===")// 1\. 編譯正則表達式的兩種方式fmt.Println("1\. 編譯正則表達式:")// 方式1:Compile - 返回錯誤pattern := `\d+`re1, err := regexp.Compile(pattern)if err != nil {fmt.Printf("編譯錯誤: %v\n", err)return}fmt.Printf("Compile成功: %v\n", re1)// 方式2:MustCompile - 編譯失敗會panic(適合確定正確的模式)re2 := regexp.MustCompile(pattern)fmt.Printf("MustCompile成功: %v\n\n", re2)// 2\. 基本匹配方法text := "我的電話是138-1234-5678,朋友的是139-8765-4321"phonePattern := `1[3-9]\d-\d{4}-\d{4}`phoneRe := regexp.MustCompile(phonePattern)// 2.1 MatchString - 檢查是否匹配fmt.Println("2\. 基本匹配方法:")matched := phoneRe.MatchString(text)fmt.Printf("MatchString: %t\n", matched)// 2.2 FindString - 找到第一個匹配firstMatch := phoneRe.FindString(text)fmt.Printf("FindString: %s\n", firstMatch)// 2.3 FindAllString - 找到所有匹配allMatches := phoneRe.FindAllString(text, -1) // -1表示找到所有fmt.Printf("FindAllString: %v\n", allMatches)// 2.4 FindStringIndex - 找到第一個匹配的位置firstIndex := phoneRe.FindStringIndex(text)fmt.Printf("FindStringIndex: %v\n", firstIndex)if firstIndex != nil {fmt.Printf("第一個匹配的內容: %s\n", text[firstIndex[0]:firstIndex[1]])}// 2.5 FindAllStringIndex - 找到所有匹配的位置allIndexes := phoneRe.FindAllStringIndex(text, -1)fmt.Printf("FindAllStringIndex: %v\n\n", allIndexes)
}
3.2 分組和捕獲
// 演示分組和捕獲
func demonstrateGroupsAndCapture() {fmt.Println("=== 分組和捕獲演示 ===")// 1\. 基本分組()text := "張三的郵箱是zhangsan@example.com,李四的郵箱是lisi@gmail.com"// 使用分組捕獲郵箱的用戶名和域名emailPattern := `(\w+)@(\w+\.\w+)`emailRe := regexp.MustCompile(emailPattern)// FindStringSubmatch - 返回第一個匹配及其分組firstSubmatch := emailRe.FindStringSubmatch(text)fmt.Printf("郵箱分組匹配:\n")if len(firstSubmatch) > 0 {fmt.Printf("完整匹配: %s\n", firstSubmatch[0])fmt.Printf("用戶名: %s\n", firstSubmatch[1])fmt.Printf("域名: %s\n", firstSubmatch[2])}fmt.Println()// FindAllStringSubmatch - 返回所有匹配及其分組allSubmatches := emailRe.FindAllStringSubmatch(text, -1)fmt.Printf("所有郵箱分組匹配:\n")for i, submatch := range allSubmatches {fmt.Printf("第%d個郵箱:\n", i+1)fmt.Printf(" 完整匹配: %s\n", submatch[0])fmt.Printf(" 用戶名: %s\n", submatch[1])fmt.Printf(" 域名: %s\n", submatch[2])}fmt.Println()// 2\. 命名分組(?P<name>pattern)urlText := "訪問我們的網站:https://www.example.com:8080/path?query=value"urlPattern := `(?P<protocol>https?)://(?P<domain>[\w.]+):?(?P<port>\d*)/(?P<path>[\w/]*)\??(?P<query>.*)`urlRe := regexp.MustCompile(urlPattern)matches := urlRe.FindStringSubmatch(urlText)names := urlRe.SubexpNames()fmt.Printf("URL命名分組匹配:\n")for i, name := range names {if i != 0 && name != "" && i < len(matches) {fmt.Printf("%s: %s\n", name, matches[i])}}fmt.Println()// 3\. 非捕獲分組(?:pattern) - 分組但不捕獲text3 := "電話: 010-12345678 或 021-87654321"// 使用非捕獲分組匹配區號格式,但只捕獲完整號碼phonePattern2 := `(?:010|021)-(\d{8})`phoneRe2 := regexp.MustCompile(phonePattern2)allPhoneMatches := phoneRe2.FindAllStringSubmatch(text3, -1)fmt.Printf("非捕獲分組示例:\n")for i, match := range allPhoneMatches {fmt.Printf("第%d個匹配:\n", i+1)fmt.Printf(" 完整匹配: %s\n", match[0])fmt.Printf(" 后8位號碼: %s\n", match[1])}
}
3.3 替換操作
// 演示替換操作
func demonstrateReplacement() {fmt.Println("=== 替換操作演示 ===")// 1\. ReplaceAllString - 簡單替換text1 := "今天是2023年12月25日,明天是2023年12月26日"datePattern := `\d{4}年\d{1,2}月\d{1,2}日`dateRe := regexp.MustCompile(datePattern)replaced1 := dateRe.ReplaceAllString(text1, "[日期]")fmt.Printf("原文: %s\n", text1)fmt.Printf("替換后: %s\n\n", replaced1)// 2\. ReplaceAllStringFunc - 使用函數替換text2 := "蘋果5元,香蕉3元,橙子8元"pricePattern := `(\w+)(\d+)元`priceRe := regexp.MustCompile(pricePattern)replaced2 := priceRe.ReplaceAllStringFunc(text2, func(match string) string {// 可以在這里做復雜的處理return "[商品價格]"})fmt.Printf("原文: %s\n", text2)fmt.Printf("函數替換后: %s\n\n", replaced2)// 3\. 使用分組進行替換text3 := "聯系人:張三,電話:138-1234-5678"contactPattern := `聯系人:(\w+),電話:([\d-]+)`contactRe := regexp.MustCompile(contactPattern)// $1, $2引用捕獲的分組replaced3 := contactRe.ReplaceAllString(text3, "姓名: $1, 手機: $2")fmt.Printf("原文: %s\n", text3)fmt.Printf("分組替換后: %s\n\n", replaced3)// 4\. 復雜的分組替換 - 格式化電話號碼text4 := "電話號碼:13812345678, 15987654321, 18611112222"phonePattern3 := `(\d{3})(\d{4})(\d{4})`phoneRe3 := regexp.MustCompile(phonePattern3)formatted := phoneRe3.ReplaceAllString(text4, "$1-$2-$3")fmt.Printf("原文: %s\n", text4)fmt.Printf("格式化后: %s\n", formatted)
}
3.4 常用方法表
方法名 | 功能描述 | 性能建議 |
---|---|---|
MatchString | 直接判斷是否匹配,內部自動編譯正則表達式 | 適用于單次匹配,頻繁調用需預編譯 |
Compile / MustCompile | 預編譯正則表達式,返回可重復使用的*Regexp 對象 | 必須用于高頻匹配場景 |
FindString / FindAllString | 查找匹配的子串,不捕獲分組內容 | 簡單查找場景 |
FindStringSubmatch / FindAllStringSubmatch | 查找匹配的子串并捕獲分組內容 | 需要提取分組時使用 |
ReplaceAllString / ReplaceAllStringFunc | 替換匹配的子串,支持靜態替換或函數式動態替換 | 替換場景通用方法 |
Split | 按正則表達式分割字符串 | 替代strings.Split 處理復雜分隔符 |
標志位(?i)(?s)(?m) | 修改正則表達式匹配規則(不區分大小寫、點號匹配換行、多行模式) | 復雜文本匹配時使用 |
四、常見的正則表達式
以下是一些常見的正則表達式示例,用于匹配不同類型的文本。
注意:這些示例是基于常見的文本格式,可能需要根據實際需求進行調整。
1. 匹配郵箱
^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$
2. 匹配手機號(以中國大陸為例)
^1[3-9][0-9]{9}$
3. 匹配身份證號(中國18位)
^\d{6}(18|19|20)?\d{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])\d{3}(\d|X|x)$
4. 匹配URL
^https?://[^\s/$.?#].[^\s]*$
5. 匹配IP地址(IPv4)
^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$
??注意:上面的正則沒有嚴格驗證0-255,可用下面更嚴格的版本:
^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
6. 匹配數字字符串
^\d+$
7. 匹配僅包含字母(不區分大小寫)
^[a-zA-Z]+$
8. 匹配密碼復雜性(8-20位,包含字母和數字)
^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$
9. 匹配日期(YYYY-MM-DD)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
10. 匹配正整數
^[1-9]\d*$
11. 匹配負整數
^-[1-9]\d*$
12. 匹配浮點數(正負均可)
^-?\d+(\.\d+)?$
13. 匹配由字母、數字和下劃線組成的字符串
^\w+$
14. 匹配中文字符
[\p{Han}]+
需要 go 1.13+ 支持,更通用。
15. 匹配郵政編碼(中國6位)
^\d{6}$
16. 匹配含有 HTML 標簽的文本
<([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>(.*?)</\1>
17. 匹配空白行
^\s*$
18. 匹配 MAC 地址
^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$
19. 匹配單行注釋
^//.*$
20. 匹配多行注釋
(?s)/\*.*?\*/
4厄3
五、實戰演示
package mainimport ("fmt""regexp""strings"
)// Link 鏈接結構體
type Link struct {URL stringText string
}// String 返回鏈接的字符串表示
func (l Link) String() string {return fmt.Sprintf("URL: %s, 文本: %s", l.URL, l.Text)
}// HTMLDataExtractor HTML數據提取器
type HTMLDataExtractor struct {patterns map[string]*regexp.Regexp
}// NewHTMLDataExtractor 創建HTML數據提取器,帶錯誤處理
func NewHTMLDataExtractor() (*HTMLDataExtractor, error) {extractor := &HTMLDataExtractor{patterns: make(map[string]*regexp.Regexp),}// 編譯正則表達式并處理錯誤if err := extractor.compilePatterns(); err != nil {return nil, fmt.Errorf("編譯正則表達式失敗: %w", err)}return extractor, nil
}// compilePatterns 編譯常用的正則表達式模式,帶錯誤處理
func (e *HTMLDataExtractor) compilePatterns() error {patterns := map[string]string{"title": `<title[^>]*>([^<]+)</title>`,// 優化meta標簽匹配,支持更靈活的屬性順序和引號"meta_desc": `(?i)<meta\s+(?:[^>]*\s+)?name\s*=\s*["']description["'][^>]*\s+content\s*=\s*["']([^"']*)["']`,// 優化鏈接匹配,支持單引號/雙引號和更多屬性格式"links": `<a[^>]*href\s*=\s*(?:["'])([^"']+)["'][^>]*>([^<]*)</a>`,"images": `<img[^>]*src\s*=\s*(?:["'])([^"']+)["'][^>]*>`,"emails": `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`,"phones": `1[3-9]\d{9}`,"prices": `¥\s*(\d+(?:\.\d{2})?)`,// 修復:使用十六進制范圍表示中文漢字"chinese_text": `[\x{4e00}-\x{9fa5}]+`,}for name, pattern := range patterns {re, err := regexp.Compile(pattern)if err != nil {return fmt.Errorf("編譯模式 %s 失敗: %w", name, err)}e.patterns[name] = re}return nil
}// ExtractTitle 提取標題
func (e *HTMLDataExtractor) ExtractTitle(html string) string {matches := e.patterns["title"].FindStringSubmatch(html)if len(matches) > 1 {return strings.TrimSpace(matches[1])}return ""
}// ExtractMetaDescription 提取meta描述
func (e *HTMLDataExtractor) ExtractMetaDescription(html string) string {matches := e.patterns["meta_desc"].FindStringSubmatch(html)if len(matches) > 1 {return strings.TrimSpace(matches[1])}return ""
}// ExtractLinks 提取所有鏈接
func (e *HTMLDataExtractor) ExtractLinks(html string) []Link {matches := e.patterns["links"].FindAllStringSubmatch(html, -1)var links []Linkfor _, match := range matches {if len(match) > 2 {links = append(links, Link{URL: strings.TrimSpace(match[1]),Text: strings.TrimSpace(match[2]),})}}return links
}// ExtractImages 提取所有圖片URL
func (e *HTMLDataExtractor) ExtractImages(html string) []string {matches := e.patterns["images"].FindAllStringSubmatch(html, -1)var images []stringfor _, match := range matches {if len(match) > 1 {images = append(images, strings.TrimSpace(match[1]))}}return images
}// ExtractEmails 提取所有郵箱地址
func (e *HTMLDataExtractor) ExtractEmails(html string) []string {return e.patterns["emails"].FindAllString(html, -1)
}// ExtractPhones 提取所有手機號碼
func (e *HTMLDataExtractor) ExtractPhones(html string) []string {return e.patterns["phones"].FindAllString(html, -1)
}// ExtractPrices 提取所有價格
func (e *HTMLDataExtractor) ExtractPrices(html string) []string {matches := e.patterns["prices"].FindAllStringSubmatch(html, -1)var prices []stringfor _, match := range matches {if len(match) > 1 {prices = append(prices, match[1])}}return prices
}// ExtractChineseText 提取中文文本
func (e *HTMLDataExtractor) ExtractChineseText(html string) []string {return e.patterns["chinese_text"].FindAllString(html, -1)
}// 使用示例
func main() {fmt.Println("=== HTML數據提取演示 ===\n")// 示例HTML內容sampleHTML := `<!DOCTYPE html><html><head><title>購物網站 - 最優質的商品</title><meta NAME="description" content='這是一個專業的購物網站,提供優質商品和服務'></head><body><h1>歡迎來到我們的商店</h1><p>聯系我們:info@example.com 或者 support@shop.com</p><p>客服電話:13812345678,18611112222</p><div class="products"><h2>熱門商品</h2><div class="product"><img src='/images/phone.jpg' alt='手機'><h3>智能手機</h3><p>價格:¥2999.00</p><a href='/product/phone' class='btn'>查看詳情</a></div><div class="product"><img src=/images/laptop.jpg alt=筆記本><h3>筆記本電腦</h3><p>價格:¥5999.99</p><a href='/product/laptop'>立即購買</a></div></div><footer><p>更多信息請訪問 <a href='https://www.example.com'>我們的官網</a></p></footer></body></html>`// 創建提取器并處理錯誤extractor, err := NewHTMLDataExtractor()if err != nil {fmt.Printf("錯誤: %s\n", err)return}// 提取各種數據fmt.Println("📌 頁面標題:")fmt.Printf(" %s\n\n", extractor.ExtractTitle(sampleHTML))fmt.Println("📝 頁面描述:")fmt.Printf(" %s\n\n", extractor.ExtractMetaDescription(sampleHTML))fmt.Println("📧 郵箱地址:")emails := extractor.ExtractEmails(sampleHTML)for i, email := range emails {fmt.Printf(" %d. %s\n", i+1, email)}fmt.Println()fmt.Println("📞 手機號碼:")phones := extractor.ExtractPhones(sampleHTML)for i, phone := range phones {fmt.Printf(" %d. %s\n", i+1, phone)}fmt.Println()fmt.Println("🔗 鏈接:")links := extractor.ExtractLinks(sampleHTML)for i, link := range links {fmt.Printf(" %d. %s\n", i+1, link)}fmt.Println()fmt.Println("🖼? 圖片:")images := extractor.ExtractImages(sampleHTML)for i, img := range images {fmt.Printf(" %d. %s\n", i+1, img)}fmt.Println()fmt.Println("💰 價格:")prices := extractor.ExtractPrices(sampleHTML)for i, price := range prices {fmt.Printf(" %d. ¥%s\n", i+1, price)}fmt.Println()fmt.Println("中文文本片段:")chineseTexts := extractor.ExtractChineseText(sampleHTML)for i, text := range chineseTexts {if len(text) > 2 { // 過濾短文本fmt.Printf(" %d. %s\n", i+1, text)}}
}
記住:正則表達式是一把雙刃劍,用好了事半功倍,用錯了可能帶來性能問題。關鍵是要在合適的場景使用合適的工具!