User:Xyy23330121/Python/正则表达式


正则表达式是处理、分割字符串的重要工具。它可以以一定的模式对字符串进行“匹配”,从而可以按模式筛选字符串或子字符串,并对筛选结果进行操作。

Python 专属的正则表达式语法

编辑

以下语法中,一部分为 Python 的正则表达式专属。在其它的正则表达式实现中使用可能会出错。

基本匹配

编辑

正则表达式中,大多数字符都只匹配其自身。比如:正则表达式中 t 会唯一的匹配字符 t,而正则表达式 e 会唯一的匹配字符 e等等。

正则表达式满足相加性质。即当“正则表达式A”匹配“字符串a”,而“正则表达式B”匹配“字符串b”时。拼接 A 和 B 得到的“正则表达式A+B”,一定匹配拼接得到的“字符串a+b”。

比如,正则表达式 test 会唯一的匹配字符串 test

元字符

编辑

对于正则表达式而言,上面的“基本匹配”是不足以满足“按模式匹配”的需要的。为了描述模式,正则表达式设置了以下的元字符:

. ^ $ * + ? { } [ ] \ | ( )

这些元字符在正则表达式中表示特殊的意思,从而也就不能匹配字符串中相同字符。元字符之外的字符,都只匹配其自身。

以下将讲解元字符的用法。

^ 字符串开头

编辑

匹配位于字符串开头的空字符串。比如:

  • 正则表达式 a 和字符串 aba 匹配,会得到两个子字符串 a
  • 而正则表达式 ^a 和字符串 aba 匹配,只会匹配到位于字符串开头的子字符串。

$ 字符串末尾

编辑

匹配字符串末尾的空字符串,如果字符串末尾是换行符,也可以匹配该换行符前的空字符串。

\ 转义字符

编辑

通过 \,我们可以在正则表达式转义出匹配元字符的表达式。比如:

  • \[ 唯一地匹配字符 [
  • \\ 唯一地匹配字符 \

创建正则表达式时,是通过字符串来创建的。如果要创建正则表达式 \\,需要使用字符串 "\\\\"r"\\"。为了简便和易于阅读,在创建正则表达式时,请务必使用“原始字符串”的方式。

字符串的转义

编辑

Python 字符串支持的、以下转义序列也被用作正则表达式的转义。

转义字符 含义
\a 响铃(BEL)
\b 退格(BS)
\f 换页(FF)
\n 换行(LF)
\r 回车(CR)
\t 横向制表符(TAB)
\v 纵向制表符(VT)
\ooo 八进制数ooo所表示的字符
\xhh 二位十六进制数hh所表示的字符
\N{name} Unicode 数据库中,名为name的字符,
name可以为别名。有些别名不被支持。
\uxxxx 四位十六进制数xxxx所表示的字符
\Uxxxxxxxx 八位十六进制数xxxxxxxx表示的字符

特别的,以下几种方式与字符串中的使用有区别:

  • 对于 \ooo,只有以下两种方式会被视为此转义:
    • 首位数字是 0,比如 \01
    • 使用了完整的三位数字,比如 \123
  • 对于 \b,只有在“字符类”中,才认为是退格符。
  • 对于 \uxxxx\Uxxxxxxxx\N{name},仅在 Unicode 字符串模式才会被识别。

其它转义

编辑
转义 解释
\A 只匹配字符串开头
\b 只匹配单词开头或结尾
单词开头和结尾被定义为:
  • \w\W 之间的空字符串
  • 或者 \w 和字符串开头或结尾之间的空字符串
\B 非 \b 的空字符串
\Z 只匹配字符串结尾

关于 \w\W,参见下面 ASCII 模式和 Unicode 模式 章节。

[...] 字符类

编辑

[] 括住的部分为字符类。字符类会匹配任何在类中的字符,比如:[aA1] 能匹配三个字符串:aA1

\以外的元字符在字符类中都不起作用。比如:[$] 会唯一的匹配字符 $

如果要添加 ] 到字符类中,则必须要使用转义方法 \]

除上述规则以外,为简便起见,字符类有以下的特殊的表示方式:

字符类:“-”

编辑

- 可以在字符类中表示“范围”,比如:

  • [a-c] 能匹配 abc
  • [a-zA-Z0-9] 能匹配任何单个英文字母字符。

需要注意的是,如果把 - 放在首尾,则不会表示“范围”,而是表示单独的 - 字符。比如:

  • [-a] 能匹配 -a
  • [-a-c] 能匹配 -abc

字符类:“^”

编辑

若字符类中第一个字符为 ^,则认为是对字符类取反,比如:

  • [^a] 能匹配任何不是 a 的字符。
  • [^a-z] 能匹配任何不是小写英文字母的字符。

需要注意的是,如果把 ^ 放在中间,则没有此效果,比如:

  • [ ^] 能匹配空格字符和 ^

. 任意字符

编辑

. 可以匹配除了换行符以外的任意字符。

重复匹配

编辑

通过以下的方式,可以设定匹配次数。

* 匹配任意次

编辑

正则表达式 ab* 会匹配字符串 aababb 等。

+ 匹配 >=1 次

编辑

正则表达式 ab+ 会匹配字符串 ababb 等。但不会匹配 a

? 匹配 0 或 1 次

编辑

正则表达式 ab? 会匹配字符串 a 和字符串 ab

{m} 匹配 m 次
编辑

比如:正则表达式 a{3} 会匹配字符串 aaa

使用 a{m} 时,m 必须是正整数。

{m,n} 匹配 m ~ n 次
编辑

m 和 n 必须是正整数。

比如:正则表达式 a{2,3} 会匹配字符串 aa 和字符串 aaa

特别的,使用 {,n} 会匹配 0 ~ n 次。使用 {m,} 会匹配 m ~ 任意次。使用 {,} 等同于使用 *

重复匹配算法

编辑

在进行重复匹配时,默认会先尽可能多地进行匹配。在正则表达式 ^[a-z]*reabrer进行匹配时,会进行以下步骤:

  • [a-z]* 匹配 abrer
    • r 到字符串末尾,无法匹配,回退
  • [a-z]* 匹配 abre
    • r 匹配 r
    • e 到字符串末尾,无法匹配,回退
  • [a-z]* 匹配 abr
    • r 无法匹配 e ,回退
  • [a-z]* 匹配 ab
    • r 匹配 r
    • e 匹配 e
    • 正则表达式结束,匹配完毕,匹配到子字符串 abre

可以看出 [a-z]* 会先尝试匹配 5 个字符、再尝试匹配 4 个字符,从匹配字符数从多到少进行尝试。这种尝试的模式被称为“贪婪模式”。

? 非贪婪模式

编辑

在代表重复匹配的结构后添加 ? ,则会改为匹配字符数从少到多进行尝试。

比如,在正则表达式 ^[a-z]*?reabrer进行匹配时,会进行以下步骤:

  • [a-z]*? 匹配 a
    • r 无法匹配 b ,回退
  • [a-z]*? 匹配 ab
    • r 匹配 r
    • e 匹配 e
    • 正则表达式结束,匹配完毕,匹配到子字符串 abre

+ 占有模式

编辑

在代表重复匹配的结构后添加 + ,则会在贪婪模式的基础上,不允许回退。这也被称为“占有模式”。

比如,在正则表达式 ^a?[a-z]*+reabrer进行匹配时,会进行以下步骤:

  • a? 匹配 a
    • [a-z]*+ 匹配 brer 并“占有”
      • r 无法匹配字符串末尾,回退
        不能回退并修改[a-z]*+的匹配结果
        回退并修改在[a-z]*+之前的a?
  • a? 匹配空字符串
    • [a-z]*+ 匹配 abrer 并“占有”
      • r 无法匹配字符串末尾,回退
        不能回退并修改[a-z]*+的匹配结果
  • 正则表达式结束,匹配完毕,没有匹配到字符串。

“占有模式”和“非贪婪模式”不能同时设置。

正则表达式 a|b 会匹配字符串 ab

特别的,如果左边的“分支”匹配成功了,就不会尝试对右边的分支进行匹配。比如:

[a-z]*|[0-9]*114 和字符串进行匹配时,会先匹配 [a-z]*114,匹配失败后再尝试匹配 [0-9]*114。如果 [a-z]*114 匹配成功,就不会尝试 [0-9]*114 了。举个例子:

  • [a-z]*|[0-9]*1141140114 匹配:
    • 尝试匹配 [a-z]*114
      • [a-z]* 匹配到空字符串
        • 114 匹配到 114
          匹配成功,匹配到 114

在匹配过程中,根本不会尝试匹配 [0-9]*114。即便 [0-9]*114 能匹配完整的 1140114

(...) 组合

编辑

我们可以把一系列正则表达式作成组合,并对组合进行操作。比如:

  • 正则表达式 (ab)? 会匹配字符串 ab 和字符串 abab
  • 正则表达式 (ab)|c 会匹配字符串 ab 和字符串 c
匹配方式字符
字符 匹配方式
i 忽略大小写
m 允许多行匹配
s . 会匹配任意字符
(包括换行符)
x 带注释的正则表达式
a ASCII 模式
u Unicode 模式
L 按本地语言区域模式

(?imsxauL) 设置匹配方式

编辑

以上的匹配方式并不绝对。如果在正则表达式中,出现了形如 (?a) 的组,则会按照该组的内容设置匹配方式。

这种类型的组不会匹配任何内容,它只匹配空字符串。

右表列出了该组中,可用的字符。这些字符可以进行组合,以同时设置多个匹配方式。

其中,ASCII 模式、 Unicode 模式和“按本地语言区域”模式互相矛盾,因此不能相互组合。在未设置这些模式时,默认使用 Unicode 模式。“按本地语言区域”的模式不可靠,不建议使用。

ASCII 模式和 Unicode 模式

编辑

ASCII 模式和 Unicode 模式设置以下转义得到的字符类的行为:

转义 ASCII 模式 Unicode 模式
\d [0-9] 任何属于 Unicode 字符类别 ND 的字符。
\D [^0-9] 任何非 \d 的字符。
\s [ \t\n\r\f\v] 任何 Unicode 空白字符
\S [^ \t\n\r\f\v] 任何非 Unicode 空白字符
\w [a-zA-Z0-9_] 任何 str.isalnum() 方法返回 True 的字符
以及下划线
\W [^a-zA-Z0-9_] 任何 str.isalnum() 方法返回 False 的字符
不包含下环线

多行匹配

编辑

多行匹配模式会影响 ^$ 的行为。

在设置了多行匹配模式后:

  • ^ 会匹配位于换行符后的空字符串。
  • $ 会匹配位于换行符前的空字符串。

带注释的正则表达式

编辑

此时,允许创建带有注释的、美观的正则表达式。以下两种方式得到的结果是相同的:

pattern = r"(0b[01]*)|(0o[0-7]*)|([0-9]*)|(0x[0-9a-fA-F]*)"
r = re.compile(pattern) #编译正则表达式
pattern = r"""(?x) #匹配数字:
  (0b[01]*)          #二进制数字
| (0o[0-7]*)         #八进制数字
| ([0-9]*)           #十进制数字
| (0x[0-9a-fA-F]*)   #十六进制数字
"""
r = re.compile(pattern) #编译正则表达式

此时,正则表达式会忽略字符串中的空格,以及多行字符串中、自动加入的换行符。

如果要匹配空格,则使用 \ 。如果要匹配换行符,则使用 \n

(?imsxauL-imsx:...) 设置局部匹配方式

编辑

仅对其中的正则表达式启用或弃用某匹配方式。比如:

  • (?i)ab 会匹配:abAbaBAB
  • (?i)a(?-i:b) 会匹配:abAb。弃用了对 b 的“忽略大小写模式”。
  • a(?i:b) 会匹配:aBAB。仅对 b 启用“忽略大小写模式”。

特别的,不允许简单地弃用“ASCII 模式”“Unicode 模式”和“按本地语言区域模式”。

(?>...) 占有模式

编辑

该组会对组中的表达式启用“占有模式”,比如:

  • x*+ 等同于 (?>x*)

(?:...) 非捕获模式

编辑

此方法会影响 Python 在进行匹配时、保存的结果。比如:

  • (xyz) 在匹配时,会在结果中保存匹配到的 xyz
  • (?:xy(z)) 在匹配时,会匹配到相同的内容,但结果中仅保存 z

(?P<name>...) 带命名的组

编辑

通过这种方式,可以给组进行命名。re.Match 实例的多个方法,以及 re.Pattern 实例的 sub 方法会引用命名组匹配到的内容。

被命名的组可以用以下方式,在正则表达式中引用。

(?P=name)

编辑

仅匹配第一次匹配到 (?P<name>...) 时、匹配到的内容。

import re
p = re.compile(r'''(?x)
<(?P<ele_name>[a-zA-Z]+).*?> #匹配<html>
.*                           #匹配中间
</(?P=ele_name)>             #匹配</html>
''')
s = '<p>Paragraph</pi>'
s1 = '<p>Paragraph</p>'

print(p.match(s).string[matchobj.start():matchobj.end()])  #输出空字符串,没有得到匹配。
print(p.match(s1).string[matchobj.start():matchobj.end()]) #输出:<p>Paragraph</p>

类似 (?P=name) ,但此方式也可以用于引用一般的组。

仅匹配第一次匹配到第 num 个组时,匹配到的内容。

(?#...) 注释

编辑

作为注释使用,仅匹配空字符串。

(?=...) 前视断言

编辑

仅匹配空字符串。

其中的正则表达式和当前位置右侧的内容相匹配,则匹配空字符串成功。否则匹配失败。

比如:正则表达式 Isaac(?= Newton) 将会匹配 Isaac Newton 中的 Isaac,但不会匹配 Isaac Brown 中的 Isaac

这种方式被称为前视断言 (lookahead assertion)。

(?!...) 否定型前视断言

编辑

仅匹配空字符串。

其中的正则表达式和当前位置右侧的内容不匹配,则匹配空字符串成功。否则匹配失败。

比如:正则表达式 Isaac(?! Newton) 将会匹配 Isaac Brown 中的 Isaac,但不会匹配 Isaac Newton 中的 Isaac

这种方式被称为否定型前视断言 (negative lookahead assertion)。

(?<=...) 肯定型后视断言

编辑

仅匹配空字符串。

其中的正则表达式与当前位置左侧的内容相匹配,则匹配空字符串成功。否则匹配失败。

这种方式被称为肯定型后视断言 (positive lookbehind assertion)。

(?<!…) 否定型后视断言

编辑

仅匹配空字符串。

其中的正则表达式和当前位置左侧的内容不匹配,则匹配空字符串成功。否则匹配失败。

这种方式被称为否定型后视断言 (negative lookbehind assertion)。

(?(num/name)yes-pattern|no-pattern) 条件匹配

编辑

在匹配时,之前已有的、\num(?P=name) 所示的组、匹配的并非空字符串,则按照 yes-pattern 进行匹配。否则,按照 no-pattern 进行匹配。

Python 使用正则表达式

编辑
旗标列表
旗标 匹配方式
re.I
re.IGNORECASE
忽略大小写
re.M
re.MULTILINE
允许多行匹配
re.S
re.DOTALL
. 会匹配任意字符
(包括换行符)
re.X
re.VERBOSE
带注释的正则表达式
re.A
re.ASCII
ASCII 模式
re.U
re.UNICODE
Unicode 模式
re.L
re.LOCALE
按本地语言区域模式
re.NOFLAG 表示空旗标。
特别的:re.NOFLAG 就是整数 0
re.DEBUG 显示有关编译表达式的调试信息。

旗标

编辑

旗标和上述 设置匹配方式 章节的效果是相同的。右表列出了可用的旗标。

在使用旗标时,可以用“按位或”运算符 | 组合多个旗标,并把结果传入下面函数中的 flags 参数中。

特别的:re.NOFLAG 和任何其它旗标进行组合,都会返回其它旗标。

re.DEBUG 会在使用正则表达式时,返回编译的结果。

re.compile(pattern, flags=0)

编辑

编译正则表达式。返回一个 re.Pattern 实例。re 模块是通过该类型的实例方法来进行匹配操作的。

re.Pattern 的属性
属性 解释
flags 旗标。是 compile 函数获取的旗标以及正则表达式中用 (?imsxauL) 设置的匹配方式的组合。
groups 捕获到的模式串中组的数量。
groupindex 映射由 (?P<name>) 定义的、有命名的组,其命名:索引位置组成的的字典。
如果没有有命名的组,那字典就是空的。
pattern 编译对象的原始样式字符串。

re.Pattern 实例方法

编辑

简单匹配

编辑
search(string[, pos[, endpos]])
编辑

string[pos: endpos] 中,查找匹配正则表达式的第一个位置,并返回对应的 re.Match 实例。如果没有匹配的位置,则返回 None。

match(string[, pos[, endpos]])
编辑

类似 search 方法,但是从 string[pos: endpos] 的开头开始匹配:不在 string[pos: endpos] 最开头的、匹配的位置都不会被匹配到。

fullmatch(string[, pos[, endpos]])
编辑

如果 string[pos: endpos] 整个字符串都与正则表达式匹配,则返回相应的 re.Match 实例。

finditer(string[, pos[, endpos]])
编辑

string[pos: endpos] 中,查找匹配正则表达式的、所有非重复的位置。

每个位置,都会生成一个 re.Match 实例。该函数会返回生成的 re.Match 实例组成的迭代器。

findall(string[, pos[, endpos]])
编辑

类似 finditer 方法。但是输出不同。

如果正则表达式中不包含一般的组,则会输出匹配的子字符串组成的列表。而如果正则表达式中包含组,则只输出组中的内容。比如:

import re
p = re.compile(r"\b[tT]\w+\b")
s1 = "This is a test string."
print(p.findall(s1)) #输出:['This', 'test']

q = re.compile(r"\b(\w+)=([^ ]+)")
s2 = '<img src="./image.png" width=100 height=100 />'
print(q.findall(s2)) #输出:[('src', '"./image.png"'), ('width', '100'), ('height', '100')]

分割

编辑
split(string, maxsplit=0)
编辑

string 中所有匹配的子字符串为分割符,返回分割后的、子字符串的列表。

最多进行 maxsplit 次分割。

特别的:

  • 如果 string 的开头被匹配,则返回的列表会以空字符串开头。
  • 如果正则表达式中包含一般的组(而非(?:...)的组),则会在分割中保留组内匹配的内容。
import re
p = re.compile("(a)(b)")
q = re.compile("(?:a)(?:b)")

s = "ab"

print(p.split(s)) #输出:['c', 'a', 'b', 'c']
print(q.split(s)) #输出:['c', 'c']

可以看出,使用一般的组的 p 在进行分割时,输出的结果中包含了组中的内容。而使用非捕获模式的 q 则没有进行这样的操作。

替换

编辑
sub(repl, string, count=0)
编辑

repl 所示的模式替换 string 中、匹配的内容。最多替换 count 次。repl 可以是字符串或函数。

如果 repl 是字符串,则直接进行替换,比如:

import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
r = 'REPLACE'

print(p.sub(r, s))  #输出:<img REPLACE REPLACE REPLACE />

特别的, repl 中包含的、字符串转义序列的原始序列都会被转义。并且一些特殊的转义也会被执行:

特殊转义
转义 注释
\g<num> 正则表达式中,第 num 个组匹配到的内容
\num 正则表达式中,第 num 个组匹配到的内容
由于和 \ooo 冲突,num 不能超过 100,且
开头不能为 0。
\g<name> 正则表达式中,名为 name 的组匹配到的内容
import re
p = re.compile(r"\b(?P<arg>\w+)=([^ ]+)")
s = '<img src="./image.png" />'
r = r'\g<1>\1\g<arg>'

print(p.sub(r, s))  #输出:<img srcsrcsrc />

如果 repl 是函数,则该函数应可以传入每次匹配时,对应的 re.Match 实例,并返回替换的结果。

import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
def r(matchobj):
    print(matchobj.string[matchobj.start():matchobj.end()])
    return "REPLACE"

print(p.sub(r, s))

输出为:

src="./image.png"
width=100
height=100
<img REPLACE REPLACE REPLACE />
subn(repl, string, count=0)
编辑

类似 sub 方法,但返回一个元组: (替换后字符串, 替换次数)

单次使用

编辑

如果只匹配一次,也可以直接使用 re 模块的以下函数:

函数 等同于
re.search(pattern, string, flags=0) re.compile(pattern, flags).search(string)
re.match(pattern, string, flags=0) re.compile(pattern, flags).match(string)
re.fullmatch(pattern, string, flags=0) re.compile(pattern, flags).fullmatch(string)
re.split(pattern, string, maxsplit=0, flags=0) re.compile(pattern, flags).split(string, maxsplit)
re.findall(pattern, string, flags=0) re.compile(pattern, flags).findall(string)
re.finditer(pattern, string, flags=0) re.compile(pattern, flags).finditer(string)
re.sub(pattern, repl, string, count=0, flags=0) re.compile(pattern, flags).sub(repl, string, count)
re.subn(pattern, repl, string, count=0, flags=0) re.compile(pattern, flags).subn(repl, string, count)

re.Match 的实例方法与属性

编辑

替换字符串

编辑
expand(template)
编辑

替换字符串 template 的内容,使其被转义。并返回替换后的字符串。

除字符串的转义之外,\1\g<1> 等也会被转义。

匹配到的组

编辑
group([group1, ...])
编辑

返回指定的组所匹配的内容。

其参数可以为正整数,代表组的索引(以 1 开头)。也可以是字符串,代表组的名称。

如果只有一个参数,则会返回字符串。如果有多个参数,则会返回字符串组成的元组。

特别的:

  • 参数 0 对应的是匹配的完整子字符串。
  • 不输入任何参数时,等同于 self.group(0)
import re
p = re.compile(r"\b(?P<attr>\w+)=(?P<value>[^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
m = p.search(s)

print(m.group())               #输出:src="./image.png"
print(m.group(0))              #输出:src="./image.png"
print(m.group(1))              #输出:src
print(m.group(1,2))            #输出:('src', '"./image.png"')
print(m.group("attr","value")) #输出:('src', '"./image.png"')
__getitem__(g)
编辑

使用 self[g] 来调用此方法。

等同于 self.group(g)

groups(default=None)
编辑

返回包含所有匹配子组的字符串。对于总共有 n 个组的情况,等同于 self.group(1,2,...,n)

形如 (pattern)* 的组可能一次都不会被匹配。此时,该组对应的位置会输出 default 的内容。

groupdict(default=None)
编辑

类似 groups 方法,但是返回的是 组的名称:匹配到的内容 的字典。

lastindex
编辑

该属性保存最后一个匹配的整数索引值。如果没有匹配的组,该属性为 None。

嵌套中的组不会被计数。比如,对于字符串 'ab',表达式 (a)b, ((a)(b)), 和 ((ab)) 将得到 lastindex == 1 , 而 (a)(b) 会得到 lastindex == 2 。

lastgroup
编辑

最后一个匹配的命名组名字。如果没有匹配的组,该属性为 None。

索引

编辑
start([group])
编辑

返回 group 所示的组,在原始字符串中起始位置的索引。

如果不输入 groupgroup 为 0,则返回匹配开始的位置。

end([group])
编辑

返回 group 所示的组,在原始字符串中结束位置的索引。

如果不输入 groupgroup 为 0,则返回匹配结束的位置。

span([group])
编辑

返回 (start([group]), end([group])) 元组。

匹配参数

编辑

生成 re.Match 需要用到 使用 re.compile(pattern).match(string, pos, endpos) 或 re.compile(pattern).search(string, pos, endpos) 等方法。以下属性表示其中的参数或对象。

生成此 re.Match 实例所使用的参数 pos。

endpos
编辑

生成此 re.Match 实例所使用的参数 endpos。

生成此 re.Match 实例所使用的 re.Pattern 实例。

string
编辑

生成此 re.Match 实例所使用的参数 string。即被匹配的字符串。

其它操作

编辑

re.escape(pattern)

编辑

生成一个能匹配 pattern 字符串的、正则表达式字符串。

re.purge()

编辑

清除正则表达式的缓存。

注意事项

编辑

由于“多次匹配”的匹配机制,一些正则表达式可能会导致匹配时间随字符串长度指数增长,比如以下是导致 Cloudflare 在 2019 年 7 月发生全球宕机事故的正则表达式的一部分:

  • .*(?:.*=.*)

如果用这个正则表达式来匹配形如 title=User:Xyy23330121 的内容,它会:

  • .* 匹配到 title=User:Xyy23330121
    • .* 匹配到空字符串
      • = 到字符串尾,无法匹配,回退
    • 空字符串无法回退,继续向前回退。
  • .* 匹配到 title=User:Xyy2333012
    • .* 匹配到 1
      • = 到字符串尾,无法匹配,回退
    • .* 匹配到空字符串
      • = 匹配到 1,无法匹配,回退
    • 空字符串无法回退,继续向前回退。
  • .* 匹配到 title=User:Xyy233301
  • ...

从结论而言,它会尝试匹配总共 1 + 2 + ... + 16 = 136 次才会正确匹配所有内容。而如果等号后面的内容更多,情况只会更糟。

请在设置正则表达式时尽可能慎重。

參考文獻

编辑