正则表达式学习笔记
正则表达式在计算机领域可以说是用途广泛,写爬虫时,用他过滤无用的html标签,或者只保留汉字,预防代码注入漏洞时,可以用它来过滤危险命令,删除文件时,还可以通过正则表达式来批量删除指定的文件,所以很难不学习。
python与正则表达式
python的库功能强大,本文用python来学习正则表达式。在Python中,正则表达式主要通过re模块来实现。re库是Python的标准库之一,专门用于处理正则表达式。
re库主要有search,match,sub等方法。当使用search()、match()或finditer()时,返回的是一个Match对象。这个对象包含了匹配的详细信息。
re.match()函数用于搜索字符串的开始是否与给定的正则表达式模式匹配。如果匹配成功,它返回一个Match对象;如果没有匹配项,它返回None。re.match()只从字符串的开头开始匹配。如果模式在字符串的开始位置找不到匹配项,即使在其他地方有匹配项,re.match()也不会成功。
re.search()用于搜索给定字符串中的第一个与指定正则表达式模式匹配的子串。与re.match()不同,re.search()会检查字符串中的所有位置,而不仅仅是开始位置。如果re.search()找到了一个匹配项,它将返回一个Match对象;如果没有找到匹配项,它将返回None。re.search()只会返回第一个找到的匹配项的Match对象。如果你想找到字符串中所有的匹配项,应该使用re.findall()或re.finditer()。
下面是一段示例代码:
1 | import re |
输出:
1 |
|
不妨从这段正则表达式入手[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}。
[a-zA-Z0-9._%+-] 定义了一个字符类,其中的字符可以是任何小写字母a-z、大写字母A-Z、数字0-9或特殊字符._%+-中的一个。 + 表示前面的字符类可以出现一次或多次。@匹配邮箱地址中的@.
[a-zA-Z0-9.-] 定义了一个字符类,其中的字符可以是任何小写字母a-z、大写字母A-Z、数字0-9或特殊字符.和-中的一个。+ 表示前面的字符类可以出现一次或多次。由于.在正则表达式中是一个特殊字符,表示匹配任何字符(除了换行符),所以使用\对其进行转义,使其匹配实际的.字符。[a-zA-Z] 定义了一个字符类,其中的字符可以是任何小写字母a-z或大写字母A-Z中的一个。{2,} 表示前面的字符类必须至少出现两次。这是为了确保顶级域名至少有两个字符长,符合大多数实际情况。
正则表达式必知必会
来自书《正则表达式必知必会》
匹配单字符
匹配纯文本
正则表达式甚至可以只包含纯文本,但注意正则表达式区别大小写。
1 | import re |
1 | output: |
匹配任意字符
.字符可以匹配任何一个单个字符,包括.本身。
1 | match_output=re.findall(r"b.t","bAtbotbatb.t") |
1 | output: |
.通常不匹配换行符。
匹配特殊字符
如果要只匹配.呢?显然我们需要一个转义符号,在正则表达式中,\可以进行转义。
1 | match_output=re.findall(r"b\.t","bAtbotbatb.t") |
1 | output: |
匹配一组字符
字符集合
在正则表达式中,字符集合允许我们匹配来自某一集合的任意一个字符。字符集合是通过将一组字符放在[]中来定义的。
比如:
1 | match_output=re.findall(r"[bc]at","cat bat lat dat") |
1 | output: |
其中的[bc]是一个字符集合,这个集合将只匹配字符b或者c。
字符集合区间
当我们需要匹配全部阿拉伯数字或者英文字母时,在字符集合里面一个一个穷举过于麻烦了,可以使用-定义范围,但-也可以被转义。
1 | match_output=re.findall(r"[A-Z][0-9][0-9a-f]","A10 A2f B9c C1z d99 Q09") |
1 | output: |
利用正则表达式匹配中文就用到了字符集合区间[一-龥]:
1 | match_output=re.findall(r"[一-龥]","a中b文c匹配def") |
1 | output: |
当然匹配中文没那么简单,具体可以参考:
取非匹配
某些场合,我们需要排除一些不需要的字符,可以使用^来进行取非匹配。比如[^0-9]对阿拉伯数字进行取非。
1 | match_output=re.findall(r"[^0-9]","abc123decade79") |
1 | output: |
或匹配
| 符号代表“或”操作,它用于指定两个或多个可选的模式中的一个。比如cat|dog,和[]有点类似,但是可以匹配字符串。
元字符的使用
元字符是具有特殊意义的字符,它们不匹配字面值,而是代表特定的模式。
数字/非数字和字母+数字/非字母+数字
用元字符\d匹配任何一个数字字符,\D匹配任何一个非数字,\w匹配任何一个字母数字或者下划线字符,等价于[a-zA-Z0-9_],\W相反。
1 | match_output=re.findall(r"\d\D\w\W","0A_? 3B_b") |
1 | output: |
空白字符
\s等价于[\f\n\r\t\v],\S与之相反。
十六进制或八进制
八进制和十六进制序列允许匹配指定的ASCII或Unicode字符。但python只支持十六进制和Unicode十六进制。
1 | match_output=re.findall(r"\x41","01aA_? 3B_b") |
1 | output: |
重复匹配
前面所学的都只能匹配一个字符,如果要匹配多个呢?就需要一些其他的元字符来实现。
匹配一个或多个字符
元字符+匹配它前面的元素出现的一次或多次连续重复。
1 | match_output=re.findall(r"\d+","123abc456hjkl") |
1 | output: |
匹配零个或多个字符
这种匹配用*完成。
1 | str_list=["b.","b","b...","c"] |
1 | output: |
匹配零个或一个字符
这种需求看似奇怪,其实很常见,比如匹配url,有http和https两种协议,此外,访问文件服务器也有ftp和sftp两种前缀。可以用元字符?解决。
1 | str_list=["http://baidu.com","https://qwq.com","httpsssss://google.com","http://google.com"] |
1 | output: |
匹配的重复次数
之前提到的+和*,匹配的个数都没有上限,如何为匹配的个数设置一个区间呢?可以使用{}。比如匹配三位数的阿拉伯数字。
1 | str_list=["111","22","333","4","666"] |
1 | output: |
此外,还可以用{2,5}这样的格式,表示重复2-5次;{2,}表示最少两次;{,3}表示最多三次。{} 设置的重复是贪婪的,这意味着它会尽可能匹配更多的重复。例如,在字符串 “aaaaa” 中,正则表达式 a{2,4} 将匹配前四个 ‘a’。
防止过度匹配
如果希望匹配尽可能少的重复,可以在大括号后加上一个 ?。例如,对于字符串 “aaaaa”,正则表达式 a{2,4}? 将只匹配两个 ‘a’。
位置匹配
某些场合,需要对某段字符串的特定位置进行匹配。
边界
单词边界
由限定符\b指定的单词边界,\b匹配一个单词的开始或者结尾。单词边界可以被认为是 \w 和 \W 之间的边界,或者位于字符串的开始或结束,并与 \w 相邻。
1 | match_output=re.findall(r"\bcat\b","cat scatter cats") |
1 | output: |
\b匹配且只匹配一个位置,可以发现匹配得到的结果是三个字符而不是五个。\b 表示单词边界,是一个“位置字符”,表示一个位置,而不是一个实际的字符。
如果要不匹配一个单词边界,可以使用\B。\B 是 \b 的反义,它表示非单词边界。就像 \b 是用来匹配位于单词边界的位置,\B 是用来匹配不在单词边界的位置。
1 | match_output=re.search(r'\Bcat\B',"cat scatter cats") |
1 | output: |
字符串边界
^ 匹配字符串的开头,$匹配字符串的结尾。
1 | str_list=["0x0a","0xff","333","0x04","666"] |
1 | output: |
1 | str_list=["0x0a","0xff","333","0x04","666"] |
1 | output: |
分行匹配模式
在多行模式下,^ 和 $ 还可以匹配每一行的开始和结束。在Python的re模块中,可以使用re.MULTILINE或re.M标志来激活多行模式。
1 | s='''qwq |
1 | output: |
子表达式的使用
有时候我们的需求比较复杂,比如需要匹配重复abc的字符串,那abc*肯定不能满足需求,这时候就要用到子表达式。子表达式(通常被称为捕获组或捕获括号)允许你将正则表达式的部分内容括在一对括号中,以此来捕获它们匹配的文本。子表达式用()。
1 | str_list=["0x0a","0xff","333","abc","abcabc"] |
1 | output: |
非捕获组
非捕获组是正则表达式中的一种构造,允许你定义一个组来应用量词或者进行组合,但不捕获该组匹配的文本。这意味着匹配的结果不会保存供之后引用或检索。非捕获组在正则表达式中用 (?:...) 表示。使用非捕获组可能会提高正则表达式的性能,因为引擎不需要保存匹配的子字符串。
命名子表达式
使用 (?P<name>...) 来命名一个子表达式。
1 | text = "123-abc" |
1 | output: |
回溯引用
如果一个正则表达式中的部分与文本匹配,回溯引用可以在后面的表达式中引用或重新使用这部分匹配的文本。每个捕获组都有一个关联的编号。第一个捕获组是 1,第二个是 2,依此类推。比如查找重复的单词:
1 | match_output=re.search(r'\b(\w+)\b\s+\1\b',"cat cat dog ") |
1 | output: |
还有一个例子是匹配HTML的标题标签(<H1>到<H6>)。
1 | s='''<H1>title</H1> |
1 | output: |
如果用传统的正则表达式<[hH][1-6]>,*?</[hH][1-6]>,那么<h3>title</h4>这种不合法的标题也会被匹配到。使用回溯引用来避免这种情况。
在替换中回溯引用也经常被用到。
1 | s = "hello, world, good, morning" |
1 | output: |
前后查找
正则表达式中的前向和后向查找(也称为前瞻和后顾)是一种高级的匹配技术,允许你在匹配某个模式时考虑其前后的内容,但这些前后的内容并不包含在最终的匹配结果中。比如我们需要提取web页面的标题内容:
1 | <head><title>Title</title></head> |
如果用<title>.*</title>来匹配,显然<title>和</title>是我们不需要了,白白浪费性能。
可以使用前向查找来解决这个问题:
1 | s="<head><title>Title</title></head>" |
1 | output: |
向前查找
一个向前查找模式就是一个以?=开头的子表达式,需要匹配的文本跟在=的后面。
比如匹配出URL地址中的协议名部分:
1 | s="http://www.baidu.com" |
1 | output: |
向前查找用于查找出现在被匹配文本之后的字符,而不消费这个字符(可以简单理解为不包含在返回的匹配结果中)。
向后查找
向后查找操作符是?<=。
书中有一个小提示帮助理解:向后查找符有小于号,这个小于号是一个箭头,指向文本阅读方向的后方。
比如匹配美元价格:
1 | result=re.findall(r'(?<=\$)[0-9.]+',"$1.00 $5.99") |
1 | output: |
最终匹配的结果没有美元的$字符。
向前查找和向后查找结合
参考最初给出的例子(?<=<title>).*(?=</title>),开头的(?<=<title>)用于向后查找<title>,结尾的(?=</title>)用于向前匹配</title>。
对前后查找取非
前后查找还有一种不太常用的方法叫负前后查找。负向前查找将向前查找不与给定模式相匹配文本,负向后查找同理。前后查找用!来取非。
书中给出了各种前后查找操作符:
| 操作符 | 说明 |
|---|---|
| (?=) | 正向前查找 |
| (?!) | 负向前查找 |
| (?<=) | 正向后查找 |
| (?<!) | 负向后查找 |
举个例子,匹配非美元的价格:
1 | result=re.findall(r'(?<!\$)[0-9]+\.[0-9]+',"$1.00 $5.99 ¥100.00 ¥4.99") |
1 | output: |
嵌入条件
嵌入条件(也被称为条件表达式或条件子表达式)基于某些条件进行不同的匹配。条件可以是一个子组是否匹配,或者一个查找断言是否为真。
在python中,需要安装regex库来支持这种高级特性:
1 | pip install regex |
正则表达式里的条件用?来定义。
子组存在条件
基于一个指定的子组是否匹配来决定接下来的匹配方式。语法:
1 | (?(id/name)yes-pattern|no-pattern) |
id/name:子组的ID或名称yes-pattern:如果子组存在,应匹配的模式no-pattern:如果子组不存在,应匹配的模式
比如:
1 | pattern = regex.compile(r'((a)?b(?(2)c|d))') |
1 | output: |
其中最外面大括号是1,(a)是2,那么(?(2)c|d)的意思是,如果a被匹配到了,则应该匹配c,否则匹配d,返回结果也验证了之前的推理。这里把整个正则表达式括起来主要是为了输出匹配的结果。
一些常用的正则表达式
匹配中文
[一-龥]
更复杂的实现参考: