正则是什么
实现字符串的检索、替换、匹配验证
实际操作
import re
content = 'Hello 123 4567 World_This is a Regex Demo'
print(len(content))
result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content)
print(result)
'^Hello\s\d\d\d\s\d{4}\s\w{10}'
- ^ 表示正则的开始
- \s 表示匹配空白字符创
- \d 匹配数字
- \d{4}表示同样的规则匹配四次
- \w{10}表示匹配十个字母及下划线
常用的匹配规则
模式 | 描述 |
---|---|
\w | 匹配字母、数字及下划线 |
\W | 匹配不是字母、数字及下划线 |
\s | 匹配任意空白字符,等价于 [\t\n\r\f] |
\S | 匹配任意非空字符 |
\d | 匹配任意数字。等价于[0-9] |
\D | 匹配任意非数字的字符 |
\A | 匹配字符串开头 |
\Z | 匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串 |
\z | 匹配字符串结尾,如果存在换行,同时还会匹配换行符 |
\G | 匹配最后匹配完成的位置 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
^ | 匹配一行字符串的开头 |
$ | 匹配一行字符串的结尾 |
. | 匹配任意字符,除了换行符,当 re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符 |
[…] | 用来表示一组字符,单独列出,比如 [amk] |
[^…] | 不在 [] 中的字符,比如 匹配除了 a、b、c 之外的字符 |
* | 匹配 0 个或多个表达式 |
+ | 匹配 1 个或多个表达式 |
? | 匹配 0 个或 1 个前面的正则表达式定义的片段,非贪婪方式 |
{n} | 精确匹配 n 个前面的表达式 |
{n, m} | 匹配 n 到 m 次由前面正则表达式定义的片段,贪婪方式 |
a | b |
() | 匹配括号内的表达式,也表示一个组 |
re.match
向它传入要匹配的字符串,以及正则表达式,就可以检测这个正则表达式是否匹配字符串。
match 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回 None。
匹配目标&group分组
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())
运行结果:
<_sre.SRE_Match object; span=(0, 19), match='Hello 1234567 World'>
Hello 1234567 World
1234567
(0, 19)
成功得到了 1234567。这里用的是 group(1),它与 group() 有所不同,后者会输出完整的匹配结果,而前者会输出第一个被 () 包围的匹配结果。假如正则表达式后面还有 () 包括的内容,那么可以依次用 group(2)、group(3) 等来获取。
通用匹配
*.可以匹配任意字符(除换行符外)
import re
content = 'Hello 123 4567 World_This is a Regex Demo'
result = re.match('^Hello.*Demo$', content)
print(result)
print(result.group())
print(result.span())
运行结果如下:
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
Hello 1234567 World_This is a Regex Demo
(0, 40)
看到,group 方法输出了匹配的全部字符串,也就是说我们写的正则表达式匹配到了目标字符串的全部内容;span 方法输出 (0, 41),这是整个字符串的长度。
贪婪与非贪婪
使用上面的通用匹配 .* 时,有时候匹配到的并不是我们想要的结果。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))
这里我们想获取字符创中间的数字,所以中间依然写的是 (\d+)。由于数字两侧的内容比较杂乱,所以略写成 .。最后,组成 ^He.(\d+).*Demo$,看样子并没有什么问题。
运行结果:
<re.Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
7
我们取group(1)按照我的理解来说,应该取到的是“1234567”但是这里只取到了一个数字7
。这里就涉及一个贪婪匹配与非贪婪匹配的问题了。在贪婪匹配下,. 会匹配尽可能多的字符。正则表达式中 . 后面是 \d+,也就是至少一个数字,并没有指定具体多少个数字,因此,.* 就尽可能匹配多的字符,这里就把 123456 匹配了,给 \d+ 留下一个可满足条件的数字 7,最后得到的内容就只有数字 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
贪婪匹配是尽可能匹配多的字符,非贪婪匹配就是尽可能匹配少的字符。当 .? 匹配到 Hello 后面的空白字符时,再往后的字符就是数字了,而 \d+ 恰好可以匹配,那么 .? 就不再进行匹配,交给 \d+ 去匹配后面的数字。这样 .*? 匹配了尽可能少的字符,\d+ 的结果就是 1234567 了。
所以,在做匹配的时候,字符串中间尽量使用非贪婪匹配,也就是用 .*? 来代替 .,以免出现匹配结果缺失的情况。
但需要注意的是,如果匹配的结果在字符串结尾,.*? 就有可能匹配不到任何内容了,因为它会匹配尽可能少的字符。例如:
import re
content = 'http://weibo.com/comment/kEraCN'
result1 = re.match('http.*?comment/(.*?)', content)
result2 = re.match('http.*?comment/(.*)', content)
print('result1', result1.group(1))
print('result2', result2.group(1))
运行结果:
result1
result2 kEraCN
可以观察到,.? 没有匹配到任何结果,而 . 则尽量匹配多的内容,成功得到了匹配结果。
修饰符
正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。
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 "/Users/cuinn/PycharmProjects/spider/lenanRe/re_05_修饰符.py", line 7, in <module>
print(result.group(1))
AttributeError: 'NoneType' object has no attribute 'group'
运行报错,说明没有匹配到字符,为什么就加了一个换行符就匹配不到了?
这是因为我们匹配的是除换行符之外的任意字符,当遇到换行符时,.*? 就不能匹配了,导致匹配失败。
加上一个 ‘re.S’,就可以改正了
import re
content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo$', content,re.S) # 加上re.S
print(result.group(1))
运行结果:
1234567
这个 re.S 在网页匹配中经常用到。因为 HTML 节点经常会有换行,加上它,就可以匹配节点与节点之间的换行了。
附上常见的修饰符:
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响 ^ 和 $ |
re.S | 使匹配包括换行在内的所有字符 |
re.U | 根据 Unicode 字符集解析字符。这个标志影响 \w、\W、\b 和 \B |
re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解 |
较为常用的修饰符有 re.S 和 re.I。
转义匹配
匹配除换行符以外的任意字符,目标字符串里面就包含 .,就使用转义匹配
import re
content = '(百度) www.baidu.com'
result = re.match('\(百度\) www\.baidu\.com', content)
print(result)
print(result.group())
运行结果:
<re.Match object; span=(0, 18), match='(百度) www.baidu.com'>
(百度) www.baidu.com
当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。例 . 就可以用 . 来匹配。
search
match 方法是从字符串的开头开始匹配的,一旦开头不匹配,就好匹配失败。
seach 匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。
实践演练
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></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<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))
这里就不写结果了,可以自己在电脑上面写着,尝试匹配所有歌手玩玩~
re库的其他方法
到了这里我们已经用了match Search,再说一下其他常用的方法:
- findall:该方法会搜索整个字符串,然后返回匹配正则表达式的所有内容。
- sub:借助它来修改文本。比如,想要把一串文本中的所有数字都去掉,如果只用字符串的 replace 方法,那就太烦琐了,这时可以借助 sub 方法。
- compile:可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。