正则表达式先入门,精不精通看修行

1.元字符

元字符是构造正则表达式的一种基本元素,可以匹配一个或多个字符,或者根据特定的规则进行匹配。

元字符 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串结束
复制代码
匹配有abc开头的字符串:
\babc或者^abc

匹配8位数字的QQ号码:
^\d\d\d\d\d\d\d\d$

匹配1开头11位数字的手机号码:
^1\d\d\d\d\d\d\d\d\d\d$

2.重复限定符

正则表达式中一些重复限定符,把重复部分用合适的限定符替代。

语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
复制代码
匹配8位数字的QQ号码:
 ^\d{8}$

匹配1开头11位数字的手机号码:
^1\d{10}$

匹配银行卡号是14~18位的数字:
^\d{14,18}$

匹配以a开头的,0个或多个b结尾的字符串
^ab*$

3.分组

从上面的例子中看到,限定符是作用在与他左边最近的一个字符,那么问题来了,如果我想要 ab 同时被限定那怎么办呢?

正则表达式中用**小括号 ()**来做分组,也就是括号中的内容作为一个整体。

复制代码
匹配字符串中包含 0 到多个 ab 开头:

^(ab)*

4.转义

我们看到正则表达式用小括号来做分组,那么问题来了:

如果要匹配的字符串中本身就包含小括号,那是不是冲突?应该怎么办?

针对这种情况,正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简答,就是在要转义的字符前面加个斜杠,也就是\即可。

复制代码
例如,要匹配以 (ab) 开头:
 ^(\(ab\))*

5. 条件或

正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。

复制代码
比如联通有 130/131/132/155/156/185/186/145/176 等号段
假如让我们匹配一个联通的号码,就可以用或条件来处理这个问题

^(130|131|132|155|156|185|186|145|176)\d{8}$

6.区间

正则提供一个元字符中括号 \[\] 来表示区间条件。

限定 0 到 9 可以写成 0-9
限定 A-Z 写成 A-Z
限定某些数字 165

复制代码
^(130|131|132|155|156|185|186|145|176)\d{8}$
可以简化为
^((13[0-2])|(15[56])|(18[5-6])|145|176)\d{8}$

7.零宽断言

‌零宽断言‌是正则表达式中的一种方法,用于查找在某些内容之前或之后的东西,但不包括这些内容

本身。它们像\b, ^, $等锚点一样,用于指定一个位置,这个位置应该满足一定的条件(即断言),

因此它们也被称为零宽断言。

正则表达式的先行断言和后行断言一共有 4 种形式:

  • (?=pattern) 零宽正向先行断言
  • (?!pattern) 零宽负向先行断言
  • (?<=pattern) 零宽正向后行断言
  • (?<!pattern) 零宽负向后行断言

概念说明

  • 正向(Positive): 匹配括号中的表达式,即断言所作的条件判断是肯定的,即只有当条件成立时,匹配才成功。
  • 负向(Negative): 不匹配括号中的表达式,即断言所作的条件判断是否定的,即只有当条件不成立时,匹配才成功。
  • 先行(Lookahead): 表示断言发生在匹配位置之前。
  • 后行(Lookbehind): 表示断言发生在匹配位置之后。

假设我们要用爬虫抓取 csdn 里的文章阅读量。通过查看源代码可以看到文章阅读量这个内容是这样的结构。

复制代码
"<span class="read-count">阅读数:641</span>"

要取到阅读量,在正则表达式中就意味着要能匹配到'</span>'前面的数字内容,按照上所说的正

向先行断言可以匹配表达式前面的内容。

复制代码
import re

# 定义正则表达式和测试文本
reg = r".+(?=</span>)"
test = "<span class=\"read-count\">阅读数:641</span>"

print("文本:" + test)
print("正则表达式:" + reg)

# 编译正则表达式
pattern = re.compile(reg)

# 创建匹配器
mc = pattern.finditer(test)

# 遍历所有匹配项
for match in mc:
    print("匹配结果:")
    print(match.group())


运行结果:
文本:<span class="read-count">阅读数:641</span>
正则表达式:.+(?=</span>)
匹配结果:
<span class="read-count">阅读数:641

如果只要数字,那么匹配数字 \d, 那可以改成:

复制代码
import re

# 定义正则表达式和测试文本
reg = r"\d+(?=</span>)"
test = "<span class=\"read-count\">阅读数:641</span>"

print("文本:" + test)
print("正则表达式:" + reg)

# 编译正则表达式
pattern = re.compile(reg)

# 创建匹配器
mc = pattern.finditer(test)

# 遍历所有匹配项
for match in mc:
    print(match.group())

//匹配结果:
//641

先行是匹配前面的内容,那后行就是匹配后面的内容啦。可以用后行断言来处理:

复制代码
import re

# 定义正则表达式和测试文本
reg = r"(?<=<span class=\"read-count\">阅读数:)\d+"
test = "<span class=\"read-count\">阅读数:641</span>"

print("文本:" + test)
print("正则表达式:" + reg)

# 编译正则表达式
pattern = re.compile(reg)

# 创建匹配器
mc = pattern.finditer(test)

# 遍历所有匹配项
for match in mc:
    print(match.group())


输出结果:
文本:<span class="read-count">阅读数:641</span>
正则表达式:(?<=<span class="read-count">阅读数:)\d+
641

8.捕获与非捕获组

捕获通常和分组联系在一起,也就是"捕获组"。捕获组通过**圆括号()**来标识,能够在匹配时将括号

内的内容单独保存,并在后续处理中提取或引用。捕获组的出现是为了简化复杂模式的匹配,并方

便从文本中提取关键信息,如提取日期、邮箱、URL 等。

在正则表达式中,通过将部分模式用圆括号括起来,形成一个捕获组。

每个捕获组会分配一个组号,从左到右依次编号,最左边的组为第一个,编号为 1

编号为 0 的组总是指整个正则表达式匹配的内容。

通过这些组号,匹配到子字符串可以在后续处理中被引用和操作。

而根据命名方式的不同,又可以分为两种组:

1.数字编号捕获组:

语法:(exp)

解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第 0 组为整个表达式,第一组开始为分组。

比如固定电话的:020-85653333

他的正则表达式为:(0\d{2})-(\d{8})

按照左括号的顺序,这个表达式有如下分组:

序号 编号 分组 内容
0 0 (0\d{2})-(\d{8}) 020-85653333
1 1 (0\d{2}) 020
2 2 (\d{8}) 85653333

2.命名编号捕获组

语法:(?<name>exp)

解释:分组的命名由表达式中的 name 指定

比如区号也可以这样写:(?<quhao>\0\d{2})-(?<haoma>\d{8}),按照左括号的顺序,这个表达式有如下分组:

序号 名称 分组 内容
0 0 (0\d{2})-(\d{8}) 020-85653333
1 quhao (0\d{2}) 020
2 haoma (\d{8}) 85653333

非捕获组

语法:(?:exp)

它不捕获文本 ,也不针对组合计进行计数。比如上面的正则表达式,程序不需要用到第一个分组,那就可以这样写:

复制代码
(?:\0\d{2})-(\d{8})
序号 编号 分组 内容
0 0 (0\d{2})-(\d{8}) 020-85653333
1 1 (\d{8}) 85653333

9.反向组

捕获会返回一个捕获组,这个分组是保存在内存中,不仅可以在正则表达式外部通过程序进行引

用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。它的作用主要是用来查找一

些重复的内容或者做替换指定字符。

正则表达式的匹配是从左到右进行的。当遇到反向引用时,会尝试用之前捕获组匹配到的内容去替

换当前位置,如果替换后能够继续匹配,则继续;否则,匹配失败。

根据捕获组的命名规则,反向引用可分为:

  • 数字编号组反向引用:\k 或\number

  • 命名编号组反向引用:\k 或者\'name'

例如,找一串字母"aabbbbgbddesddfiid"里成对的字母

思路:

1)匹配到一个字母

2)匹配第下一个字母,检查是否和上一个字母是否一样

3)如果一样,则匹配成功,否则失败

首先匹配一个字母:\w,我们需要做成分组才能捕获,因此写成这样:(\w)

那这个表达式就有一个捕获组:(\w)

然后我们要用这个捕获组作为条件,那就可以:(\w)\1这样就大功告成了。

捕获组和反向引用的工作原理

  1. 捕获组 (\\w)

    • 每次 \\w 匹配到一个字符时,这个字符会被捕获到一个组中。这个组的编号为1。

    • 捕获组的内容是动态的,每次匹配到新的字符时,组中的内容会被更新。

  2. 反向引用 \\1

    • \\1 会匹配与第一个捕获组(即 \\w)相同的字符。

    • 每次 \\w 匹配到新的字符时,\\1 会匹配与这个新字符相同的下一个字符。

  • 字符串: aabbbbgbddesddfiid

  • 匹配过程:

    • aa:第一个 \\w 匹配 a,捕获到组1中,\\1 匹配 a,输出 aa

    • bb:第一个 \\w 匹配 b,捕获到组1中,\\1 匹配 b,输出 bb

    • bb:第一个 \\w 匹配 b,捕获到组1中,\\1 匹配 b,输出 bb

    • bb:第一个 \\w 匹配 b,捕获到组1中,\\1 匹配 b,输出 bb

    • dd:第一个 \\w 匹配 d,捕获到组1中,\\1 匹配 d,输出 dd

    • dd:第一个 \\w 匹配 d,捕获到组1中,\\1 匹配 d,输出 dd

    • ii:第一个 \\w 匹配 i,捕获到组1中,\\1 匹配 i,输出 ii

      import re

      待匹配的字符串

      text = 'aabbbbgbddesddfiid'

      正则表达式,匹配连续的相同字符对

      pattern = r'(\w)\1'

      使用re模块的findall方法查找所有匹配的字符对

      matches = re.findall(pattern, text)

      输出匹配结果

      for match in matches:
      print(match + match) # 输出连续的相同字符对

10.贪婪与非贪婪

在正则表达式中,贪婪(greedy)和非贪婪(non-greedy)是两种不同的匹配模式,它们决定了正

则表达式在匹配字符串时的行为。贪婪模式是正则表达式的默认匹配模式。在这种模式下,正则表

达式会尽可能多地匹配字符。非贪婪模式(也称为懒惰模式)与贪婪模式相反。在这种模式下,正

则表达式会尽可能少地匹配字符。

前面我们讲过重复限定符,其实这些限定符就是贪婪量词,比如表达式\d{3,6}:

复制代码
文本:61762828 176 2991 871
贪婪模式:\\d{3,6}
匹配结果:617628
匹配结果:176
匹配结果:2991
匹配结果:871

贪婪模式:正则表达式 \d{3,6} 会尽可能多地匹配字符,但不会超过6位。
匹配结果:匹配所有长度在3到6位之间的数字。

匹配 61762828
正则表达式 \d{3,6} 从字符串的开头开始匹配。
它会尽可能多地匹配数字,但不会超过6位。
因此,它匹配了前6位数字 617628

匹配过的字符不会再参与后续的匹配。
正则表达式引擎在找到一个匹配后,会从匹配结束的位置继续查找下一个匹配
不会重新考虑已经匹配过的字符

当多个贪婪量词在一起时,正则表达式引擎会按照从左到右的顺序进行匹配,每个量词都会尽可

能多地匹配字符,但会受到其他量词和整个正则表达式的约束。如果某个量词的贪婪匹配导致后续

部分无法匹配成功,正则表达式引擎会进行回溯,尝试减少当前量词的匹配长度,以便后续部分能

够成功匹配。

示例

假设我们有字符串 abcde12345,要匹配的正则表达式是 (.*)(\d+)

  1. 初始贪婪匹配

    • (.*) 是一个贪婪量词,它会尽可能多地匹配字符。在匹配字符串 abcde12345 时,它会先匹配整个字符串,即 abcde12345

    • \d+ 也表示贪婪匹配一个或多个数字,但由于前面的 (.*) 已经匹配了整个字符串,此时 \d+ 无法再匹配到数字,因为已经没有剩余的字符了。

  2. 回溯过程

    • 正则表达式引擎发现**(\d+)** 无法匹配,于是开始回溯。它会减少 (.*) 的匹配长度,尝试让后续的 **(\d+)**能够匹配成功。

    • (.*) 先减少一个字符,匹配 abcde1234,此时 (\d+) 可以匹配到数字 5,匹配成功。所以最终匹配结果是 abcde1234(.*) 匹配,5(\d+) 匹配。

    import re

    待匹配的字符串

    text = 'abcde12345'

    正则表达式,将\d+部分也用圆括号括起来,使其成为一个捕获组

    pattern = r'(.*)(\d+)'

    使用re模块的search方法进行匹配

    match = re.search(pattern, text)

    if match:
    # 输出匹配结果
    print("匹配成功!")
    print("正则表达式:" + pattern)
    print("匹配的整个字符串为:", match.group(0))
    print("第一个捕获组匹配的内容为:", match.group(1))
    print("第二个捕获组匹配的内容为:", match.group(2))
    else:
    print("匹配失败!")

    输出结果:
    匹配成功!
    正则表达式:(.*)(\d+)
    匹配的整个字符串为: abcde12345
    第一个捕获组匹配的内容为: abcde1234
    第二个捕获组匹配的内容为: 5

懒惰(非贪婪)

懒惰(非贪婪)量词在正则表达式中用于尽可能少地匹配字符。与贪婪量词相反,懒惰量词会尝试

匹配最少的字符,以满足整个正则表达式的匹配条件。懒惰量词通常通过在量词后面加上一个问号

? 来表示。

代码 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
复制代码
假设我们有字符串 "abc123def456",使用正则表达式 (\w+?)(\d+):
(\w+?):懒惰模式,匹配一个或多个字母、数字或下划线,但尽可能少。
(\d+):贪婪模式,匹配一个或多个数字。


匹配过程
从文本"abc123def456"开始,正则表达式首先尝试用(\\w+?)匹配。
它匹配"abc",因为"abc"是第一个字母序列,且非贪婪匹配使得它在这里停止。
接下来,正则表达式用(\\d+)匹配。在"abc"之后,它匹配"123",因为"123"是第一个数字序列。

import re

# 定义正则表达式和测试文本
reg = r"(\w+?)(\d+)"
test = "abc123def456"

print("文本:" + test)
print("正则表达式:" + reg)

# 编译正则表达式
p1 = re.compile(reg)

# 创建匹配器
m1 = p1.search(test)

if m1:
    print("匹配结果:")
    print("整个匹配:" + m1.group(0))
    print("部分1:" + m1.group(1))
    print("部分2:" + m1.group(2))
else:
    print("没有匹配到任何内容")

运行结果:
文本:abc123def456
正则表达式:(\w+?)(\d+)
匹配结果:
整个匹配:abc123
部分1:abc
部分2:123
相关推荐
吴声子夜歌2 小时前
正则表达式——环视
开发语言·正则表达式
吴声子夜歌18 小时前
正则表达式——边界
正则表达式
营养充电站1 天前
C++23 新特性在 CLion 中的实战体验
macos·docker·jupyter·正则表达式
吴声子夜歌2 天前
正则表达式——字符组
正则表达式
天若有情6732 天前
自制浏览器端文本批量处理小工具,支持查找替换、内容删除、正则表达式,开箱即用
正则表达式
吴声子夜歌2 天前
正则表达式——简单的模式匹配
正则表达式
吴声子夜歌2 天前
正则表达式——前后查找
正则表达式
吴声子夜歌2 天前
正则表达式参考
正则表达式
吴声子夜歌3 天前
正则表达式——常见应用软件和编程语言中的正则表达式
正则表达式
吴声子夜歌3 天前
正则表达式——使用子表达式
正则表达式