Python 4-01 re
  1Hh2sdYQZd4C 2023年11月02日 45 0

Python 内置模块 re

正则表达式(Regular Expression 简写 regex)是一种字符串匹配的模式(pattern)。


一、匹配原理

正则表达式是由正则表达式引擎编译执行的。


1、预编译(pre-use compile)

通过 re.compile(pattern) 预编译返回 Pattern 对象

通过 re.match(pattern, text) 即用编译,虽然也会有缓存 Pattern

对象,但是每次使用都需要去缓存中取出,比预编译多一步取操作。

import re

# 将正则表达式 'hello' 编译成 Pattern 对象
pattern = re.compile('hello')
print(type(pattern))  # <class 're.Pattern'>
print(dir(pattern))
# ['findall', 'finditer', 'flags', 'fullmatch', 'groupindex', 'groups', 'match', 'pattern', 'scanner', 'search', 'split', 'sub', 'subn']
# 使用 Pattern 匹配文本,获得匹配结果,无法匹配时将返回 None
match = pattern.match('hello world!')
print(type(match))
print(dir(match))
# <class 're.Match'>
# ['end', 'endpos', 'expand', 'group', 'groupdict', 'groups', 'lastgroup', 'lastindex', 'pos', 're', 'regs', 'span', 'start', 'string']]

# match = re.match('hello', 'hello world!') # 即用编译
if match:
    # 使用Match获得分组信息
    print(match.group())
    
# hello


2、引擎

正则引擎主要可以分为基本不同的两大类:


DFA (Deterministic finite automaton) 确定型有穷自动机

NFA (Non-deterministic finite automaton) 非确定型有穷自动机

DFA引擎 和 NFA引擎 的区别就在于:在没有编写正则表达式的前提下,是否能确定字符执行顺序!


3、回溯

大多数编程语言选择 NFA 引擎,它的精髓——回溯。当某个正则分支匹配不成功之后,文本的位置需要回退,然后换另一个分支匹配,而回退这步专业术语就叫:回溯。


例:text = ‘aaaaa’,pattern = “^(a*)*b$”,匹配过程大致是


(a*):匹配到了文本中的 aaaaa

匹配正则中的 b,但是失败,因为 (a*) 已经把 text 都吃了

这时候引擎会要求 (a*) 吐出最后一个字符 a,但是无法匹配 b

第二次是吐出倒数第二个字符 a,依然无法匹配

就这样引擎会要求 (a*) 逐个将吃进去的字符都吐出来

但是到最后都无法匹配 b

有些复杂的正则表达式可能有多个部分都要回溯,那回溯次数就是指数级。


4、优化

NFA 引擎的特点是:功能强大、但有回溯机制所以效率慢,需要优化以减少引擎回溯次数以及更直接的匹配到结果。


二、re 模块

Python 通过 re 模块提供对正则表达式的支持。


1、re 模块常量 flag

re 模块中有 9 个常量,RegexFlag 枚举类型,值都是 int 类型!可叠加使用 |


class RegexFlag(enum.IntFlag):
    IGNORECASE = I = 2  # ignore case 忽略大小写
    ASCII = A = 1 # assume ascii "locale"  让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配 ASCII,而不是 Unicode。
    LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale
    UNICODE = U = sre_compile.SRE_FLAG_UNICODE # assume unicode "locale"
    MULTILINE = M = sre_compile.SRE_FLAG_MULTILINE # make anchors look for newline '^'和'$'会匹配每行的开头和结尾
    DOTALL = S = sre_compile.SRE_FLAG_DOTALL # make dot match newline 匹配换行符 \n
    VERBOSE = X = sre_compile.SRE_FLAG_VERBOSE # ignore whitespace and comments 详细模式
    # sre extensions (experimental, don't rely on these)
    TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking
    DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation


2、re 参数说明

Python 4-01 re_Python


3、re 方法

1)预编译

在多次使用某个正则表达式时推荐使用正则对象 Pattern 以增加复用性,因为通过 re.compile(pattern) 编译后的模块级函数会被缓存!

template 和 compile 类似, template 增加了 re.TEMPLATE。

re.compile(pattern,flags=0)

编译正则表达式模式,返回一个 Pattern 对象。把常用的正则表达式进行预编译,方便后续调用及提高效率。

import re

content = 'Citizen wang , always fall in love with neighbour,WANG'
pattern = re.compile(r'wan\w', re.I)  
subStrings = pattern.findall(content)

# subStrings = re.findall(r'wan\w', content, re.I)
# subStrings = re.findall(re.compile(r'wan\w', re.I), content)


2)查找一个匹配项

查找并返回一个匹配项的方法:


match: 必须从字符串开头匹配

search: 查找任意位置的匹配项

fullmatch: 整个字符串与正则完全匹配

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

尝试从字符串的起始位置匹配一个模式,匹配成功 re.match 方法返回一个匹配的对象,否则返回None。

我们可以使用 group(num) 或 groups() 匹配对象函数来获取匹配表达式。


Python 4-01 re_Python_02

import re

print(re.match('www', 'www.baidu.com'))  # 在起始位置匹配
print(re.match('com', 'www.baidu.com'))  # 不在起始位置匹配
# <re.Match object; span=(0, 3), match='www'>
# None

line = "Cats are smarter than dogs"
matchObj = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)

if matchObj:
    print("match_obj.group(): ", matchObj.group())
    print("match_obj.group(1): ", matchObj.group(1))
    print("match_obj.group(2): ", matchObj.group(2))
    print("match_obj.group(1,2): ", matchObj.group(1, 2))
    print("match_obj.groups(): ", matchObj.groups())
else:
    print("None match!")

# match_obj.group():  Cats are smarter than dogs
# match_obj.group(1):  Cats
# match_obj.group(2):  smarter
# match_obj.group(1,2):  ('Cats', 'smarter')
# match_obj.groups():  ('Cats', 'smarter')


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

扫描整个字符串并返回第一个成功的匹配。

匹配成功 re.search 方法返回一个匹配的对象,否则返回 None。

import re

print(re.search('www', 'www.baidu.com'))
print(re.search('com', 'www.baidu.com'))

line = "Cats are smarter than dogs"

searchObj = re.search(r'(.*) are (.*?).*', line, re.M | re.I)

if searchObj:
    print("searchObj.group() : ", searchObj.group())
    print("searchObj.group(1) : ", searchObj.group(1))
    print("searchObj.group(2) : ", searchObj.group(2))
else:
    print("Nothing found!!")

# re.Match object; span=(0, 3), match='www'>
# <re.Match object; span=(11, 14), match='com'>

# searchObj.group() :  Cats are smarter than dogs
# searchObj.group(1) :  Cats
# searchObj.group(2) :  smarter


3)查找多个匹配项

findall: 从字符串任意位置查找,返回一个列表

finditer:从字符串任意位置查找,返回一个迭代器

re.findall(pattern, string, flags=0)
pattern.findall(string[, pos[, endpos]])

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。


查找字符串中的所有数字:

import re

pattern = re.compile(r'\d+')  # 查找数字
result1 = pattern.findall('Python 123 google 456')
# result1 = re.findall('\d+', 'Python 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)

print(result1)
print(result2)
# ['123', '456']
# ['88', '12']
re.finditer(pattern, string, flags=0)

在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。


import re

it = re.finditer(r"\d+", "12a32bc43jf3")
for match in it:
    print(match.group())

# 12

# 32
# 43
# 3



4)替换字符串中的匹配项

Python 的 re 模块提供了 re.sub 用于替换字符串中的匹配项。re.subn 和它类似。


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

注意:repl 替换内容既可以是字符串,也可以是一个函数哦! 如果 repl为函数时,只能有一个入参:Match 匹配对象。


import re

phone = "2004-959-559 # 这是一个电话号码"
num = re.sub(r'#.*$', "", phone)  # 删除注释
print("电话号码 : ", num)
num = re.sub(r'\D', "", phone)  # 移除非数字的内容
print("电话号码 : ", num)
# 电话号码 :  2004-959-559
# 电话号码 :  2004959559
...

repl 参数是一个函数
以下实例中将字符串中的匹配的数字乘于 2
```python
import re

# 将匹配的数字乘于 2
def double(matched):
    value = int(matched.group('value'))
    return str(value * 2)

s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double, s))
# A46G8HFD1134

5)分割

split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:

re.split(pattern, string[, maxsplit=0, flags=0])

>>> s = 'Python,Python,Python.'
>>> re.split('\W+',s)
['Python', 'Python', 'Python', '']
>>> re.split('(\W+)',s) 
['', ' ', 'Python', ',', 'Python', ',', 'Python', '.', '']
>>> re.split('\W+', s, 1) 
['', 'Python, Python, Python.']
>>> re.split('a', 'hello world')
['hello world']# 对于一个找不到匹配的字符串而言,split 不会对其作出分割


6)其它

re.escape(pattern) 可以转义正则表达式中具有特殊含义的字符,比如:. 或者 * 。

re.purge() 作用就是清除 正则表达式缓存


三、正则表达式

正则表达式是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。


1、匹配单个字符

Python 4-01 re_字符串_03

2、匹配前一个字符重复次数

Python 4-01 re_Python_04

3、匹配范围中一个字符

Python 4-01 re_字符串_05

4、[] 和 |

[] 字符集合设定符 匹配其中一个字符

表明一个字符集合,能够匹配包含在其中的任意一个字符。比如 [abc123] ,表明字符 ’a’ ‘b’ ‘c’ ‘1’ ‘2’ ‘3’ 都符合它的要求。


在 [] 中还可以通过 - 减号来指定一个字符集合的范围,比如 [a-zA-Z] 匹配一个大小写英文字母。


如果在 [] 里面的开头写一个 ^ 号,则表示取非,即在括号里的字符都不匹配。如 [^a-zA-Z] 表明不匹配所有英文字母。但是如果 ^ 不在开头,则它就不再是表示取非,而表示其本身,如 [a-z^A-Z] 表明匹配所有的英文字母和字符 ^。


| 或规则

表示只要满足其中之一就可以匹配。比如 [a-zA-Z]|[0-9] 表示满足数字或字母就可以匹配,这个规则等价于 [a-zA-Z0-9]


注意 :


它在 [] 之中不再表示或,而表示他本身的字符。如果要在 [] 外面表示一个 | 字符,必须用反斜杠引导,即 /| ;

它的有效范围是它两边的整条规则,比如‘dog|cat’ 匹配的是‘ dog’ 和 ’cat’ ,而不是 ’g’ 和 ’c’。如果想限定它的有效范围,必需使用一个无捕获组 ‘(?: )’ 包起来。比如要匹配 ‘ I have a dog’ 或 ’I have a cat’ ,需要写成 r’I have a (?:dog|cat)’ ,而不能写成 r’I have a dog|cat’。

5、匹配一个位置

Python 4-01 re_正则表达式_06

6、字母大写为非

Python 4-01 re_字符串_07

7、分组

Python 4-01 re_Python_08

re.search("(?P<province>[0-9]{4})(?P<city>[0-9]{2})(?P<birthday>[0-9]{8})","371481199306143242").groupdict() 
结果{'province': '3714', 'city': '81', 'birthday': '19930614'}
pattern=re.compile(r'<li><a href="(\d*.html)">(.*?)</a></li>',re.S)
result=pattern.findall(response.text)

注意!!!有分组,返回的是分组内容,由元组组成的列表,如果没有分组则返回 pattern 中的全部。


例:Python 使用正则表达式验证密码

必须包含大小写字母、数字和[$#@] 中的至少1个字符,长度6-12位。


import re
secrets = input().split(',')
pattern = re.compile('^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[$#@]).{6,12}$')
print(','.join([psw for psw in secrets if pattern.match(psw)]))

(?= re)

意思是断言,只匹配一个位置。

想匹配 “宣科”,但是只想匹配宣科人的宣科,就可以用一下表达式


x='宣科院中宣科人'
y=re.search('宣科(?=人)',x)
y
# <re.Match object; span=(4, 6), match='宣科'>

(?=foo) 一个正向的先行断言

(?:foo) 包含子表达式 foo 的非捕获组。


先行断言指的是,x 只有在 y 前面才匹配,必须写成 ‘x(?=y)’。比如,只匹配百分号之前的数字,要写成 ‘\d+(?=%)’。先行否定断言指的是,x 只有不在 y 前面才匹配,必须写成 ‘x(?!y)’。比如,只匹配不在百分号之前的数字,要写成 ‘\d+(?!%)’。


re.search(r"\d+(?=%)", '100% of US presidents have been male')
# <re.Match object; span=(0, 3), match='100'>
re.search(r"\d+(?!%)", 'that’s all 44% of them')  
# <re.Match object; span=(11, 12), match='4'>

上面两个字符串,如果互换正则表达式,就不会得到相同结果。另外,还可以看到,先行断言括号之中的部分((?=%)),是不计入返回结果的。


后行断言正好与先行断言相反,x 只有在 y 后面才匹配,必须写成 ‘(?<=y)x’。比如,只匹配美元符号之后的数字,要写成 ‘(?<=$)\d+’。”后行否定断言“则与”先行否定断言“相反,x 只有不在 y 后面才匹配,必须写成 ‘(?<!y)x’。比如,只匹配不在美元符号后面的数字,要写成 ‘(?<=$)\d+’。

re.search(r"(?<=\$)\d+",'Benjamin Franklin is on the $100 bill') 
<re.Match object; span=(29, 32), match='100'>
re.search(r"(?<!\$)\d+",'it’s is worth about €90')   
<re.Match object; span=(21, 23), match='90'>

上面的例子中,“后行断言”的括号之中的部分((?<=$)),也是不计入返回结果。


8、贪婪模式和非贪婪模式

在 python 中默认采用的是贪婪模式,总是尝试匹配尽可能多的字符;非贪婪模式正好相反,总是尝试匹配尽可能少的字符。非贪婪模式,在量词后面直接加上一个问号 “?”。


例子 “abbbbbbcd” 正则表达式 “ab+”,当匹配到 “ab” 时已经匹配成功,由于采用的是贪婪模式,所以还需要往后继续匹配,一直到匹配到最后一个 “b” 的时候。返回匹配结果 “abbbbbb”。


“+” 对字母 b 匹配 1 次或多次,非贪婪模式匹配 1 次,贪婪模式匹配所有。


非贪婪模式就是将 “ab+” 改为 “ab+?”,当匹配到 “ab” 时,已经匹配成功,不在向后继续尝试,返回匹配成功的字符串 “ab”。


import re

example = "<div>test1</div><div>test2</div>"

greedPattern = re.compile("<div>.*</div>")
notGreedPattern = re.compile("<div>.*?</div>")

greedResult = greedPattern.search(example)
notGreedResult = notGreedPattern.search(example)

print("greedResult = %s" % greedResult.group())
print("notGreedResult = %s" % notGreedResult.group())

greedResult = <div>test1</div><div>test2</div>
notGreedResult = <div>test1</div>


在前端开发中经常使用到的20个正则表达式

1、校验密码强度

密码的强度必须是包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间。


^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$


2、校验中文

字符串仅能是中文


^[\\u4e00-\\u9fa5]{0,}$

3、由数字、26个英文字母或下划线组成的字符串

^\\w+$

4、校验E-Mail 地址

[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?

5、校验身份证号码

下面是身份证号码的正则校验。15 或 18位。

15位:


^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$

18位:


^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$

6、校验日期

“yyyy-mm-dd“ 格式的日期校验,已考虑平闰年。


^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$

7、校验金额

金额校验,精确到2位小数。


^[0-9]+(.[0-9]{2})?$

8、校验手机号

下面是国内 13、15、18开头的手机号正则表达式。(可根据目前国内收集号扩展前两位开头号码)


^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$

9、判断IE的版本

IE目前还没被完全取代,很多页面还是需要做版本兼容,下面是IE版本检查的表达式。


^.*MSIE [5-8](?:\\.[0-9]+)?(?!.*Trident\\/[5-9]\\.0).*$

10、校验IP-v4地址

ip 地址的范围为 0.0.0.0-255.255.255.255,分成四段,则每段的范围都是 0-255。

将每段的取值分为 3 个区间。即 250-255、200-249、0-199,注意:从大到小。


# 匹配 0-255的表达式
pattern = re.compile(r'(25[0-5]|2[0-4]\d|1?\d{1,2})')

使用四段相同的表达式,并在四段表达式之间增加3个点(.)。

# 匹配 0.0.0.0-255.255.255.255的表达式书写方法
pattern = re.compile(r'((?:25[0-5]|2[0-4]\d|1?\d{1,2})\.){3}(?:25[0-5]|2[0-4]\d|1?\d{1,2})')

测试:


# 以下两个示例不是正确的ip,所以匹配不到,无输出结果

import re

pattern = re.compile(r'((?:25[0-5]|2[0-4]\d|1?\d{1,2})\.){3}(?:25[0-5]|2[0-4]\d|1?\d{1,2})')
res = pattern.fullmatch('255.255.255.256')
print(res) # None
res = pattern.fullmatch('1255.255.255.255')
print(res) # None


再使用 search、match 方法对以上两个错误 ip 进行匹配测试。


res = pattern.search('255.255.255.256')
print(res) # <re.Match object; span=(0, 14), match='255.255.255.25'>
res = pattern.search('1255.255.255.255')
print(res) # <re.Match object; span=(1, 16), match='255.255.255.255'>

res = pattern.match('255.255.255.256')
print(res) # <re.Match object; span=(0, 14), match='255.255.255.25'>
res = pattern.match('1255.255.255.255')
print(res) # None


Python 4-01 re_字符串_09

由于 search 匹配时,会查找整个字符串,然后返回满足表达式的结果。所以使用 search 方法进行匹配时,对于 ip 的第一个字段和最后一个字段出错的情况下,会自动进行 ip 址的截取,尽量使结果满足表达式的要求,但是这种结果并不是我们想要的。


match 是从头开始匹配,当 ip 地址的前三段都正确,而最后一个字段出错时,也无法得出预期的结果。


fullmatch 是完全匹配,因此字符串要完全满足 ip 地址规则时,才会返回正确结果,ip 地址有误时,无输出(输出为None)。


当然,非要使用 search 和 match 进行匹配也是可以的,首先了解一下“零宽断言”。


零宽断言:用于查找特定内容之前或之后的内容,但并不包括特定内容本身(零宽)。类似于^、 $、 \b一样的作用,指定某一位置需要满足某些条件(断言)。


Python 4-01 re_字符串_10

匹配 ip 地址时,不允许对不合法的地址进行截取,以得到符合规则的ip地址,即是要求:匹配结果在原字符串中的位置之前和之后不能有被截取的点(.)和数字。


修改原有正则表达式,在原表达式的前面添加(?<![.\d]),最后面添加(?![.\d]),即修改之后完整的表达式为:


pattern = re.compile(r'(?<![\.\d])((?:25[0-5]|2[0-4]\d|1?\d{1,2})\.){3}(?:25[0-5]|2[0-4]\d|1?\d{1,2})(?![\.\d])')

通过上面这条正则表达式,对之前错误的 ip 地址重新进行匹配验证,结果如下,


import re

pattern = re.compile(r'(?<![\.\d])((?:25[0-5]|2[0-4]\d|1?\d{1,2})\.){3}(?:25[0-5]|2[0-4]\d|1?\d{1,2})(?![\.\d])')
try:
    res = pattern.search('255.255.255.256')
    print(res) # None
    res = pattern.search('1255.255.255.255')
    print(res) # None   
    res = pattern.match('255.255.255.256')
    print(res) # None
    res = pattern.match('1255.255.255.255')
    print(res) # None
    res = pattern.search('257.127.0.0.1')
    print(res) # None
    res = pattern.search('255.255.255.122.256')
    print(res) # None
except Exception as e:
    print(e)

11、校验IP-v6地址

IP6 正则语句。


(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))

12、检查URL的前缀

应用开发中很多时候需要区分请求是HTTPS还是HTTP,通过下面的表达式可以取出一个url的前缀然后再逻辑判断。


if (!s.match(/^[a-zA-Z]+:\\/\\//))
{
    s = 'http://' + s;
}

13、提取URL链接

下面的这个表达式可以筛选出一段文本中的URL。


^(f|ht){1}(tp|tps):\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w- ./?%&=]*)?

14、文件路径及扩展名校验

验证windows下文件路径和扩展名(下面的例子中为.txt文件)


^([a-zA-Z]\\:|\\\\)\\\\([^\\\\]+\\\\)*[^\\/:*?"<>|]+\\.txt(l)?$

15、提取Color Hex Codes

有时需要抽取网页中的颜色代码,可以使用下面的表达式。


^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$

16、提取网页图片

假若你想提取网页中所有图片信息,可以利用下面的表达式。

\\< *[img][^\\\\>]*[src] *= *[\\"\\']{0,1}([^\\"\\'\\ >]*)

17、提取页面超链接

提取html中的超链接。


(<a\\s*(?!.*\\brel=)[^>]*)(href="https?:\\/\\/)((?!(?:(?:www\\.)?'.implode('|(?:www\\.)?', $follow_list).'))[^"]+)"((?!.*\\brel=)[^>]*)(?:[^>]*)>

18、查找CSS属性

通过下面的表达式,可以搜索到相匹配的CSS属性。


^\\s*[a-zA-Z\\-]+\\s*[:]{1}\\s[a-zA-Z0-9\\s.#]+[;]{1}


19、抽取注释

如果你需要移除HMTL中的注释,可以使用如下的表达式。


20、匹配HTML标签

通过下面的表达式可以匹配出HTML中的标签属性。


<\\/?\\w+((\\s+\\w+(\\s*=\\s*(?:".*?"|'.*?'|[\\^'">\\s]+))?)+\\s*|\\s*)\\/?>

1

零宽断言

本文主要总结了python正则零宽断言(zero-length-assertion)的一些常用用法。


1. 什么是零宽断言

有时候在使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面或后面需要是特定的内容,但我们又不想要前面或后面的这个特定的内容,这时候就需要零宽断言的帮助了。所谓零宽断言,简单来说就是匹配一个位置,这个位置满足某个正则,但是不纳入匹配结果的,所以叫“零宽”,而且这个位置的前面或后面需要满足某种正则。


比如对于一个字符串:“finished going done doing”,我们希望匹配出其中的以ing结尾的单词,就可以使用零宽断言:

import re
s = 'finished going done doing'
p = re.compile(r'\b\w+(?=ing\b)')

print '【Output】'
print [x + 'ing' for x in re.findall(p,s)]
【Output】
['going', 'doing']

可以看出从中匹配出了’going’和’doing‘两个单词,达到目的。


这里正则中使用的(?=ing\b)就是一种零宽断言,它匹配这样一个位置:这个位置有一个’ing’字符串,后面跟着一个’\b’符号,并且这个位置前面的字符串满足正则:\b\w+,于是匹配结果就是:[‘go’,‘do’]


2. 不同的零宽断言

零宽断言分为四种:正预测先行断言、正回顾后发断言、负预测先行断言、负回顾后发断言,不同的断言匹配的位置不同。


总结一下,这几个仿佛说的不是"人话"的令人费解的名词可以这样理解:其中的“正”指的是肯定预测,即某个位置满足某个正则,而与之对应的“负”则指的是否定预测,即某个位置不要满足某个正则;其中的“预测先行”则指的是“往后看”,“先往后走”的意思,即这个位置是出现在某一个字符串后面的,而与之相反的“回顾后发”则指的是相反的意思:“往前看”,即匹配的这个位置是出现在某个字符串的前面的。


1) 正预测先行断言:(?=exp)

匹配一个位置(但结果不包含此位置)之前的文本内容,这个位置满足正则exp,举例:匹配出字符串s中以ing结尾的单词的前半部分:


s = "I'm singing while you're dancing."
p = re.compile(r'\b\w+(?=ing\b)')

print '【Output】'
print re.findall(p,s)
【Output】
['sing', 'danc']


2) 正回顾后发断言:(?<=exp)

匹配一个位置(但结果不包含此位置)之后的文本,这个位置满足正则exp,举例:匹配出字符串s中以do开头的单词的后半部分:

s = "doing done do todo"
p = re.compile(r'(?<=\bdo)\w+\b')

print '【Output】'
print re.findall(p,s)
【Output】
['ing', 'ne']


3) 负预测先行断言:(?!exp)

匹配一个位置(但结果不包含此位置)之前的文本,此位置不能满足正则exp,举例:匹配出字符串s中不以ing结尾的单词的前半部分:


s = 'done run going'
p = re.compile(r'\b\w+(?!ing\b)')

print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']

可见,出问题了,这不是我们预期的结果(预期的结果是:done和run),这是因为负向断言不支持匹配不定长的表达式,将p改一下再匹配:


s = 'done run going'
p = re.compile(r'\b\w{2}(?!ing\b)')

print '【Output】'
print re.findall(p,s)
【Output】
['do', 'ru']


可见一次只能匹配出固定长度的不以ing结尾的单词,没有完全达到预期。这个问题还有待解决。


4) 负回顾后发断言:(?<!exp)

匹配一个位置(但结果不包含此位置)之后的文本,这个位置不能满足正则exp,举例:匹配字符串s中不以do开头的单词:

s = 'done run going'
p = re.compile(r'(?<!\bdo)\w+\b')

print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']

可见也存在与负预测先行断言相同的问题,改一下:


s = 'done run going'
p = re.compile(r'(?<!\bdo)\w{2}\b')

print '【Output】'
print re.findall(p,s)
【Output】
['un', 'ng']

5) 正向零宽断言的结合使用

举例:字符串ip是一个ip地址,现在要匹配出其中的四个整数:


ip = '160.158.0.77'
p = re.compile(r'(?<=\.)?\d+(?=\.)?')

print '【Output】'
print re.findall(p,ip)
【Output】
['160', '158', '0', '77']

6) 负向零宽断言的结合使用

举例:匹配字符串s中的一些单词,这些单词不以’x’开头且不以’y’结尾:


s = 'xaay xbbc accd'
p = re.compile(r'(?<!\bx)\w+(?!y\b)')

print '【Output】'
print re.findall(p,s)
【Output】
['xaay', 'xbbc', 'accd']

可见这里因为负向断言不支持不定长表达式,所以也存在和前面相同的问题。


3、零宽断言的应用

1) 匹配html标签之间的内容

s = '<span>Hello world!</span>'
p = re.compile(r'(?<=<(?:\w+)>(.*)(?=</\1>))')

print '【Output】'
print re.findall(p,s)
# 报错:error: look-behind requires fixed-width pattern

上面的报错是因为零宽断言的正则中不能含有不定长的表达式,改一下:


s = '<span>Hello world!</span>'
p = re.compile(r'(?<=<(\w{4})>)(.*)(?=</\1>)')

print '【Output】'
print re.findall(p,s)
【Output】
[('span', 'Hello world!')]

2) 匹配存在多种规则约束(含否定规则)的字符串

匹配一个长度为4个字符的字符串,该字符串只能由数字、字母或下划线3种字符组成,且必须包含其中的至少两种字符,且不能以下划线或数字开头:

# 测试数据
strs = ['_aaa','1aaa','aaaa','a_12','a1','a_123','1234','____']
p = re.compile(r'^(?!_)(?!\d)(?!\d+$)(?![a-zA-Z]+$)\w{4}$')

print '【Output】'
for s in strs:
    print re.findall(p,s)
【Output】
[]
[]
[]
['a_12']
[]
[]
[]
[]

3) 注意点

零宽断言虽然也是用小括号括起来的,但不占用分组的默认命名空间。举例如下:


s = 'goingxxx'
# 在紧跟'ing'后面的字符串前加上'AAA'
print re.sub(r'(?<=ing)(\w+)\b',r'AAA\1',s)
# 输出: goingAAAxxx

正则表达式 ^(?=.[A-Z])(?=.[^A-Za-z0-9]).{6,12}$ 这个是什么意思啊?

(?=xxx) 是零宽断言,表示后面的字符串必须符合 xxx 这个正则表达式,但是不消耗字符串,实际匹配字符串的正则是 .{6,12} 即 6 到 12 位字符

(?=.*[A-Z])

(?=.[A-Z]) 表示后面必须符号 .[A-Z] 这个 ,即必须有大写字母

整个正则表达式表示 6 到 12 位字符,必须有大写字母和不是字母数字的字符


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
1Hh2sdYQZd4C