Loading

正则表达式与re模块

一、正则表达式

什么是正则表达式

  • 一套规则———匹配字符串的

能做什么

  1. 检测一个输入的字符串是否合法——web开发项目 表单验证
    • 用户输入一个内容的时候,我们要提前做检测
    • 能够提高程序的效率并且减轻服务器的压力
  2. 从一个大文件中找到所有符合规则的内容——日志分析或爬虫
    • 能够高效的从一大段文字中快速找到符合规则的内容

二、正则规则

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

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

  • 接受范围,可以描述多个范围,连着写就可以了
  • [abc]:一个中括号只表示一个字符位置
    • 匹配a或b或者c
  • [0-9]:根据ASCII进行范围的比对
  • [a-z]:匹配a到z范围内的所有小写字母
  • [A-Z]:匹配A到Z范围内的所有大写字母
  • [a-zA-z]:匹配所有大小写字母
  • [0-9a-z]:匹配0到9和所有的小写字母
  • [0-9a-zA-Z_]:匹配所有数字大小写字母和下划线

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

  • [0-9] ———> \d:表示匹配一位任意数字(digit)
  • [0-9a-zA-Z_]———> \w:表示匹配数字字母下划线(word)
  • 空格 ———> 就是空格
  • tab ———> \t:匹配tab空白
  • enter回车 ———> \n:匹配回车
  • 空格,tab和回车———> \s:表示匹配所有空白包括空格tab和回车

元字符——匹配内容的规则

  • []:——匹配字符组内的一个字符

  • [^]:——除了字符组内的内容都匹配

  • \d:——匹配所有的数字

  • \D 和 [^\d]:——匹配所有非数字(大写D)

  • \w:——匹配所有的字母

  • \W 和 [^\w]:——匹配所有的非字母(大写W)

  • \s:——匹配所有的空白

  • \S:——匹配所有的非空白(大写S)

  • [\d\D]、[\w\W]、[\s\S]:——表示匹配所有

  • . ——匹配除了执行换行符之外的所有

  • ^:——匹配一个字符串的开始比如(^\d)匹配以数字开头的

  • $:——匹配一个字符串的结尾比如(\d$)匹配以数字结尾的

  • a表达式|b表达式:——匹配a或者b表达式中的内容,如果a匹配成功了,不会继续和b匹配,所以,如果两个规则有重叠部分,总是把长的放在前面

  • ():——分组,约束或描述的内容的范围问题

    • 匹配 www.baidu.com 或 www.oldboy.com 或 www.jd.com
      方法一:www\.oldboy\.com|www\.baidu\.com|www\.jd\.com
      方法二:www\.(oldboy|baidu|jd)\.com
      

三、量词

  • {n}:——表示匹配n次
  • {n,}:——表示匹配至少n次
  • {n,m}:——表示至少匹配n次,最多匹配m次
  • ?:——表示匹配 0 次或 1 次
  • +:——表示 1 次或多次
  • *:——表示 0 次或多次

匹配0次:

  • 整数:\d+
  • 小数:\d+\.\d+
  • 整数或小数:\d+\.?\d*(23423.)也会匹配上
  • 分组的作用匹配小数或整数:\d+(\.d+)?

手机号码:

  • 1 3-9 11位
    • 1[3-9]\d{9}
    • 第一位是1开头,第二位是3-9范围的任意数字后面是9位数字

判断用户输入的内容是否合法,如果用户输入的对就能查到结果,如果输入的不对就不能查到结果

  • 加上首尾匹配元字符:就会固定匹配一个满足条件的字符
    • ^1[3-9]\d{9}$

贪婪匹配:

  • 在量词允许的情况下,尽量多的匹配内容
  • .*x:表示匹配任意字符,任意多次数,遇到最后一个x才停下来

非贪婪(惰性)匹配

  • .*?x:表示匹配任意字符,任意多次数,但是一旦遇到x就停下来
  • 在量词后加?号就会变成惰性匹配

转义符

  • 原本有特殊意义的字符,到了表达它本身的意义的时候,需要转义
  • 有一些有特殊意义的内容,放在字符组中,会取消它的特殊意义
    • [().*+?]——所有的内容放在字符组中会取消它的特殊意义
    • [a\-c]:——在字符组中 - 表示范围,如果不希望它表示范围,需要转义,或者放在字符组的最前面或最后面

四、re模块

  • re.findall ('表达式','需要匹配的内容') :

    • 以字典的形式返回匹配到的所有字符串
    import re
    ret = re.findall('\d+','123abc456def')
    print(ret)
    # 输出
    ['123', '456']
    
    • findall:遇到分组的时候,会按照完整的正则进行匹配,只是显示括号里匹配到的内容
    • 取所有符合条件的,优先显示分组中的
    ret = re.findall('2(\d)\d','1234abcd4267efgh')
    print(ret)		# 只会显示匹配到234和267的第二个字符
    # 输出
    ['3', '6']
    
  • re.search('表达式','需要匹配的内容'):

    • 匹配第一个满足条件到的字符返回的是一个变量,只能用group()查看。
    ret = re.search('\d+','123abc456def')
    print(ret)  # 返回的是变量
    if ret:		# 匹配到了才显示,没匹配到返回 None,所以做了个判断
        print(ret.group())
    # 输出
    <re.Match object; span=(0, 3), match='123'>
    123
    
    • search:还是按照完整的正则进行匹配,显示也显示匹配到的第一个内容,但我们可以通过给group方法传参数来获取具体元组的内容
    • 只取第一个符合条件的,没有优先显示这件事儿
    • 得到的结果是一个变量:
      • 变量 .group() 的结果 完全和 变量 .group(0) 的结果一致
      • 变量 .group(n) 的形式来指定获取第 n 个分组中匹配到的内容
    ret = re.search('2(\d)(\d)','1234abcd4267efgh')
    print(ret)  # 变量
    if ret:
        print(ret.group())
        print(ret.group(1))
        print(ret.group(2))
    # 输出
    <re.Match object; span=(1, 4), match='234'>
    234
    3
    4
    
  • 为什么在search中不需要分组优先,而在findall中需要?

    • 在 findall中加上分组括号,是为了对真正需要的内容进行提取
    ret = re.findall('<\w+>(\w+)</\w+>','<h1>abcd12345678efgh</h1>')
    print(ret)
    # 输出
    ['abcd12345678efgh']
    
    • 在search中
    ret = re.search('<(\w+)>(\w+)</\w+>','<h1>abcd123678efgh</h1>')
    print(ret.group())   # 获取所有匹配到的内容
    print(ret.group(1))  # 获取第一个分组的内容
    print(ret.group(2))  # 获取第二个分组的内容
    # 输出
    <h1>abcd12345678efgh</h1>
    h1
    abcd12345678efgh
    
  • 为什么要用分组,以及 findall 的分组优先到底有什么好处

    • 使用findall分组优先可以把想要的内容放在分组里面
    • 使用search分组就可以在匹配完了之后对自己想要操作的分组进行操作
    exp = '2-3*(5+6)'
    ret = re.search('(\d+)[+](\d+)',exp)
    print(ret) 			# 获取a+b或a-b,并且计算他们的结果。
    print(ret.group(1))	
    print(ret.group(2))
    print(int(ret.group(1)) + int(ret.group(2)))
    
  • 如何取消分组优先:

    • 如果在写正则的时候由于不得已的原因,导致不要的内容也写在分组里
  • (?:)——取消这个分组的优先显示

    ret = re.findall('<(?:\w+)>(\w+)</\w+>','<h1>abcd12345678efgh</h1>')
    print(ret)
    # 输出
    ['abcd12345678efgh']
    
  • split:以匹配的字符为截短(加上分组可以显示分组内容)

    import re
    ret = re.split('\d+','alex222wusir')
    print(ret)
    # 输出
    ['alex', 'wusir']
    ----------------------------------------
    import re
    # 加上分组会显示分组的内容
    ret = re.split('\d(\d)\d', 'alex123wusir')
    print(ret)
    # 输出
    ['alex', '2', 'wusir']
    
  • sub('匹配需要替换内容表达式','替换的新内容',替换次数):替换匹配到的内容

    import re
    ret = re.sub('\d+','HH','aa33bb44cc')
    print(ret)
    # 输出
    aaHHbbHHcc
    --------------------------------------
    import re
    ret = re.sub('\d+','HH','aa33bb44cc',1) # 替换1次
    print(ret)
    # 输出
    aaHHbb44cc
    
  • subn:和sub一样,不过以元组的形式返回替换之后的的内容和替换的次数

    import re
    ret = re.subn('\d+','H','aa33bb44cc')
    print(ret)
    # 输出
    ('aaHbbHcc', 2)
    
  • match:在表达式之前默认加了个 ^ 元字符(匹配以什么开头)

    import re
    # 表达式相当于='^\d+'匹配任意一个或多个数字开头的字符
    ret = re.match('\d+','1234abcd567')  
    print(ret.group())
    # 输出
    1234
    
  • compile:节省代码时间的工具,没有重复使用同一个正则,也不能节省时间。

    • 假如同一个正则表达式要被多次使用
    • 节省了多次解析同一个正则表达式的时间
    import re
    ret = re.compile('\d+')
    res1 = ret.search('abcd1234')
    res2 = ret.findall('abcd1234')
    print(res1)
    print(res2)
    # 输出
    <re.Match object; span=(4, 8), match='1234'>
    ['1234']
    
  • finditer:返回的是一个迭代器,为了节省空间

    import re
    ret = re.finditer('\d+','agks1ak018093')
    print(ret)				# 是一个迭代器
    for i in ret:			# 把匹配出来的结果循环出来,节省了空间的占用
        print(i.group())
    # 输出
    <callable_iterator object at 0x00000204E6721E20>
    1
    018093
    

五、分组命名

  • 标准格式

    (?P<组名>正则表达式)
    
  • 有的时候我们要匹配的内容是包含在不想要的内容之中的

    • 只能先把不想要的内容匹配出来,然后再想办法从结果中去掉
import re
exp = '2014-9-23和1999-04-30'
ret = re.search(r"[1-9]\d{3}-(?P<month>[1][0-2]|0?[1-9])-(?P<date>[[1-2][0-9]|[3][01]|0?[1-9])", exp)

print(ret.group())
print(ret.group('month'))
print(ret.group('date'))
# 输出
2014-9-23
9
23

posted @ 2021-04-28 11:14  Mr-Yang`  阅读(123)  评论(0编辑  收藏  举报