正则表达式(Regular Expression)是一种基于匹配模式的文本处理工具。它有如同一门编程语言一样的模式表示法,赋予使用者描述和分析文本的能力。关于正则表达式,其历史发展和知识体系都非常繁杂,不太可能在此全部展开,如果你对正则表达式有兴趣并愿意深入了解,推荐阅读《精通正则表达式 - Jeffrey E.F Friedl》一书,相信你会得到最全面的理解。在此我们只讲与 Go 相关的以及使用方式即可。
首先说说正则引擎:
-
目前的主流正则引擎又分为 3 类:一、DFA,二、传统型 NFA,三、POSIX NFA。
-
目前使用 DFA 引擎的程序主要有:awk,egrep,flex,lex,MySQL,Procmail 等;
-
使用传统型 NFA 引擎的程序主要有:GNU Emacs,Java,ergp,less,more,.NET 语言,PCRE library,Perl,PHP,Python,Ruby,sed,vi;
-
使用 POSIX NFA 引擎的程序主要有:mawk,Mortice Kern Systems’ utilities,GNU Emacs(使用时可以明确指定);
-
也有使用 DFA/NFA 混合的引擎:GNU awk,GNU grep/egrep,Tcl。
以上信息引用自《精通正则表达式》。可见传统 NFA 使用最为广泛,大多数编程语言都内置 NFA 引擎实现正则功能。Go 与 2009 年面世,和其他编程语言一样,Go 也内置 NFA 正则引擎。
全面正则表达式语法体系复杂度可比肩一门编程语言,具体使用语法请参考相关教程或工具书,在此只演示一些常用的正则规则:
常用正则规则(其他请详见官方文档):
符号 | 意义 |
---|---|
\d | 数字 |
\D | 非数字 |
\w | 单词字符:大小写字母+数字+下划线_ |
\W | 非单词字符 |
\s | 空白字符:\t、\n、\r、\f 之一 |
\S | 非空白字符 |
. | 换行符以外的任意字符 |
. | 一个真正的点 |
re+ | re 表示的片段出现 1 到多次 |
re* | re 表示的片段出现 0 到多次 |
re? | re 表示的片段出现 0 到 1 次 |
re | re 表示的片段出现 n 次 |
re | re 表示的片段出现 m 到 n 次 |
re | re 表示的片段出现 m 到无限多次 |
re | re 表示的片段出现 0 到 n 次 |
[abc] | a、b、c 中间的一个字符 |
[\s\S] | 习惯上表示绝对的任意字符 |
[a-z] | a 到 z 中的任意一个字符 |
[^abc] | 除了 abc 以外的任意字符 |
re1 | re2 |
^re$ | re片段匹配全文,^匹配字符串开始,$匹配字符串结尾 |
re*?,re+? | re*或 re+所代表的片段,使用非贪婪模式 |
非贪婪模式 | re*或 re+匹配的字符,越少越好 |
[a-z]*?http | 任意多个小写字母,截止到 http 出现为止 |
Go 标准库提供 regexp 包支持正则表达式搜索。Go 的正则表达式采用 RE2 语法(除了\c、\C),和 Perl、Python 等语言的正则基本一致。
https://studygolang.com/pkgdoc Go 正则包也提供一些正则语法参考与使用用例,值得一提的是,该包保证正则表达式搜索复杂度为 O(n),其中 n 为输入的长度。这一点很多其他开源实现是无法保证的。虽然在性能上无需担忧,但正则引擎的性能相对于 strings 包还是较高的,建议日常的字符搜索或替换使用 strings 包即可,当涉及较复杂的模式匹配时才用正则表达式。
regexp 包提供多种正则匹配的方式,四种主要匹配模式的工厂函数:
func Compile(expr string) (*Regexp, error)func CompilePOSIX(expr string) (*Regexp, error)func MustCompile(str string) *Regexpfunc MustCompilePOSIX(str string) *Regexp
返回*Regexp 后就可搜索目标文本了,其内部提供多种搜索方法,最常使用的是 MustCompile()模式匹配函数,返回一个*Regexp 指针,该类型指针提供多种搜索方法,FindAllStringSubmatch()支持搜索所有的全匹配和子模式匹配。其他的请根据需求阅读包文档吧。
以下演示一下常用的手机、邮箱、链接、身份证号码的匹配示例:
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
)
//正则表达式演练
const (
RE_PHONE = `(1[356789]\d)(\d{4})(\d{4})`
RE_EMAIL = `(\w+?)(\.\w+)?@(\w+)?\.(\w{2,5})(\.\w{2,3})?`
RE_LINK = `<a[\s\S]+?href="(http[\s\S]+?)"`
/*身份证号:4-50121-1970-11-05-5756*/
//reID = `[1-6]\d{5}-( (19\d{2}) | (20((0\d)|(1[0-8]))) )-((0[1-9])|(1[012]))-((0[1-9])|([12]\d)|(3[01]))-\d{3}[\dX]`
RE_IDENTITY = `[1-6]\d{5}((19\d{2})|(20((0\d)|(1[0-8]))))((0[1-9])|(1[012]))((0[1-9])|([12]\d)|(3[01]))\d{3}[\dX]`
)
func HandleError(where string, err error) {
if err != nil {
fmt.Println("发现错误:", err, "「", where, "」")
os.Exit(1)
}
}
func GetHtmlContent(url string) (html string) {
resp, err := http.Get(url)
HandleError("http.Get", err)
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
HandleError("ioutil.ReadAll", err)
return string(bytes)
}
//正则电话号码
func PhoneSpider() {
//爬取电话号码网页
htmlForPhone := GetHtmlContent("http://tieba.baidu.com/p/5395331642")
//正则匹配电话号码
compilePhone := regexp.MustCompile(RE_PHONE)
matchPhones := compilePhone.FindAllStringSubmatch(htmlForPhone, -1)
//输出结果
for _, v := range matchPhones {
fmt.Println(v[0])
}
}
func EmailSpider() {
//爬取邮箱
htmlForEmail := GetHtmlContent("https://www.douban.com/group/topic/113790741/")
htmlForEmail += "fsj.qie@aa.com" //加一下三级域名有点号的邮箱
htmlForEmail += "sfsaf.rwer@bb.com.cn"
//正则匹配邮箱
compileEmail := regexp.MustCompile(RE_EMAIL)
matchEmail := compileEmail.FindAllStringSubmatch(htmlForEmail, -1)
//输出结果
//fmt.Println(matchEmail[0])
for _, v := range matchEmail {
fmt.Println(v[0])
}
}
func IdNumSpider() {
//爬取身份证号
htmlForIden := GetHtmlContent("https://www.sohu.com/a/239090484_99956882")
//正则匹配邮箱
compileId := regexp.MustCompile(RE_IDENTITY)
matchId := compileId.FindAllStringSubmatch(htmlForIden, -1)
//输出结果
for _, x := range matchId {
fmt.Println(x[0])
}
}
//抓取导航网站首页获取链接
func LinksSpider() {
html := GetHtmlContent("https://www.hao123.com/")
//fmt.Println(html)
re := regexp.MustCompile(RE_LINK)
rets := re.FindAllStringSubmatch(html, -1)
for _, x := range rets {
fmt.Println(x[1])
}
}
至此 Go 的正则表达式使用就介绍到这,正则表达式是强大的编程工具,一旦你熟悉使用该工具会让你在日常开发中事半功倍,建议继续深入学习正则表达式!