番外篇之正则

1. 基本概念

  1. re模块本身只是用来操作正则表达式的和正则本身无关
  2. 正则表达式:是一种匹配字符串的规则
  3. 为什么要有正则:应用场景
    • 匹配字符串
    • 表单验证:11位,全数字,1开头,第二个数 3-9,绑定银行卡
    • 爬虫:从网页源码中获取链接,重要数据

2. 规则

2.1 元字符

  • 是哪个一字符就匹配字符串中的哪一个字符
  1. 字符组(3)
    • [ad] ,匹配a/d,单字符匹配
    • [0-9], [a-z], [A-Z] (范围是从小到大),遵循ASCII码
    • [a-zA-Z], [0-9x]
  2. 转义字符(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'。
  3. 特殊符号的含义(4)
    • . 除了换行符之外的任意内容
    • [\d] [0-9] \d 没有区别。 [\d\D] 匹配所有
    • [^abc]:非字符组,[abc] 取反
    • ^:表示一个字符的开始。 $:表示一个字符的结束 (^abc$)
  4. | 和()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)

格式: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) 分组,除了原有的编号外再指定一个额外的别名 (?Pabc) abcabc
(?P=name) 引用别名为的分组匹配到字符串 (?P\d)abc(?P=id) 1abc15abc5
<number> 引用编号为的分组匹配到字符串 (\d)abc\1 1abc15abc5
(?<=….) 以…开头,并不包括开头
(?<!….) 不以…结尾,并不包括开头

Note(3)

  1. [^] 带有特殊意义的元字符到字符组中大部分会取消它特殊意义
  2. [()+*.]:取消特殊含义,恢复原本意义
  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)
posted @ 2019-10-15 22:00  RunningForever  阅读(142)  评论(0编辑  收藏  举报