作者:shark
一、正则表达式介绍
正则表达式(称为RE,或正则,或正则表达式模式)指定了一组与之匹配的字符串;本质上是嵌入在Python中的一种微小的、高度专业化的编程语言,可通过 re 模块获得。
模块内的函数可以让你检查某个字符串是否跟给定的正则表达式匹配(或者一个正则表达式是否匹配到一个字符串,这两种说法含义相同)。
使用这种小语言,你可以为要匹配的可能字符串集指定规则;此集可能包含英语句子,电子邮件地址,TeX命令或你喜欢的任何内容。 然后,您可以询问诸如“此字符串是否与模式匹配?" 或者 "模式匹配到了哪些字符串" 。
其实很多其他语言或者软件工具都会支持正则表达式。
比如 Java Perl JS vi sed awk 等。Python 中的 re 模块提供的是类似 Perl 语言中的正则表达式。
正则表达式模式被编译成一系列字节码,然后由用 C 编写的匹配引擎执行。
友情提示:
正则表达式语言相对较小且受限制,因此并非所有可能的字符串处理任务都可以使用正则表达式完成。
还有一些任务 可以 用正则表达式完成,但表达式变得非常复杂。 在这些情况下,你最好编写 Python 代码来进行处理;虽然 Python 代码比精心设计的正则表达式慢,但它也可能更容易理解。
二、正则表达式语法介绍
正则表达式可以拼接; 如果 A 和 B 都是正则表达式, 那么 AB 也是正则表达式。
正则表达式可以包含普通或者特殊字符。绝大部分普通字符,比如 'A', 'a', 或者 '0',都是最简单的正则表达式。它们就匹配自身。
可以拼接普通字符,所以 last 匹配字符串 'last'. (在此文档的其他部分,我们将用 lenovo这种不带引号的方式表示正则表达式,要匹配的字符串用 'lenovo' ,单引号形式。比如: 正则表达式 lenovo 可以匹配到字符串 'lenovo')。
三、匹配字符
由于正则表达式用于对字符串进行操作,因此我们将从最常见的任务开始:匹配字符。
1. 普通字符
大多数字母和字符只会匹配自己。 例如,正则表达式 test 将完全匹配字符串 test 。
你可以启用一个不区分大小写的模式,让这个正则匹配 Test 或 TEST,稍后会介绍。
2. 特殊字符
一些字符是特殊的 metacharacters (元字符),并且不匹配自己。 相反,它们表示应该匹配一些与众不同的东西,或者通过重复它们或改变它们的含义来影响正则的其他部分。
本文档的大部分内容都致力于讨论各种元字符及其功能。
接下来一个一个介绍,在展示示例的时候,我使用了 re 模块中的 search() 方法。这个方法就是从整个字符串的开头查找符合正则模式的字符,直到找到第一个匹配的字符为止,后面假如还有符合的字符串,也不会继续匹配。
返回的是一个 RE 对象,匹配到的内容会作为对象 match 属性的值。
正则特殊字符 匹配内容 .匹配除换行符( \n)以外的单个任意字符
In [6]: import re
In [7]: s = '联想 lenovo 2020'
In [8]: re.search('.', s)
Out[8]: <_sre.SRE_Match object; span=(0, 1), match='联'>
正则特殊字符 匹配内容 ^匹配整个字符串的开头 
In [18]: re.search('^联想', s)
Out[18]: <_sre.SRE_Match object; span=(0, 2), match='联想'>
正则特殊字符 匹配内容 `<article class="_2rhmJa" style="box-sizing: border-box; display: block; font-weight: normal; line-height: 1.8; margin-bottom: 20px; color: rgb(64, 64, 64); font-family: -apple-system, BlinkMacSystemFont, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;" 匹配整个字符串的结尾 
In [20]: re.search('2020/article>, s)
Out[20]: <_sre.SRE_Match object; span=(10, 14), match='2020'>
正则特殊字符 匹配内容 []匹配中括号内字符中的任何一个 比如: [1a]匹配到1或者a
In [21]: re.search('[en]', s)
Out[21]: <_sre.SRE_Match object; span=(4, 5), match='e'>
正则特殊字符 匹配内容 [-]匹配连续的字符范围 比如 [0-9]表示0到9之间的任意一个数字
In [22]: re.search('[a-z]', s)
Out[22]: <_sre.SRE_Match object; span=(3, 4), match='l'>
In [23]: re.search('[0-9]', s)
Out[23]: <_sre.SRE_Match object; span=(10, 11), match='2'>
In [24]: re.search('[0-9a-z]', s)
Out[24]: <_sre.SRE_Match object; span=(3, 4), match='l'>
注意,在 中括号内的任何元字符都不生效,都会成为普通的字符,只能匹配到自身。比如:
[akm$]将匹配'a','k'、'm'或'/article>中的任意一个字符;'/article>通常是一个元字符,但在一个中括号内它被剥夺了特殊性。
In [28]: s1 = '$520.00'
In [29]: re.search('00/article>, s1)
Out[29]: <_sre.SRE_Match object; span=(5, 7), match='00'>
In [30]: re.search('[00$]', s1)
Out[30]: <_sre.SRE_Match object; span=(0, 1), match='/article>>)
^在中括号中是取反的意思,比如[^a58]匹配到不是a、5、8中的任意一个其他字符 。
In [41]: s2 = 'ab58abc'
In [42]: re.search('[^a58]', s2)
Out[42]: <_sre.SRE_Match object; span=(1, 2), match='b'>
正则特殊字符 匹配内容 \w匹配单个字母、数字、汉字或下划线 类似于 [a-zA-Z0-9_]
In [37]: re.search('e\w', s)
Out[37]: <_sre.SRE_Match object; span=(4, 6), match='en'>
In [38]: re.search('e\w\w', s)
Out[38]: <_sre.SRE_Match object; span=(4, 7), match='eno'>
正则特殊字符 匹配内容 \W匹配任何不是单词字符的字符。 这与 \w正相反。类似于[!a-zA-Z0-9_]
In [254]: re.search('\W', '.')
Out[254]: <_sre.SRE_Match object; span=(0, 1), match='.'>
In [261]: re.search('\W', ' ')
Out[261]: <_sre.SRE_Match object; span=(0, 1), match=' '>
正则特殊字符 匹配内容 \d匹配单个数字 
In [40]: re.search('\d\d\d', s)
Out[40]: <_sre.SRE_Match object; span=(10, 13), match='202'>
正则特殊字符 匹配内容 \s匹配单个任意的空白符,这等价于 [ \t\n\r\f\v]\S匹配任何非空白字符, [^ \t\n\r\f\v] 
In [18]: re.search('^联想', s)
Out[18]: <_sre.SRE_Match object; span=(0, 2), match='联想'>
正则特殊字符 匹配内容 \b匹配单词边界 注意,通常
\b定义为\w和\W之间的边界,或者\w和字符串开始/结尾的边界, 意思就是r'\bfoo\b'匹配'foo','foo.','(foo)','bar foo baz'但不匹配'foobar'或者'foo3'。
In [44]: s3 = 'hello2020 hello.'
In [45]: re.search('hello', s3)
Out[45]: <_sre.SRE_Match object; span=(0, 5), match='hello'>
In [46]: re.search(r'\bhello\b', s3)
Out[46]: <_sre.SRE_Match object; span=(10, 15), match='hello'>
正则特殊字符 匹配内容 ` ` 表示或的关系,比如 `A B , ***A***和 ***B*** 可以是任意正则表达式,创建一个正则表达式,匹配 A 或者 B。循环扫描字符串时,一旦 ***A*** 匹配成功, ***B*** 就不再进行匹配。如果要匹配'' 字符,使用|, 或者把它包含在字符集里,比如[]`. 
In [48]: s
Out[48]: '联想 lenovo 2020'
In [49]: re.search('e|n', s)
Out[49]: <_sre.SRE_Match object; span=(4, 5), match='e'>
正则特殊字符 匹配内容 (...)(组合),匹配括号内的任意正则表达式,并标识出组合的开始和结尾。匹配完成后,组合的内容可以被获取,并可以在之后用 \number 转义序列进行再次匹配 
In [51]: s
Out[51]: '联想 lenovo 2020'
In [52]: re.search('(eno)', s)
Out[52]: <_sre.SRE_Match object; span=(4, 7), match='eno'>
稍后在后面分组时,进行详细说明。
| 正则特殊字符 | 匹配内容 | 
|---|---|
| . | 匹配除换行符( \n)以外的单个任意字符 | 
| ^ | 匹配整个字符串的开头 | 
| `<article class="_2rhmJa" style="box-sizing: border-box; display: block; font-weight: normal; line-height: 1.8; margin-bottom: 20px; color: rgb(64, 64, 64); font-family: -apple-system, BlinkMacSystemFont, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;" | 匹配整个字符串的结尾 | 
| [] | 匹配中括号内字符中的任何一个 比如: [1a]匹配到1或者a | 
| [-] | 匹配连续的字符范围 比如 [0-9]表示0到9之间的任意一个数字 | 
| \w | 匹配单个字母、数字、汉字或下划线 | 
| \d | 匹配单个数字 | 
| \s | 匹配单个任意的空白符 | 
| \b | 匹配单词的开始或结束 | 
[0-9a-zA-z]表示0 1 2 3 4 5 6 7 8 9其中任何一个数字,或者 26 个英文字符大小写的任何一个。
\w,\s,\d可以包含在中括号中。 例如,[\s,.]是一个匹配任何空格字符的字符类或者',',或'.'
重复次数匹配:
| 正则特殊字符 | 匹配内容 | 示例 | 
|---|---|---|
| * | 对它前面的正则模式匹配0到任意次重复, 尽量多的匹配字符串。 | ab*会匹配'a','ab', 或者'a'后面跟随任意个'b'。 | 
| + | 对它前面的正则式匹配1到任意次重复。 | lv6+匹配lv后面跟随1个以上到任意个 '6',它不会匹配 'lv'。 | 
| ? | 对它前面的一个正则模式匹配0到1次重复。 | lv6?匹配lv和lv6 | 
| {m} | 重复它前一个正则模式 m 次 | a{2}匹配aa | 
| {m,} | 重复它前一个正则模式 m 次或 m 次以上 | a{2,}匹配aa或连续两个a以上 | 
| {m, n} | 重复前一个正则模式 m 到 n 次之间的任意一个都可以 | a{2,5}匹配连续2 个到 5 个之间的a。如:'aa','aaa','aaaa','aaaaa' | 
四、关于原始字符串
正则表达式使用反斜杠('\')来表示特殊形式,或者把特殊字符转义成普通字符。 而反斜杠在普通的 Python 字符串里也有相同的作用,所以就产生了冲突。比如说,要匹配一个字面上的反斜杠,正则表达式模式不得不写成 '\\\\',因为正则表达式里匹配一个反斜杠必须是 '\\' ,而每个反斜杠在普通的 Python 字符串里都要写成 '\\' 。
解决办法是对于正则表达式样式使用 Python 的原始字符串表示法;在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 因此 r"\n" 表示包含 '\' 和 'n' 两个字符的字符串,而 "\n" 则表示只包含一个换行符的字符串。 样式在 Python 代码中通常都会使用这种原始字符串表示法来表示。
五、python使用正则
1. 常用方法
match
只在整个字符串的起始位置进行匹配
示例字符串
string = "isinstance yangge enumerate www.qfedu.com 1997"
示例演示:
import re
In [4]: r = re.match("is\w+", string)
In [8]: r.group()  # 获取匹配成功的结果
Out[8]: 'isinstance'
search
从整个字符串的开头找到最后,当第一个匹配成功后,就不再继续匹配。
In [9]: r = re.search("a\w+", string)
In [10]: r.group()
Out[10]: 'ance'    
findall
搜索整个字符串,找到所有匹配成功的字符串,比把这些字符串放在一个列表中返回。
In [16]: r = re.findall("a\w+", string)
In [17]: r
Out[17]: ['ance', 'angge', 'ate']
sub
把匹配成功的字符串,进行替换。
# 语法:
"""
("a\w+",    "100",        string,     2)
匹配规则,替换成的新内容,  被搜索的对象, 有相同的话替换的次数
"""
In [24]: r = re.sub("a\w+", "100", string, 2)
In [25]: r
Out[25]: 'isinst100 y100 enumerate www.qfedu.com 1997'
# 模式不匹配时,返回原来的值
split
以匹配到的字符进行分割,返回分割后的列表
In [26]: string
Out[26]: 'isinstance yangge enumerate www.qfedu.com  1997'
In [27]: r = re.split("a", string, 1)
使用多个界定符分割字符串
>>> line = 'asdf fjdk; afed, fjek,asdf,  foo'
>>> import re
>>> re.split(r'[;,\s]\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
2. 分组
正则分组
==从已经成功匹配的内容中,再去把想要的取出来==
# match
In [64]: string
Out[64]: 'isinstance yangge enumerate www.qfedu.com  1997'
In [65]: r = re.match("is(\w+)", string)
In [66]: r.group()
Out[66]: 'isinstance'
In [67]: r.groups()
Out[67]: ('instance',)
# search
# 命名分组
In [87]: r = re.search("is\w+\s(?P<name>y\w+e)", string)  
In [88]: r.group()
Out[88]: 'isinstance yangge'
In [89]: r.groups()
Out[89]: ('yangge',)
In [90]: r.groupdict()
Out[90]: {'name': 'yangge'}
# findall
In [98]: string
Out[98]: 'isinstance all yangge any enumerate www.qfedu.com  1997'
In [99]: r = re.findall("a(?P<name>\w+)", string)
In [100]: r
Out[100]: ['nce', 'll', 'ngge', 'ny', 'te']
In [101]: r = re.findall("a(\w+)", string)
In [102]: r
Out[102]: ['nce', 'll', 'ngge', 'ny', 'te']
# split
In [113]: string
Out[113]: 'isinstance all yangge any enumerate www.qfedu.com 1997'
In [114]: r = re.split("(any)", string)
In [115]: r
Out[115]: ['isinstance all yangge ', 'any', ' enumerate www.qfedu.com 1997']
In [116]: r = re.split("(a(ny))", string)
In [117]: r
Out[117]:
['isinstance all yangge ',
 'any',
 'ny',
 ' enumerate www.qfedu.com 1997']
In [118]: tag = 'value="1997/07/01"'
In [119]: s = re.sub(r'(value="\d{4})/(\d{2})/(\d{2})"', r'\1年\2月\3日"', tag)
In  [120]: s
Out [120]: value="1997年07月01日"   
4. 常用正则分享(邮箱号、手机号、IP)
4.1 邮箱账号
shark123@qq.com
shark123@163.com
r'\w+@(163|126|qq|139).(com|cn)'
或者
r'[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+'
4.2 手机号
r'1( 5[02689]|3\d|7[279]|8\d)\d{8}'
4.3 IP 地址
A类IP地址的范围为 1.0.0.1 ----- 126.255.255.254
B类IP地址的范围为128.1.0.1 ---- 191.255.255.254
C类IP地址的范围为 192.0.1.1 ---- 223.255.255.254
D类 IP地址的范围为 224.0.0.1 ---- 239.255.255.254127.x.x.x 是 本地回环接口地址
分解动作
(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.  # 匹配 IP 地址的前 8 个字节
(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\.){2} # 匹配 IP 地址的 中间 16 个字节
   (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]\d{0,1})  # 匹配 IP 地址的后 8 个字节
(?:…)
正则括号的非捕获版本。 匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用。In [213]: re.findall(r'(2[0-3]\d|1\d\d|[1-9]\d{0,1})\.', '239.') Out[213]: ['239'] In [214]: re.findall(r'(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.', '239.') Out[214]: ['239.']
合并
pattern = r'\b(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\.){2}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\b'
由于
\b是匹配
比如:In [283]: re.findall(r'\bfoo\b', 'foo.foo') Out[283]: ['foo', 'foo'] In [284]: re.findall(r'foo(?![.])', 'foo.foo') Out[284]: ['foo']
也可以是这样
r'(?<![.\d])(?:2[0-3]\d|1\d\d|[1-9]\d{0,1})\.(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})\.){2}?(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d{0,1})(?![.\d])'
(?<!…):
匹配当前位置之前不是...的样式。这个叫 negative lookbehind assertion (后视断定取非)。
(?!…)
匹配…不符合的情况。这个叫 negative lookahead assertion (前视取反)。比如说,Isaac (?!Asimov)只有后面 不 是 'Asimov' 的时候才匹配'Isaac '。
示例:
s = 'lenovo 192.137.1.336 shark 192.168.1.130 255.255.255.255 qf 192.168.1.138 lenovo'
贪婪 和 非贪婪
# 匹配所有包含小数在内的数字
print(re.findall('\d+\.?\d*',"asdfasdf123as1.13dfa12adsf1asdf3")) #['123', '1.13', '12', '1', '3']
#.*默认为贪婪匹配
print(re.findall('a.*b','a1b22222222b')) #['a1b22222222b']
#.*?为非贪婪匹配:推荐使用
print(re.findall('a.*?b','a1b22222222b')) #['a1b']