正则表达式

正则表达式
Kaede简介
正则虽小, 但是不好学, 有一定的难度的. 正则表达式不只限于 Python, 而是独立于编程语言, 用于处理复杂文本的强大的高级文本操作工具. 正则表达式来源于 Perl 语言, 其他的编程语言也是支持了 Perl 的这个正则语言. 基本上, 语法相似度高达 90%.
正则对于字符串的操作, 无非就是分割, 匹配, 查找和替换. 也就是四个字: 模糊查询
比如说, 我现在有这样的一个字符串
1 | s = "yyt always loves lc but lc loves yyt forever" |
我们可以使用字符串的一个方法 .find() 来查找是否出现, 并且获取出现的位置.
1 | s = "yyt always loves lc but lc loves yyt forever" |
这叫做精准查询, 不是正则. 这个例子中看不太出来, 不过我们可以换一个字符串:
1 | s = "11 always loves 123 but 123 loves 11 forever and today is 2025.4.13" |
我想要找到所有的数字, 如果数据量很大, 我们就不能用人眼找了. 这里就需要使用正则表达式!
在 Python 中, 如果需要使用正则表达式, 需要引入一个标准库的模块 re. 我们需要匹配, 那么先输入匹配的规则, 再指定匹配的字符串, 即调用函数: re.findall(规则, 字符串).
这里什么意思暂时不用知道, 看看就 OK
1 | import re |
该段代码会输出一个列表: ['11', '123', '123', '11', '2025', '4', '13'], 这就是正则表达式的用法了: 模糊的, 快速的匹配一个字符串中的相应字符.
元字符
正则表达式其实就是两个知识点, 一个是元字符, 另一个是方法. 方法其实只有几个而已, 元字符反而是最重要的. 下面会记录最常用的 11 种元字符, 全部都需要掌握, 记住名字.
通配符
写出来其实就是一个句点号: ., 它可以匹配除了换行符 \n 以外的任何符号. 这里提供这样一个有很多单词组成的字符串, 大部分单词都是以 a 开头以 e 结尾:
1 | s = "age ape apple angle agree alone alive aside awake appl\ne" |
正则表达式的匹配, 我们可以进行精准匹配, 比如我就要寻找 apple:
1 | import re |
这样可以, 但是没有必要; 更多的, 我们还是要进行模糊的匹配. 比如说, 我改一下, 查询 a.e, 则代表我要查询:
- 第一个字符是 a
- 第二个字符无所谓
- 第三个字符是 e
的单词. 调用如下代码, 可以得到结果.
1 | import re |
[!note] 注意
这里的 ake 其实来自于 awake, 并不是错了, 而是匹配到了这一部分内容
另外, 假如有这样一个单词:a\ne, 也是不会被匹配的, 因为通配符无法匹配换行符
那么同理, 我想要匹配 a...e, 则会返回 ['apple', 'angle', 'agree', 'alone', 'alive', 'aside', 'awake'], 可以使用代码进行验证.
总而言之, 一个点 ., 表示的其实就是任何字符 (除了换行符).
字符集
顾名思义, 也可以匹配字符, 但是范围更小一些了. 字符集写作 [], 它会匹配一个中括号中的, 出现的任意字符符号, 字符之间不需要分隔符且数量不限. 不过这代表的是, 只要符合其中的一个就算.
例如这样一个字符串:
1 | s = "yys yyt ywt yot yyo yyk y1t" |
我只想要匹配 y 开头, t 结尾, 中间只能是 y 或者 1, 则可以使用字符集的形式进行匹配:
1 | import re |
[!info] 注意
匹配的规则是: 中间是 y 或者 1, 所以如果你匹配字符串yy1t, 并不符合规则.
如果你写的匹配表达式为y,1, 也是不对的, 这样如果有字符串y,t, 也会匹配成功
但如果我想要匹配很多东西, 比如 a~z 的所有小写字符呢? 可以使用字符集的一个 - 来表示一段范围. 比如我有如下的匹配, 寻找中间是小写字母的字符串:
1 | import re |
同理, 大写字符也是一样的, 直接使用 [A-Z] 即可. 这样相较于上面的通配符, 就进行了一个范围的缩小.
1 | import re |
数字也是一样的, 从小到大, 就是 [0-9], 这样就匹配了所有的数字.
1 | import re |
[!warning] 注意
这里由于我们使用了通配符., 所以空格也是会被识别的, 只要连续三个字符中间是数字, 就会被匹配返回, 所以出现了类似于A1这样的东西.
另外, 字符集中还有一种方式, 叫做取反. 比如我希望, 只要一个字符串中间的字符不是数字, 就成立, 则可以使用取反的写法. 取反写作 ^, 会让当前字符集的内容取反. 如下, 会取出所有的, 中间不是数组的连续三个字符, 包括换行符.
1 | import re |
综上, 如果我现在想要取出一段字符串中的所有非英文数字字符, 就可以直接使用取反+区间的方式了. 下面是一个案例:
1 | import re |
重复元字符
在之前的内容中, 匹配的都是一个字符, 就算是字符集, 也仅仅匹配的是一个字符. 但是我们可能会有如下需求: 匹配所有 a 开头的, e 结尾的单词. 这个情况下, 我们之前学的就无法胜任了; 这里就可以使用重复元字符来实现.
重复元字符一共有四个: {}, *, +, ?, 下面边举例边学习.
{} 数量范围贪婪符
现在有如下字符串, 我想要筛选里面的指定的 a 开头, e 结尾的单词:
1 | s = "lc loves apple and ahe and age and aaa and aee" |
首先, 我可以要求, 只寻找中间有 1 个任意字符的这样的单词. 那么花括号直接写一个 1 即可:
1 | import re |
[!note] 等效
这其实就等效于一个.不过一般我们不会这么用的.
这里可以限制一下, 我想要中间的字符数量在 13 个, 则直接写 3 个任意字符. 如下代码, 可以筛选出来我们需要的内容:.{1,3} 即可, 这代表可以有 1
1 | import re |
同理, 上面的是一个闭区间, 那么如果我空了一半呢? 自然就是一个开区间了. 也就代表可以有无数个任意字符, 只要两边是 a 和 e 即可. 如下代码, 实现了这样的匹配效果.
1 | import re |
[!error] 注意
默认来说, 贪婪是往大了走的, 所以对于上面这个字符串, 整体看的话, 刚好第一个字符是 y, 最后一个字符是 t, 所以直接返回了整个字符串. 这并不是 Bug, 而是默认的贪婪匹配规则.
显然, 我们不想要这种结果. 这里我们需要改变默认的贪婪匹配.
- 默认贪婪匹配: 按照最大匹配数进行优先分配
- 取消贪婪匹配: 按照最小匹配数进行优先分配
这里, 我们需要在贪婪后面, 紧跟一个 ?, 进行反向即可.
1 | import re |
至此, 已经可以正常匹配了.
[!warning] 注意
如果里面有换行符\n, 则会进行自动截断. 因为.无法匹配换行符!
当然, 也是可以有办法匹配的, 只需要在函数后面再跟一个参数即可:re.findall(r"y.{1,}?t", s, re.S)
另外, 花括号前面也并不是只能是点, 也可以是其他的字符. 比如我想要找到一个字符串中的, 所有用 a 开头, b 在中间, c 来结尾的字符串, 则可以这么写:
1 | import re |
所以说, 这个花括号前面的东西不仅为普通的字符, 其他的任何字符都是可以进行匹配的.
最后, 可以综合前面的字符集一起运用. 比如, 我只想要中间的字符为小写字母, 不想有其他的特殊字符或者数字, 就可以这么写:
1 | import re |
给一个实际一些的案例, 比如我要从一句话中, 找到所有的 Java 版本号, 就可以这么写:
1 | import re |
* 左边原子出现 0 或多次
[!note] 注意
花括号才是最重要的, 这些特殊符号不过是简洁了一些而已.
这里的 * 表示左边的原子出现 0 次或者多次. 等同于 {0,}. 例如, 我现在要匹配 a 开头 e 结尾的所有单词, 就可以使用这个字符来简化代码了:
1 | import re |
这里使用 [a-z] 是为了屏蔽空格, 否则会输出不应该输出的内容.
[!note] 注意
相反的, 如果你没有加反贪婪符号?, 则会输出从第一个 e 开始, 到最后一个 e 字符. 不过其实这种用的并不多, 因为我们一般需要的是某一段内容.
应用案例
比如说, 有这样一个爬取到的字符串:
1 | <ul> |
我现在想要使用正则表达式, 获取所有的水果是什么. 这个时候, 就可以使用正则表达式快速的获取内容了! 示例代码如下:
1 | import re |
这样就可以筛选出来所有的 li 内容了, 这里其实就是用到了一个非常重要的组合: .*?
[!info] 扩展
这里的.其实也可以是其他的符号, 甚至是字符集, 比如[a-zA-Z], 这样匹配的就是只有英文字符的内容了; 或者是[0-9], 这样子匹配的就是全数字内容了.相比起来, 相当于对范围做了进一步的限定.
+ 左边元字符出现 1 或多次
这里的 + 表示左边的原子出现 1 次或者多次. 等同于 {1,}
[!note] 小想法
其实这个和上面的*几乎没有区别, 不过中间的东西至少出现一次而已; 不过呢, 我们如果要获取, 自然中间都是有点东西的, 自然也就不会出现空内容的情况了√
使用和上面的 * 几乎一摸一样, 这里不再进行演示.
? 左边元字符出现 1 或多次
这里的 ? 表示前面的原子出现 0 次或者 1 次. 等同于 {0,1}
[!warning] 注意
这里的问号并不仅仅代表刚才花括号里面的取反操作, 反而是代表了出现 0~1 次.
因为重复符号并不是元字符√
使用方式大差不差, 不过多进行介绍.
补充知识
虽然前面的重复元字符部分也使用到了这个符号, 但是作用是完全不一样的. 重复元字符中, 表示的是非 xxx 的关系, 这里则表示的是一行的开头位置.
比如说, 我有如下字符串, 我需要的是获取这个字符串中的所有数字:
1 | s = "Yyt is 18 years old, and born in 2006 10 16" |
我们可以思考, 是否需要取消贪婪. 如果取消贪婪, 相当于有一个数字就行了, 这显然不是我们想要的; 我们想要的是连续的数字, 所以这里反而不需要取消贪婪!
1 | import re |
边界符
^ 开始边界符
比如说, 我有这样的一个字符串:
1 | s = "/2025/03/29/CTFShow-web17-WP/1.jpg" |
假如我现在要进行路径的匹配, 只允许传入的路径为 /2025/03/29/ 开头, 则需要使用开始边界符. 如果不使用的话, 前面随便传递一个什么, 都是符合的, 就会造成问题.
1 | import re |
理论上, 我们只想要查询符合要求的路径字符串, 但是之前的写法仍然会保留非合法的内容. 这里就需要做一个限制, 告诉程序, 我们要找的是开头为 xxx 的字符串. 直接前面加上一个 ^ 即可. 如下代码, 改后可以匹配符合要求的路径:
1 | import re |
第一个匹配失败了, 所以为空列表, 成功过滤 url.
$ 结束边界符
还是上面的路径例子, 我现在就想找到所有的, 以 / 为开头, 以中间内容后的 / 为结尾的字符串. 就可以在后面加一个 $ 结束边界符了.
1 | import re |
转义符
正则的转义符其实就是反斜杠 \, 可以和后面的字符组成一种特殊的作用符号. 不过还请注意, 正则的转义符和普通语言中的转义符并不一样, 使用起来完全不一样. 绝对不要混淆!
| 元字符 | 描述 |
|---|---|
\d |
匹配一个数字原子, 等价于 [0-9] |
\D |
匹配一个非数字原子, 等价于 [^0-9] 或者 [^\d] |
\w |
匹配一个包括下划线的单词原子, 等价于 [A-Za-z0-9_] |
\W |
匹配一个非单词原子, 等价于 [^A-Za-z0-9_] 或 [^\w] |
\n |
匹配一个换行符 |
\t |
匹配一个制表符 |
\s |
匹配一个空白元字符, 包括但不限于空格, 制表符, 换页符 |
\S |
匹配一个非空白元字符. |
\b |
匹配一个单词边界原子, 也就是单词之间的空隙 |
\B |
匹配一个非单词边界原子, 等价于 [^\b] |
假如我要找到一个字符串中的所有数字, 之前我们可以这样写:
1 | import re |
可以是可以, 但是不够简单. 这里可以直接用一个转义符 \d 来等同于阿拉伯数字.
1 | import re |
如果想要找到一个字符串中的所有单词, 则可以直接使用正则: \w+, 只匹配单词元字符:
1 | import re |
不过呢, 这个单词字符并不包含空格, 以防止贪婪模式的困扰.
单词边界 \b 比较特殊, 单词的边界, 指的就是单词之间的空白. 比如我希望能够找到一个字符串中的所有的 cat 单词, 不希望匹配到含有这个单词的更长一些的单词. 这个时候, 我们就需要使用单词边界符了.
可能想的是, 在 cat 检查的时候, 后面加一个空格, 可是如果 cat 后面是 ! 等特殊符号, 也是会有问题的. 不如直接使用单词分界符:
1 | import re |
[!warning] 注意
剩下的转义符都大差不差了, 正常使用即可.
分组与优先提取
在正则表达式中, () 就是分组符号, 其实就是把某一些内容作为一个独立的整体进行处理. 另外, 小括号也有优先提取的功能, 会优先匹配括号中的内容
1 | import re |
注意, 这里涉及到了优先提取相关的东西. 不过, 小括号的作为整体已经体现出来了.
优先提取, 就是加上 ?:. 例如, 我要找到一个字符串中的所有邮箱:
1 | import re |
该段代码没有问题, 不过我可能想要强调某些部分, 比如我只想要获取邮箱的前面, 名字部分的内容, 我就可以给名字部分加上括号, 这样获取到的内容就是只有名字了:
1 | import re |
如果有多个括号, 则会同时进行提取, 比如我既获取前面的名字, 又要 xxx.com 的 xxx 部分:
1 | import re |
或者元字符
这里的或是逻辑的或, 没有和普通有太大的出入. 或者, 就是很多个选一个出来. 在之前的普通字符集部分, 假如我想要匹配多个字符, 可以直接写: abcde, 这里的几个字符本身就是或者的关系, 所以不需要写 |.
但是, 如果我现在有一个单词需要或操作, 则需要使用 | 表示或者.
1 | import re |
常用方法
我们可以使用自带的一些方法, 更方便的从字符串中找到我们需要的内容. 下面是一些常用的正则表达式方法, 根据需要选用即可:
| 函数 | 描述 |
|---|---|
findall |
按照指定的正则, 查找符合正则的所有匹配项, 以列表返回 |
search |
在字符串的 任何位置 寻找, 找到了则返回 re.Match 对象, 否则 None |
match |
在字符串的 开始位置 寻找, 找到了则返回 re.Match 对象, 否则 None |
split |
按照正则切分字符串, 返回一个分割后的列表 |
sub |
正常寻找, 但是可以替换找到的匹配项 |
complie |
编译正则表达式模式, 并且生成一个正则表达式对象, 可以重用这个对象 |
假如, 我想要找到一个日志中的第一个 ERROR:
1 | ERROR: Hello World |
则可以使用 search 方法. 用法完全一样, 直接记录即可.
[!note] 注意
返回的是一个
Match对象, 这个对象是有很多属性的, 不妨看看.
1 | import re |





