https://tool.oschina.net/regex/ 正则表达式在线测试
官方文档:https://docs.python.org/zh-cn/3/library/re.html
1. match()方法
该方法主要传入两个参数,第一个参数是正则表达式,第二个参数是要匹配的字符串。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())
运行结果:
<re.Match object; span=(0, 19), match='Hello 1234567 World'>
Hello 1234567 World
1234567
(0, 19)
在打印结果中,可以看到的结果是Match对象,这证明匹配成功,该对象有两个方法:group()方法可以输出匹配到的内容,结果是Hello 1234567 World。正好是匹配到的内容;span()方法输出匹配到的范围,结果是(0, 19),这就是匹配到的字符串在原来字符串中的位置范围。
这里想把字符串的1234567提取出来,此时可将数字部分的正则表达式用()括起来,然后调用group(1)获取匹配结果。
Group()输出完整的匹配结果。
Group(1)会输出第一个被()包围起来的匹配结果,加入正则表达式后面还有()括起来的内容,那依次使用group(2),group(3)来获取。
- 贪婪与非贪婪匹配
使用.*可以匹配任意字符。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))
运行结果:
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
7
这里只匹配到一个7这个数字,为什么?
贪婪匹配与非贪婪匹配。在贪婪匹配下,.*会匹配尽可能多的字符。正则表达式中的.*后面是\d+,也就是至少一个数字,这里没有指定数字的个数,因此,前面的.*就尽可能匹配多的字符,这里就把123456匹配了,给\d+留下了一个可满足条件的数字7。
这里要使用非贪婪匹配,非贪婪匹配的写法:.*?多个一个问号。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))
运行结果:
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
1234567
此时成功获取到了1234567了。非贪婪匹配就是尽可能少的匹配字符。当.*?匹配到Hello 后面的空向字符时,再往后的字符就是数字了,而\d+恰好可以匹配,那么这里.的就不再进行匹配,交给\d+去匹配后面的数字。
注意:如果要匹配的字符串在结尾,.* ?就有可能什么都匹配不到,因为它匹配尽可能少的字符。
- 修饰符
正则表达式可以包含一些可选标志修饰符来控制匹配模式。修饰符被指定为一个可选的标志。
import re
content = '''Hello 1234567 World_This
is a Regex Demo'''
result = re.match('^He.*?(\d+).*Demo$', content)
print(result.group(1))
运行结果:
Traceback (most recent call last):
File "E:/eclipse-workspace/xiaomi/111/re_test/re_test.py", line 30, in <module>
print(result.group(1))
AttributeError: 'NoneType' object has no attribute 'group'
运行报错,也就是说正则表达式没有匹配到这个字符串,返回结果为None,而后面调用group(1)方法导致AttributeError
为什么加了一个换行符,就匹配不到呢?这是因为 . 匹配的是除了换行符之外的所有字符,当遇到了换行符,.*?就不能匹配了。这里需要加上一个修饰符re.S,即可修正这个错误:
content = '''Hello 1234567 World_This
is a Regex Demo'''
result = re.match('^He.*?(\d+).*Demo$', content, re.S)
运行结果:
1234567
这个修饰符re.S的作用是使 . 匹配包括换行符在内的所有字符。
这个re.S 在网页匹配中经常用到。因为HTML 节点经常会有换行,加上它,就可以匹配节点与节点之间的换行了。
还有一些修饰符re.I,意思是对大小写不敏感。这个在网页中也比较常用。
- 转义
使用\进行转义
import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result)
结果如下:
<re.Match object; span=(0, 17), match='(百度)www.baidu.com'>
当遇到用于正则匹配模式的特殊字符时,在前面加反斜杠转义一下。如使用.来匹配小数点;用()来匹配()
2.search()
前面是match()方法是从字符串开头开始匹配的,一旦开头不匹配,那么整个匹配都会失败。
import re
content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
result = re.match('Hello.*?(\d+).*?Demo',content)
print(result)
运行结果:
None
使用match()方法失败。
Search()方法它在匹配时会扫描整个字符串,然后返回第一个匹配成功的结果。将上面的代码中match()改为search(),运行结果:
<re.Match object; span=(13, 53), match='Hello 1234567 World_This is a Regex Demo'>
下面用几个实例看看search()的用法
首先,有段带匹配的html文本,我们写几个正则表达式实例来实现相应信息的提取
'''<div id="songs-list" >
<h2 class=”title ”>经典老歌</h2>
<p class=” introduction ”>
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href ="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href ="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view ="6"><a href="/4.mp3" singer="beyond">光辉岁月</a><lli>
<li data-view="5">< a href="/5.mp3" singer="陈慧琳">记事本</a><lli>
<li data-view="5">
<a href ="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>'''
可以观察到, ul 节点里有许多li 节点,其中li 节点中有的包含a节点,有的不包含a节点, a节点还有一些相应的属性一一超链接和歌手名。
尝试提取class 为active 的li 节点内部的超链接包含的歌手名和歌名,此时需要提取第三个li 节点下a 节点的singer 属性和文本。
此时的正则表达式可以是以li开头,然后寻找标志符active,中间的部分可以用 .?来匹配,要提取singer这个属性值,所以还要写singer=”(.?)”,这里使用小括号,以便后面使用group()方法提取出来。还要匹配a文本的内容,它的左边界是>,右边界是,依然使用(.*?)来匹配,所以最后的正则表达式为:
‘<li.*?active.*?singer=”(.*?)”>(.*?)</a>’
然后调用search()方法,它会搜索整个HTML文本,找到符合正则表达式的第一个内容返回。
另外,由于HTML文本中的代码有换行,所以要传入第三个参数re.S
import re
html = '''<div id="songs-list" >
<h2 class=”title ”>经典老歌</h2>
<p class=” introduction ”>
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href ="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href ="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view ="6"><a href="/4.mp3" singer="beyond">光辉岁月</a><lli>
<li data-view="5">< a href="/5.mp3" singer="陈慧琳">记事本</a><lli>
<li data-view="5">
<a href ="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>'''
result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S)
if result:
print(result.group(1),result.group(2))
歌手和歌名都是用小括号,使用group(1)和group(2)进行输出。
齐秦 往事随风
如果正则表达式中不加active,结果会如何呢?
result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html, re.S)
If result:
print(result.group(1),result.group(2))
结果是:
任贤齐 沧海一声笑
如果去掉re.S呢?
result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html)
if result:
print(result.group(1),result.group(2))
结果是:
result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html)
if result:
print(result.group(1),result.group(2))
3.findall()
如果想要获取匹配正则表达式的所有内容,就需要借助findall()方法了,该方法会搜索整个字符串,然后返回匹配正则表达式的所有内容。
还是上面的HTML文本,如果想获取a节点的超链接、歌手和歌名,就将search()换为findall()方法,如果有返回的话,就是列表类型,所以要遍历一下一次获取每组的内容。
results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)
print(results)
print(type(results))
for result in results:
print(result)
print(result[0], result[1], result[2])
运行结果:
[('/2.mp3', '任贤齐', '沧海一声笑'), ('/3.mp3', '齐秦', '往事随风'), ('/4.mp3', 'beyond', '光辉岁月'), ('/5.mp3', '陈慧琳', '记事本'), ('/6.mp3', '邓丽君', '但愿人长久')]
<class 'list'>
('/2.mp3', '任贤齐', '沧海一声笑')
/2.mp3 任贤齐 沧海一声笑
('/3.mp3', '齐秦', '往事随风')
/3.mp3 齐秦 往事随风
('/4.mp3', 'beyond', '光辉岁月')
/4.mp3 beyond 光辉岁月
('/5.mp3', '陈慧琳', '记事本')
/5.mp3 陈慧琳 记事本
('/6.mp3', '邓丽君', '但愿人长久')
/6.mp3 邓丽君 但愿人长久
4.sub()
除了使用正则表达式提取信息外,有时候还需要它来就该文本。如:想要把一串文本中的所有数字都去掉,如果只用字符串的replace()方法,那就太繁琐了,使用sub方法。
import re
content = '43gfn7gfs8hclf54njk392sdf'
content = re.sub('\d+', '',content)
print(content)
运行结果:
Gfngfshclfnjksdf
该方法实际上是做了替换,将匹配到的数字替换为空字符,就等于去掉了数字。
在上面html的示例中,如果只想取出歌名,就可以使用sub()方法将a节点去掉,只留下文本,在使用findall()提取就好了
html = re.sub('<a.*?>|</a>', '', html)
print(html)
resluts = re.findall('<li.*?>(.*?)</li>', html, re.S)
for reslut in resluts:
print(reslut.strip())
运行结果:
<div id="songs-list" >
<h2 class=”title ”>经典老歌</h2>
<p class=” introduction ”>
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li>
<li data-view="7">
沧海一声笑
</li>
<li data-view="4" class="active">
往事随风
</li>
<li data-view ="6">光辉岁月</li>
<li data-view="5">记事本</li>
<li data-view="5">
但愿人长久
</li>
</ul>
</div>
一路上有你
沧海一声笑
往事随风
光辉岁月
记事本
但愿人长久
html = re.sub(’<a.*?>|’, ‘’, html) 这里的 | 表示并列匹配。
<a href="/2.mp3" singer=“任贤齐”>沧海一声笑</a> 将匹配到的替换为空,只剩下了歌名。
5.Compile()
这个方法可以将正则字符串编译成正则表达式对象,以便在后期的匹配中使用。
import re
content1 = '2021-3-30 12:00'
content2 = '2021-3-28 12:55'
content3 = '2021-3-26 13:15'
pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern, '', content1)
result2 = re.sub(pattern, '', content2)
result3 = re.sub(pattern, '', content3)
print(result1,result2,result3)
运行结果:
2021-3-30 2021-3-28 2021-3-26
Compile()还可以传入修饰符,例如:re.S等修饰符,这样在search()、findall()等方法就不需要再次传入了。