Python 正则匹配:re库的使用

引入

正则表达式是一种描绘字符串的匹配模式,可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

比如我们可以使用以下正则表达式来匹配一个网址:

1
[a-zA-Z]+://[^\s]*

其中a-z表示匹配任意的小写字母,A-Z表示匹配任意的大写字母,^\s表示匹配任意的非空白字符,*表示匹配前面的任意多个字符。

本文将介绍正则表达式的基本概念,以及如何使用 Python 的re库进行正则匹配。


常用匹配规则

除了我们上一节说到的几个匹配规则之外,正则表达式还提供了很多的匹配规则,下表列出了常用的一些匹配规则:

模式描述
\w匹配字母、数字及下划线
\W匹配不是宇母、数字及下划线的字符
\s匹配任意空白字符,等价于[\t\n\r\f]
\S匹配任意非空字符
\d匹配任意数字,等价于[0-9]
\D匹配任意非数字的字符
\A匹配字符串开头
\Z匹配字符串结尾。加果存在换行,只匹配到换行前的结束字符串
\z匹配字符串结尾。如果存在换行,同时还会匹配换行符
\G匹配最后匹配完成的位置
\n匹配一个换行符
\t匹配一个制表符
^匹配一行字符串的开头
$匹配一行字符串的结尾
.匹配任意字符,除了换行符,当re.DOTALL标记被指定时,可以匹配包括换行符的任意字符
[...]用来表示一组字符,单独列出
[^...]匹配不在[]中的字符
*匹配0个或多个表达式
+匹配1个或多个表达式
?匹配0个或1个前面的正则表达式定义的片段,非贪婪方式
{n}精确匹配 n 个前面的表达式
{n, m}匹配 nm 次由前面正则表达式定义的片段,贪婪方式
a|b匹配 ab
()匹配括号内的表达式,也表示一个组

re库

re库是 Python 自带的标准库,无需额外安装,使用前需要导入:

1
import re

match

我们开始介绍re中的第一个常用的匹配方法——match,向它传入要匹配的字符串以及正则表达式,可以检测这个正则表达式是否和字符串相匹配。

match方法会尝试从字符串的起始位置开始匹配正则表达式,如果匹配,就会返回匹配成功的结果,否则返回None

语法格式:

1
re.match(pattern, string, flags=0)

参数说明:

参数描述
pattern正则表达式
string要匹配的字符串
flags标志位,用于控制正则表达式的匹配方式

代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re

content = "Hello 666 World"
print(len(content))
result = re.match("^Hello\s\d\d\d\sWorld$", content)
print(result)
print(result.group())
print(result.span())

# 输出:
# 15
# <re.Match object; span=(0, 15), match='Hello 666 World'>
# Hello 666 World
# (0, 15)

匹配目标

其中,group方法可以输出匹配到的内容,span方法可以输出匹配的范围。

同时,我们可以通过给group传入索引来获取指定分组的结果,如:

1
2
3
4
5
6
7
8
9
10
11
12
import re

content = "Hello 369 World"
result = re.match("^Hello\s(\d\d)(\d)\sWorld$", content)
print(result.group())
print(result.group(1))
print(result.group(2))

# 输出:
# Hello 369 World
# 36
# 9

这里我们使用了括号()将想要提取的子字符串标记出来,group中传入的索引便是第几个括号()的位置,用例中的group(1)便匹配的是36,而group(2)匹配的是9

通用匹配

上面的我们写的正则表达式比较复杂,出现空白字符就用\s匹配,出现数字就用\d匹配,这样的写法十分呆板。

其实我们完全没有必要这么复杂,正则表达式为我们提供了万能匹配方式,我们可以使用.*来匹配除换行符以外的任意字符,有了它我们就不用挨个字符进行匹配了。

之前的例子可以改写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re

content = "Hello 666 World"
print(len(content))
result = re.match("^Hello.*World$", content)
print(result)
print(result.group())
print(result.span())

# 输出:
# 15
# <re.Match object; span=(0, 15), match='Hello 666 World'>
# Hello 666 World
# (0, 15)

贪婪方式与非贪婪方式

使用通用匹配.*时,匹配到的内容可能不是我们想要的。如:

1
2
3
4
5
6
7
8
9
10
import re

content = "Hello 123456789 World"
result = re.match("^He.*(\d+).*World$", content)
print(result.group())
print(result.group(1))

# 输出:
# Hello 123456789 World
# 9

这里我们想获取目标字符串中间的数字,所以我们使用了(\d+)匹配中间,用.*来匹配两边杂乱的字符,但最终group(1)中输出的是9这一个数字,与我们想要的结果不同。

这里涉及到贪婪方式和非贪婪方式的问题。

在贪婪方式中,.*会匹配尽可能多的字符,而.*后面是\d+,也就是至少匹配一个数字,所以前面的.*会尽可能的匹配更多的字符,最后就只给\d+留下了9这一个字符。

这样会给我们带来一定的不便,我们可以用非贪婪方式来改写,使其能够获取到我们想要的结果:

1
2
3
4
5
6
7
8
9
10
import re

content = "Hello 123456789 World"
result = re.match("^He.*?(\d+).*World$", content)
print(result.group())
print(result.group(1))

# 输出:
# Hello 123456789 World
# 123456789

.*的后面加上?可以用非贪婪模式,这样它就会匹配尽可能少的字符。

修饰符

我们之前提到了match的第三个参数flags,这一节我们来讨论一下这个参数的可选项。

下表包含了可选的修饰符:

修饰符描述
re.I使匹配对大小写不敏感
re.L实现本地化识别(locale-aware)匹配
re.M多行匹配,影响^$
re.S使匹配内容包括换行符在内的所有字符
re.U根据 Unicode 字符集解析字符。影响\w\W\b\B
re.X忽略正则表达式中的空白和注释

在网页匹配中较为常用的有re.Sre.I

转义匹配

正则表达式中定义了很多的匹配规则,如用.匹配除换行符以外的任意字符,如果我们就想让.来匹配.这个字符怎么办呢?

我们可以在用作正则表达式匹配的字符前面加上\来转义,使之匹配它原本表示的字符。


上一节我们提到,match是从字符串起始位置开始匹配的,这意味着一旦字符串开头不匹配,我们就无法匹配到任何子字符串,如:

1
2
3
4
5
6
7
8
import re

content = "XiaoAoQuan Hello 123456789 World"
result = re.match("He.*?(\d+).*ld", content)
print(result)

# 输出:
# None

使用match方法进行匹配时需要考虑字符串的开头内容,使用并不便利。它更适合判断目标字符串是否符合正则表达式规则

这里就有另外一个方法——search,它会依次以每个字符开头扫描字符串,找到并返回第一个符合条件的结果,如果没找到则返回None

语法格式:

1
re.search(pattern, string, flags=0)

参数说明:

参数描述
pattern正则表达式
string要匹配的字符串
flags标志位,用于控制正则表达式的匹配方式

代码实例:

1
2
3
4
5
6
7
8
9
10
import re

content = "XiaoAoQuan Hello 123456789 World XiaoAoQuan Hello 123456789 "
result = re.search("He.*?(\d+).*World", content)
print(result)
print(result.group(1))

# 输出:
# <re.Match object; span=(11, 32), match='Hello 123456789 World'>
# 123456789

findall

findall方法和search方法类似,但它会返回所有与正则表达式匹配的子字符串。

语法格式:

1
re.findall(pattern, string, flags=0)

参数说明:

参数描述
pattern正则表达式
string要匹配的字符串
flags标志位,用于控制正则表达式的匹配方式

代码实例:

1
2
3
4
5
6
7
8
import re

content = "XiaoAoQuan Hello 123456789 World\nXiaoAoQuan Hello 987654321 World"
result = re.findall("X.*?(\d+).*d", content)
print(result)

# 输出:
# ['123456789', '987654321']

sub

sub方法类似于字符串中的replace方法,它可以将替换字符串中符合正则表达式匹配条件的子字符串全部替换为指定字符串。

语法格式:

1
re.sub(pattern, repl, string, count=0, flags=0)

参数说明:

参数描述
pattern正则表达式
repl替换的目标字符串
string原字符串
count模式匹配后替换的最大次数,默认 0 表示替换所有的匹配

代码实例:

1
2
3
4
5
6
7
8
9
import re

content = "XiaoAoQuan Hello 123456789 World\nXiaoAoQuan Hello 987654321 Python"
result = re.sub("\d+", "", content)
print(result)

# 输出:
# XiaoAoQuan Hello World
# XiaoAoQuan Hello Python

compile

compile方法可以将正则表达式字符串编译为正则表达式对象,便于在后续的匹配中反复使用。

语法格式:

1
re.compile(pattern[, flags])

参数说明:

参数描述
pattern正则表达式
flags标志位,用于控制正则表达式的匹配方式

代码实例:

1
2
3
4
5
6
7
8
9
import re

content = "XiaoAoQuan Hello 123456789 World\nXiaoAoQuan Hello 987654321 World"
pattern = re.compile('X.*?(\d+).*d')
result = pattern.findall(content)
print(result)

# 输出:
# ['123456789', '987654321']

总结

学会了正则表达式,我们就可以定向查找网页中的某些内容了,快去结合爬虫练练手吧。