番外篇之正则
1. 基本概念
- re模块本身只是用来操作正则表达式的和正则本身无关
- 正则表达式:是一种匹配字符串的规则
- 为什么要有正则:应用场景
- 匹配字符串
- 表单验证:11位,全数字,1开头,第二个数 3-9,绑定银行卡
- 爬虫:从网页源码中获取链接,重要数据
2. 规则
2.1 元字符
- 是哪个一字符就匹配字符串中的哪一个字符
- 字符组(3)
- [ad] ,匹配a/d,单字符匹配
- [0-9], [a-z], [A-Z] (范围是从小到大),遵循ASCII码
- [a-zA-Z], [0-9x]
- 转义字符(7 )
- [0-9] 等价于 \d (\转义符,转义d使得其匹配0-9之间的数)
- \w:(word,数字,大小写字母,下划线)
- \s:(space, 空格,换行,制表符) (\t(table) \n(next))
- \D \W \S(对以上结果取反)
- \b:匹配\w和\W之间,即匹配单词边界匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
- 特殊符号的含义(4)
- . 除了换行符之外的任意内容
- [\d] [0-9] \d 没有区别。 [\d\D] 匹配所有
- [^abc]:非字符组,[abc] 取反
- ^:表示一个字符的开始。 $:表示一个字符的结束 (^abc$)
- | 和()eg. abc|edf。 abc|ab
# 若果规则有重叠,需要长的在前面
www.(baidu|google).com
# () 表示分组,给一部分正则规定为一组,
2.2 量词(6)
1[3-9]\d{9} # 量词前面一个重复次数,9次
1[3-9]\d{9,} # 量词前面一个重复次数,9次以上
1[3-9]\d{n,m} # 量词前面一个重复次数,n-m次
? # ? 匹配到0次或1次,没匹配上也算一次,匹配上算2次
#(可有可无,只能有一个)
+ # + 匹配1次或多次
* # * 匹配0次或多次
# 匹配任意小数,保留两位
\d+\.\d{2}
# 匹配任意整数或小数
\d+\.?\d* # 有bug
\d+(\.\d+)? # 分组实现
2.3 贪婪匹配/惰性匹配
\d{7-12} # 默认是贪婪匹配,尽量多匹配
# 回溯算法
# 非贪婪匹配,惰性匹配,总是匹配符合条件范围内尽量小的字符串
\d{2,3}? # 匹配两位数
\d+?3 # 尽量多取,遇到3结束
元字符 量词 ?x # 按照元字符规则在量词范围内匹配,一旦遇到x停止
.*?x # 常用,先找x找到匹配结束
# 身份证号匹配(正则表达式,断言)
[1-9](\d{16}[\dx]|\d{14})
[1-9]\d{14}(\d{2}[\dx])
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$
. 是任意字符
* 是取 0 至 无限长度
? 是非贪婪模式。
.*?x # 就是取前面任意长度的字符,直到一个x出现
2.4 示例
# 匹配邮箱
\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}
# url
^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+
3. re模块
3.1 compile()
-
编译正则表达式模式,返回一个对象的模式。(可以把那些常用的正则表达式编译成正则表达式对象,这样可以提高一点效率。)
-
格式:re.compile(pattern,flags=0),pattern: 编译时用的表达式字符串。flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等。常用的flags有:
标志 | 含义 |
---|---|
re.S(DOTALL) | 使匹配包括换行在内的所有字符 |
re.I(IGNORECASE) | 使匹配对大小写不敏感 |
re.L(LOCALE) | 做本地化识别(locale-aware)匹配,法语等 |
re.M (MULTILINE) | 多行匹配,影响^和$ |
re.X (VERBOSE) | 该标志通过给予更灵活的格式以便将正则表达式写得更易于理解 |
re.U | 根据Unicode字符集解析字符,这个标志影响\w,\W,\b,\B |
import re
tt = "Tina is a good girl, she is cool, clever, and so on..."
rr = re.compile(r'\w*oo\w*')
print(rr.findall(tt))
# 查找所有包含'oo'的单词
执行结果如下:
['good', 'cool']
3.2 re.match()
格式:re.match(pattern, string, flags=0)
-
在search前的正则前加了一个:^
-
想要完全匹配,可以在表达式末尾加上边界匹配符$,没有匹配到,则返回 None
# 从字符串开头匹配,匹配上则返回一个match对像,有group()方法
import re
ret = re.match('\d', '8alex83')
print(ret)
3.3 search()
格式:re.search(pattern, string, flags=0)
- re.search只要找到第一个匹配然后返回,如果字符串没有匹配,则返回None。
print(re.search('\dcom','www.4comrunoob.5com').group())
# 执行结果如下:4com
注:match和search一旦匹配成功,就是一个match object对象,而match object对象有以下方法:
- start() 返回匹配开始的位置
- end() 返回匹配结束的位置
- span() 返回一个元组包含匹配 (开始,结束) 的位置
- group() 返回re整体匹配的字符串,可以一次输入多个组号,对应组号匹配的字符串。
a. group()返回re整体匹配的字符串,
b. group (n,m) 返回组号为n,m所匹配的字符串,如果组号不存在,则返回indexError异常
c.groups()groups() 方法返回一个包含正则表达式中所有小组字符串的元组,从 1 到所含的小组号,通常groups()不需要参数,返回一个元组,元组中的元就是正则表达式中定义的组。
import re
a = "123abc456"
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0)) #123abc456,返回整体
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1)) #123
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2)) #abc
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3)) #456
###group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。###
3.4 findall()
- 格式:re.findall(pattern, string, flags=0)
- 可以获取字符串中所有匹配的字符串,返回一个列表,没有匹配则为空。
p = re.compile(r'\d+')
print(p.findall('o1n2m3k4'))
执行结果如下:
['1', '2', '3', '4']
import re
tt = "Tina is a good girl, she is cool, clever, and so on..."
rr = re.compile(r'\w*oo\w*')
print(rr.findall(tt))
print(re.findall(r'(\w)*oo(\w)',tt)) # ()表示子表达式
执行结果如下:
['good', 'cool']
[('g', 'd'), ('c', 'l')]
3.5 finditer()
格式:re.finditer(pattern, string, flags=0)
- 搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。
# 匹配到结果为 迭代器,每一项都是match对象,通过group取值
import re
ret = re.finditer('\d', 'safh123ghakjdsfg234'*2000000)
for i in ret:
print(i.group())
3.6 split()
格式:re.split(pattern, string[, maxsplit],maxsplit用于指定最大分割次数,不指定将全部分割。
- 按照能够匹配的子串将string分割后返回列表。
- 可以使用re.split来分割字符串,如:re.split(r'\s+', text);将字符串按空格分割成一个单词列表。
import re
ret = re.split('\d+', 'henry18')
print(ret)
# 保留分组中内容
ret = re.split('(\d+)', 'henry18')
print(ret)
3.7 sub()/ subn()
格式:re.sub(pattern, repl, string, count=0)
格式:subn(pattern, repl, string, count=0, flags=0)
- 不返回/返回替换次数
import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
print(re.sub(r'\s+', lambda m:'['+m.group(0)+']', text,0)) # flags=0默认参数
执行结果如下:
JGood[ ]is[ ]a[ ]handsome[ ]boy,[ ]he[ ]is[ ]cool,[ ]clever,[ ]and[ ]so[ ]on...
# 替换 n 次
ret = re.sub('\d', 'G', 'henry18',n)
print(ret)
# 返回替换次数(tuple类型)
ret = re.subn('\d', 'G', 'henry18')
print(ret) # 返回值为tuple类型
4. 分组
4.1 特殊分组用法
语法 | 含义 | 示例 | |
---|---|---|---|
(?P |
分组,除了原有的编号外再指定一个额外的别名 | (?P |
abcabc |
(?P=name) | 引用别名为 |
(?P |
1abc15abc5 |
<number> | 引用编号为 |
(\d)abc\1 | 1abc15abc5 |
(?<=….) | 以…开头,并不包括开头 | ||
(?<!….) | 不以…结尾,并不包括开头 |
Note(3)
- [^] 带有特殊意义的元字符到字符组中大部分会取消它特殊意义
- [()+*.]:取消特殊含义,恢复原本意义
- [-]:第一个或最后表示横杠,中间位置表示范围
4.2. group()
- 括号中默认为0,即取第0个分组
s = '<h1>wahaha</h1>'
ret = re.search('(\w+)>(.*?)</\w+>', s)
print(ret.group())
print(ret.group(1))
print(ret.group(2))
4.3 分组命名
- (?P
正则表达式) - name:不需要加引号,本身就是字符串
ret = re.search('<(?P<tag>\w+)>(?P<content>.*?)</\w+>', s)
print(ret.group('tag'))
print(ret.group('content'))
4.4 引用分组
- (?P=name)
s = '<h1>wahaha</h1>'
ret = re.search('(?P<tag>\w+)>.*?</(?P=tag)>', s)
print(ret.group())
s = '<h1>wahaha</h1>'
# \1 在python中有特殊含义
ret = re.search(r'(\w+)>.*?</\1>', s)
print(ret.group())
4.5 取消分组优先
- (?😃
# findall 遇到正则中的分组 优先 显示分组中的内容
import re
ret = re.findall('\d(\d)', 'henry18')
print(ret)
# 取消分组优先(?:正则表达式)
ret = re.findall('\d+(?:\.\d+)?', '1.234+2')
print(ret)
4.6 split,保留分割符
- ()
# 保留分组中内容
ret = re.split('(\d+)', 'henry18')
print(ret)
5. 练习
# 示例1:匹配单个数字,findall方法会有屏蔽所有其他匹配项,只显示分组中内容
import re
ret = re.findall(r'\d+\.\d+|(\d)', '2+23*3.42/3.2')
print(ret)
while True:
if '' not in ret:break
ret.remove('')
print(ret)
# 示例2:匹配以...开头的数据,不包括开头
import re
m = re.findall('(?<=>)\w+', '\<a>wahaha\</a>\<b>banana\</b>\<h1>qqxing\</h1>')
for i in m:
print(i)
# 匹配不以...开头的数据,不包括结尾
m = re.findall('(?<!>)\w+', '\<a>wahaha\</a>\<b>banana\</b>\<h1>qqxing\</h1>')
print(m)
-
| :或只负责把两个表达式分开,如果是整个表达式中只对一部分内容进行或,需要分组
-
():限定一组正则的量词约束 (\d\w)?
# 示例3:以a开头,由至少一个字母组成的字
^a[a-zA-Z]+
^a[a-zA-Z]*
# 以1开头,中间3-5个数字,如果中间位置超过5个数字,则整个字符串不匹配
^1\d{3,5}$
# 示例4:匹配用户输入的身份证号
import re
content = input('用户输入:')
ret = re.match('[1-9]\d{14}(\d{2}[\dx])?$', content)
# 示例5:第一个乘除法
import re
ret = re.search('\d+(\.\d+)?[\*]-?\d+(\.\d+)?', s)
☞☞☞ 古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。 ☜ ☜ ☜