正则匹配与Re模块

本篇介绍正则匹配规则与如何在python中利用Re模块进行正则匹配。

概念

re模块和正则表达式的关系

  • 正则表达式 和 re 模块没有关系

  • 有了re模块就可以在python语言中操作正则表达式

  • re 模块本身不提供规则,它只是能让python可以使用正则表达式

什么是正则表达式?

一套规则,用来匹配字符串

能做什么?

  • 检测一个输入的字符串是否合法(场景1)(web开发项目 表单验证)

    用户输入一个内容的时候,我们要提前做检测,这样能提高程序的效率和减少服务器的压力
  • 从一个大文件当中找到所有符合规则的内容(场景2)(日志分析,爬虫)
    能够高效的从一大段文字中快速找到符合规则的内容。

这两个场景的差别在于:

  • 场景一,我们常用^ $ 限制字符串为想要的内容;场景二,要尽可能匹配符合规则地字符串。

正则规则

所有的规则中的字符就可以刚好匹配到字符串的内容

正则规则-第一部分

字符组[]

字符组:描述的是一个位置上能出现的所有可能性

接受范围:可以描述多个范围,连着写就行

  • [abc] 一个中括号只表示一个字符位置

  • 意思是匹配a或b或c

  • [匹配一个数字]

    • [0-9] 根据ASCII码进行范围的比对
    • (0的ascii到9的ascii之间的所有;这个范围只能从小到大) 如[0-7]可以 但[7-0]不行
    • [a-zA-z] 表示大小写
    • 记忆:
    1
    2
    A 65 - Z 90      a 97 - z 122
    中文的 unicode 编码范围 主要在 [\u4e00-\u9fa5]

元字符

元字符:在正则表达式中能够帮助我们表示匹配的内容的符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
铺垫:
[0-9] --> \d 表示匹配一位任意数字 digit
[0-9a-zA-Z_] --> \w 匹配所有的数字字母下划线 word
空白符(空格 \tab \enter)--> 空格|\t|\n ---> \s 表示所有空白包括空格,tab和回车

元字符: -- 匹配内容的规则
[] [^]
\d 任意数字
\w 数字字母下划线
\s 空白符
\t
\n

取反
\W # 非数字字母下划线
\D # 只要不是数字就能匹配 非数字
\S # 只要不是空白符就能匹配 非空白


[\d\D] [\w\W] [\s\W] 表示能够匹配所有 近似---> .
. 表示匹配除了换行符之外的所有的一个字符, 但是可以设置.匹配所有
加上re.DOTALL 这样就可以把换行也可以进行匹配。 re.findall(pat,st,re.DOTALL)

[^] 非字符组 必须放首位 (不在这个字符组中的都可以匹配)
[^\d] 匹配所有的非数字
[^1]


^ 匹配一个字符串的开始 (一定放开头(在字符组中有些不同(表示非字符组)))
$ 匹配一个字符串的结尾 (一定放结尾)
送命题: ^a.$ 匹配abacad中的什么?
^ 和 $ 约束了字符串的开头、结尾以及长度。 圈死了字符串的内容
a表达式| b表达式 匹配a或者b表达式中的内容,如果a匹配成功,不会继续向右
所以,如果两个规则有重叠部分,总会把长的放在前面。
小坑 ab|abc 正则是从左到右,如果先遇到ab 就不会对后面的进行检测
abc|ab
() 约束元字符的作用域 约束 | 描述的内容的范围
www\.oldboy\.com|www\.baidu\.com|www\.taobao\.com
---> www\.(taobao|baidu|oldboy)\.com
补充 :
\b 对于边界的处理 ing\b 匹配ing结尾地词

小结:记忆元字符: 都是表示能匹配哪些内容,一个元字符总是表示一个字符位置上的内容。
\d \w \s \t \n \D \W \S
[] [^] .
^ $
| ()

例子: 两位整数
[1-9]\d
字符组是最细腻的。 约束更强

注意:

  • 如果匹配成功:光标移动到匹配成功的最后一个字符
  • 如果匹配失败,就从匹配开始字符的下一个字符开始
  • 例子
1
2
print(re.findall('a.b','aaab'))   # ['aab']
print(re.findall('a.b','aabbb')) # ['aab']

量词

量词 :必须跟在元字符后面,只能约束前面元字符的出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{n} 表示匹配n次
{n,} 表示匹配至少n次
{n,m} 表示至少匹配n次,至多m次

? 表示匹配0次或1次: 要么出现要么不出现
+ 表示1次或多次 : 至少得出现一次
* 表示0次或多次: 任意次
理解: 从坐标轴上 ?是(01) + 是(1,正无穷) * 是所有
{0,1} {1,} {0,}
匹配0次: 0表示可不出现
例 匹配任意整数: \d+
例 匹配小数 \d+\.\d+
整数或小数: \d+\.?\d* 这个有个漏洞:123.
分组的作用: \d+(\.\d+)?
疑问:为什么\d+(\.\d*) 不能用? 它只能匹配小数,整数匹配不了
1
2
3
4
5
6
7
8
9
10
11
12
练习: 匹配手机号码
1 3-9 11
1[3-9]\d{9}
判断用户输入的内容是否合法,如果输入的对就能查到结果,如果输入的不对就不能查到结果
我们需要限制开头结尾了
^1[3-9]\d{9}$
场景一小结: 约束用户输入必须和规定的一样
从一个大文件中找到所有符合规则的内容
1[3-9]\d{9}
场景二小结:匹配符合规则的所有内容

场景一二的区别在于:是否限制开头结尾

贪婪匹配

1
2
3
4
5
6
7
8
9
10
11
 贪婪匹配    ---> 往右走是贪心算法,往左返回是回溯算法
\d{3,9} 为什么匹配9
在量词范围允许的情况下,尽可能地匹配多的。
\d{3,9}6

非贪婪(或惰性)匹配
\d{3,9}?6 在量词后面加?就变成非贪婪了
元字符 量词 ? (注意与元字符 ?区分) (元字符??x 表示直接匹配x)
1\d?3 1\d??3 133
.*?x 表示匹配任意字符,任意多次数,但是一旦遇到x就停下来。
.*x 表示匹配任意字符,任意多次数,遇到最后一个x就停下来。

转义符

1
2
3
4
5
6
7
原本有特殊意义地字符,到了表达她本身地意义地时候,需要转义
有一些有特殊意义的内容,放在字符组中,会取消它地特殊意义。
[.]
[().*+?]
[-c][a-] # 只表示减号
[a\-c] - 在字符组中表示范围,如果不希望它表示范围,需要转义,或者放在最前面或最后面
只有\- 是表示减号本身。

小结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 元字符  表示匹配内容地符号
\系列
[] [^] .
^ $
() | 量词约束范围或整体范围

# 量词
{} 表示任意的次数,任意的次数范围,至多至少多少次。
? + *

# 贪婪和非贪婪匹配
总是在量词范围内多匹配——贪婪
总是在量词范围内少匹配——惰性
.*?x 匹配任意内容任意次数,遇到x停止
.+?x 匹配任意内容至少一次,遇到x停止

# 转义符问题
.有特殊地意义,取消特殊的意义:\.
取消一个元字符地特殊意义有两种方法:
在这个元字符前加\
对一部分字符生效,把这个元字符放在字符组里
[.()+?*] 但一定要注意 -

练习

1
2
3
4
5
6
7
8
9
10
11
12
匹配18/15位地身份证号
#15
# 1-9
# [1-9]\d{14}
# 18
# 1-9
# 16个数字
# 0-9/x
# [1-9]\d{16}[0-9x]
# 小坑: ^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ 或 ^([1-9]\d{16}[0-9x]$|^[1-9]\d{14})$

# 法2: ^[1-9]\d{14}(\d{2}[\dx])?$

Re模块

1
2
3
4
5
import re
ret = re.findall('\d+','sfkoidf3423434')
# 所有都匹配出来,返回列表
print(ret)
ret2 = re.search('\d+1','sfkoidf3423434')

findall

以列表的形式返回匹配成功的文本

如果我们要查找的内容在一个复杂的环境中,我们要查的内容并没有一个突出的,与众不同的特点,甚至会和不需要的杂乱数据混合在一起,这个时候我们就需要把所有的数据都统计出来,然后对这个数据进行筛选,把我们真正需要的数据对应的正则表达式用()圈起来,这样我们就可以筛选出来想要的东西。(?:可以不让它优先显示)

1
2
3
4
5
6
7
>>> import re
>>> re.findall('a', 'a') # ['a']
>>> re.findall('a', 'aba') # ['a', 'a']
>>> re.findall('.', 'aba') # ['aba']
>>> re.findall('a{3}', 'aaaa') #['aaa']
>>> re.findall('\d\D', '1aa') # ['1a']
>>> re.findall('^a', 'ba') # []

search 只取第一个符合条件的,没有优先显示这件事儿

  • 还是按照完整的正则进行匹配,显示匹配到第一个内容,但是我们可以通过group方法传参数
  • 变量.group()的结果 完全和 变量.group(0)的结果一致
1
2
3
4
5
6
7
8
9
>>> r = re.match('a', 'ba')
>>> r is None
True

>>> r = re.search('a', 'ba')
>>> r
<_sre.SRE_Match object; span=(1, 2), match='a'>

>>> r = re.search('^a', 'ba') # ^ 开头匹配 | 这时 match 和 search 就一样了

match

相当于在\d+ 前面人为的加上^ , 除此之外和search一摸一样。

1
2
3
4
ret4 = re.match('\d+','小白123小黑456')
print(ret4) # None
ret5 = re.match('\d+','1小白123小黑456')
print(ret5) # <_sre.SRE_Match object; span=(0, 1), match='1'>

没有match也可以,是用search 正则中’^\d+’

应用场景: 用户输入的内容匹配的时候,要求用户输入11位手机号码,^手机号码正则$

match与search的态度/思路不同:

  • match 用来规定这个字符串必须是怎么样的。
  • search 用来寻找这个字符串中符合规则的子串。

group

match/search返回的正则对象提取结果

1
2
3
4
5
>>> re.match('a(b)','ab').group() # () 括号是特别的标示,不会影响正则匹配
'ab'

>>> re.match('a(b)','ab').groups() # .groups() 可以只提取用括号标示的内容,这招非常有用!
'b'

split

切割字符串,返回数组

1
2
3
4
ret = re.split('\d+','小白222xiaohong')
print(ret) # ['小白', 'xiaohong']
ret2 = re.split('(\d+)','小白222xiaohong') # 使用分组可以保留下来这部分内容。
print(ret2) # ['小白', '222', 'xiaohong']

问题:字符串也有切割,但字符串不提供保留切掉的内容,从逻辑的角度想想为什么它会什么是这样?

sub

替换

1
2
3
ret3 = re.sub('\d+','H','小白123xiaohong456',1)
# 指定替换的次数
print(ret3) # 小白Hxiaohong456

subn

1
2
3
ret3 = re.subn('\d+','H','小白123xiaohong456')     
# 返回的元组, 替换后的内容 和 次数
print(ret3) # ('小白HxiaohongH', 2)

解决效率问题

compile

compile —— 节省代码时间的工具

背景:当我们重复使用一个正则表达式的时候,我们可以利用compile对正则表达式进行编译。使用compile后,节省了多次解析同一个正则表达式的时间。

1
2
3
4
5
reg = re.compile('\d+')
res1 = reg.search('2343sfdsdf')
res2 = reg.findall('2343sfdsdf')
print(res1) # <_sre.SRE_Match object; span=(0, 4), match='2343'>
print(res2) # ['2343']

finditer

finditer —— 节省空间

背景:从大文件中找子串,结果特别多。这样找到的结果越多越占用内存 所以我们把返回的列表改成一个迭代器

1
2
3
4
ret = re.finditer('\d+','fosaidj234ij')
for i in ret:
print(i) # 返回对象
print(i.group()) # 通过group方法来取值

compilefinditer 组合使用

1
2
3
4
reg = re.compile('\d+')
res = reg.finditer('fosaidj234ij')
for r in res:
print(r.group())

分组命名

分组命名 (?P<名字>正则) ret.group('名字')

分组命名的引用 (?P=名字)

1
2
3
4
5
6
import re
ret = re.search('\d(\d)\d(\w+?)(\d)(\w)\d(\d)\d(\w+?)(\d)(\w)\d(\d)\d(?P<name>\w+?)(\d)(\w)','123abc45678agsf_123abc45678agsf_123abc45678agsf_')
print(ret.group(10)) # agsf_ 索引太过繁琐,所以产生命名
print(ret.group('name')) # agsf_

print(re.search('<(?P<tag>\w+)>.*?</(?P=tag)>','<abc>sdiofjosafdgj</abc>'))

分组的索引去引用

1
2
print(re.search(r'<(\w+)>.*?</\1>','<abc>sdiofjosaj</abc>'))
print([r'\1']) # \1是有意义的, 加上r 就会取消特殊的意义

断言

待补充!!!

结论

  • findallsearch 可以解决80%的问题。

  • 有的时候要匹配的内容是包含在不想要的内容之中的,这种情况先把不想要的匹配匹配出来,然后再想办法从结果中去掉。

须知

-------------The End-------------

本文标题:正则匹配与Re模块

文章作者:Naqin

发布时间:2019年04月07日 - 20:04

最后更新:2020年02月25日 - 20:02

原始链接:https://chennq.top/learn-python/20190407-Regular_Expression_and_Python_Re_module.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Naqin wechat
欢迎看官加我微信!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%