Skip to content

re

Python的内置库——正则表达式

正则表达式是用来 处理字符串 的,重在处理 规则

常用正则

  • 年份匹配:^((19|20)\d{2})$
  • 手机号码:^1[3456789]\d{9}$
  • E-mail地址:^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$
  • 网址URL:http://(/[\w-]+\.)+[\w-]+(/[\w-./?%&=])

Note

re.findall(goal, source)

匹配成功: 返回一个列表,列表中有goal的字符串,找到n次就有n个.

匹配失败: 返回一个空列表

>>> # 普通字符正则的匹配
>>>
>>> import re
>>> print(re.findall('p', 'python'))
['p']
>>> print(re.findall('python', 'I like python'))
['python']
>>> print(re.findall('o', 'I love python'))
['o', 'o']
>>> print(re.findall('2', '1234567890abcdefg'))
['2']

预定义字符

\d\D\s\S\w\W

  • \d:匹配所有 数字0~9
  • \w:匹配包含下划线 字符 [a-z|A-Z|0-9|_]
  • \s:匹配 空白符、制表符、换行符 [\n \t]

  • \D:匹配所有 非数字^[0-9]
  • \W:匹配 非正常字符 ^[a-z|A-Z|0-9|_]
  • \S:匹配 非空白符、制表符、换行符 ^[\n \t]

  • .:除了换行符之外所有字符
>>> print(re.findall(r"\d", "1234657890abcdefg"))
['1', '2', '3', '4', '6', '5', '7', '8', '9', '0']
>>> print(re.findall(r"\D", "1234657890abcdefg"))
['a', 'b', 'c', 'd', 'e', 'f', 'g']

>>> print(re.findall(r"\w", "1234657890abcd_%$#efg"))
['1', '2', '3', '4', '6', '5', '7', '8', '9', '0', 'a', 'b', 'c', 'd', '_', 'e', 'f', 'g']
>>> print(re.findall(r"\W", "1234657890abcd_%$#efg"))
['%', '$', '#']

>>> print(re.findall(r"\s", "1234 657\n890ab\tcd_%$#efg"))
[' ', '\n', '\t']
>>> print(re.findall(r"\S", "1234 657\n890ab\tcd_%$#efg"))
['1', '2', '3', '4', '6', '5', '7', '8', '9', '0', 'a', 'b', 'c', 'd', '_', '%', '$', '#', 'e', 'f', 'g']

元字符

[]{n}^-

  • []:匹配一个字符,括号内是 或者 的关系
  • ^:取反 (和这些元字符或\d这些预定义字符一起用才是取反,否则和$一起做位置匹配)
  • -:区间
  • ():分组,匹配括号内的内容,不要括号两边的内容
# 区间
>>> # 匹配123任意单个字符
>>> print(re.findall(r"[123]", "1234 657\n890ab\tcd_%$#efg"))
['1', '2', '3']

>>> # 匹配单个数字(\d)或 空格换行制表符(\s)
>>> print(re.findall(r"[\d\s]", "1234 657\n890ab\tcd_%$#efg"))
['1', '2', '3', '4', ' ', '6', '5', '7', '\n', '8', '9', '0', '\t']

# 取反
>>> # 匹配单个非数字、非空格换行制表符
>>> print(re.findall(r"[^\d\s]", "1234 657\n890ab\tcd_%$#efg"))
['a', 'b', 'c', 'd', '_', '%', '$', '#', 'e', 'f', 'g']

# 范围
>>> # 匹配单个1-7之间的数字
>>> print(re.findall(r"[1-7]", "1234 657\n890ab\tcd_%$#efg"))

>>> # 匹配单个a-d之间的字母
>>> print(re.findall(r"[a-d]", "1234 657\n890ab\tcd_%$#efg"))

>>> # 匹配单个 1-7 或 a-d 的字符
>>> print(re.findall(r"[1-7a-d]", "1234 657\n890ab\tcd_%$#efg"))

# 分组
>>> # 匹配 左边a右边是a或b或c的两个字符
>>> print(re.findall(r"a[abc]", "aaabacad"))
['aa', 'ab', 'ac']

>>> # 第一步,匹配到['aa', 'ab', 'ac']
>>> # 第二步,不要括号两边的字符,也就是不要a,得到['a', 'b', 'c']
>>> print(re.findall(r"a([abc])", "aaabacad"))
['a', 'b', 'c']

重复匹配

重复匹配默认是贪婪匹配的,会尽量匹配多位

  • {n}:表示前面的字符重复n次才被匹配成功。a{3}就匹配出:aaa
  • {n,m}:表示前面的字符至少出现n次,至多出现m次,逗号左右不能有空格,贪婪匹配
  • {n,m}?:表示前面的字符至少出现n次,至多出现m次,逗号左右不能有空格 贪婪匹配
  • {n,}:表示前面的字符至少出现n次,至多出现无穷次

  • ?:相当于{0,1},表示前面的字符出现0次或1次
  • +:相当于{1,},表示前面的字符至少出现1次
  • *:相当于{0,},表示前面的字符出现0次或无穷次,贪婪匹配
  • +?:相当于{1,}?{1},表示前面的字符至少出现1次,非贪婪匹配
  • *?:相当于{0,}?{0},表示前面的字符至少出现0次,非贪婪匹配

  • \:转义符,在需要匹配上面?+*几个符号的时候需要加上斜杠\

# {n}

>>> # 贪婪匹配连续3位都是数字
>>> print(re.findall(r"\d{3}", "1234567890abcdefg"))
['123', '456', '789']

>>> # 贪婪匹配连续2位都是正常字符
>>> print(re.findall(r"\w{2}", "12a3456789b0abcdefg"))
['12', 'a3', '45', '67', '89', 'b0', 'ab', 'cd', 'ef']

>>> # 贪婪匹配至少2位至多4位的数字
>>> print(re.findall(r"\d{2,4}", "1234567890abcdefg"))
['1234', '5678']
>>> print(re.findall(r"\d{2,4}", "12a3456789b0abcdefg"))
['12', '3456', '789']

>>> # 非贪婪匹配至少2位至多4位的数字
>>> print(re.findall(r"\d{2,4}?", "12a3456789b0abcdefg"))
['12', '34', '56', '78']

>>> # 贪婪匹配出现至少2位的数字
>>> print(re.findall(r"\d{2,}", "12a3456789b0abcdefg165465165"))
['12', '3456789', '165465165']
# ? + *

>>> # 贪婪匹配0或1个数字,0个也在匹配范围内,所以匹配到非数字的时候是空字符串
>>> print(re.findall(r"\d?", "12a3456789b0abcde_%"))
['1', '2', '', '3', '4', '5', '6', '7', '8', '9', '', '0', '', '', '', '', '', '', '', '']

>>> # 贪婪匹配1或n个数字
>>> print(re.findall(r"\d+", "12a3456789b0abcde_%"))
['12', '3456789', '0']

>>> # 非贪匹配1或n个数字
>>> print(re.findall(r"\d+?", "12a3456789b0abcde_%"))
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']

>>> # 贪婪匹配1或n个数字
>>> print(re.findall(r"\d*", "1234567890abcdefg"))
['1234567890', '', '', '', '', '', '', '']

>>> # 非贪匹配1或n个数字
>>> print(re.findall(r"\d*?", "1234567890abcdefg"))
['', '1', '', '2', '', '3', '', '4', '', '5', '', '6', '', '7', '', '8', '', '9', '', '0', '', '', '', '', '', '', '', '']
# 转义符

>>> # 匹配a*b
>>> print(re.findall(r"a\*b", "aab*ab*aaa*ba*b*a"))
['a*b', 'a*b']

>>> # 匹配b前面至少出现0次a
>>> print(re.findall(r"a*b", "aab*ab*aaa*ba*b*a"))
['aab', 'ab', 'b', 'b', 'b', 'b']

>>> # 匹配a+b
>>> print(re.findall(r"a\+b", ""))
['a+b']

>>> # 匹配b前面至少出现1次a
print(re.findall(r"a+b", "aab*ab*aa?ba*ba*b*a+b"))
['aab', 'ab']

>>> # 匹配a?b
print(re.findall(r"a\?b", "aab*ab*aa?ba*ba*b*a+b"))
['a?b']

>>> # 匹配b前面至少出现0次至多出现1次a
print(re.findall(r"a?b", "aab*ab*aa?ba*ba*b*a+b"))
['ab', 'ab', 'b', 'b', 'b', 'b']

贪婪和非贪

1
2
3
4
5
6
7
>>> # 贪婪匹配 d开头d结尾,中间至少出现1位正常字符
>>> print(re.findall(r"d\w+d", "dxxxxxxdxxxxxxd"))
['dxxxxxxdxxxxxxd']

>>> # 非贪匹配 d开头d结尾,中间至少出现1位正常字符
>>> print(re.findall(r"d\w+?d", "dxxxxxxdxxxxxxd"))
['dxxxxd']
>>> html = "<li>Boii</li><li>$18</li><li>男</li><li>i@tcp404.com</li>"

>>> # .是除了换行符以外所有字符
>>> # 贪婪匹配 <li>开头,</li>结尾,中间至少一个非换行符的字符,共1个
>>> print(re.findall(r"<li>.+</li>", html))
['<li>Boii</li><li>$18</li><li>男</li><li>i@tcp404.com</li>']

>>> # 非贪匹配 <li>开头,</li>结尾,中间至少一个非换行符的字符,共3个
>>> print(re.findall(r"<li>.+?</li>", html))
['<li>Boii</li>', '<li>$18</li>', '<li>男</li>', '<li>i@tcp404.com</li>']

>>> # 非贪匹配 <li>开头,</li>结尾,中间至少一个非换行符的字符,且舍去开头结尾,
>>> # 即只取<li></li>中的内容,共3个
>>> print(re.findall(r"<li>(.+?)</li>", html))
['Boii', '$18', '男', 'i@tcp404.com']

反向引用

>>> wordstr = """ 'hello', "python", 'love", "haha' """

# 匹配 开头和结尾都是单引号或双引号,中间至少一个正常字符
>>> print(re.findall(r"['|\"]\w+['|\"]", wordstr))
["'hello'", '"python"', '\'love"', '"haha\'']

# 匹配 开头和结尾都是单引号或双引号,中间至少一个正常字符,舍弃中间
# 即 开头和结尾都是单或双引号,中间至少一个正常字符,然后把开头分一组,结尾分一组,中间无分组被舍弃
>>> print(re.findall(r"('|\")\w+('|\")", wordstr))
[("'", "'"), ('"', '"'), ("'", '"'), ('"', "'")]

# 匹配 开头和结尾都是单引号或双引号,中间至少一个正常字符,开头一组,中间一组,结尾一组,找到一份则组合成一个元组
>>> print(re.findall(r"('|\")(\w+)('|\")", wordstr))
[("'", 'hello', "'"), ('"', 'python', '"'), ("'", 'love', '"'), ('"', 'haha', "'")]

# 匹配 开头是单双引号,中间至少一个正常字符,结尾要与开头对应开头一组那个组相同
# 开头一组,中间一组,结尾一组,找到一份则组合成一个元组
>>> print(re.findall(r"('|\")(\w+)(\1)", wordstr))
[("'", 'hello', "'"), ('"', 'python', '"')]

# 用列表生成式取出每份的1号元素,就得到了内容。
>>> res = re.findall(r"('|\")(\w+)(\1)", wordstr)
>>> li = [x[1] for x in res]
>>> print(li)
['hello', 'python']
# 校验密码,同样的字符不能连续出现3次

>>> print(re.findall(r"(\w)(\1{2,})", "44888813abgggs"))
[('8', '888'), ('g', 'gg')]
# 第一个分组(\w)匹配到是4,进入下一个元组,看到\1替换成4,开始匹配重复:4出现至少2次则匹配成功,后面紧跟着只有一个4,所以匹配失败
# 接着匹配到(\w)是8,进入下一个分组,看到\1替换成8,从第2个8开始匹配重复:8至少出现2次则匹配成功,除去第一个8被第一个分组取走,还剩3个8,匹配成功,这是贪婪匹配,所以会一直吃下去吃到遇见不是8停止,打包成元组
# 接着1匹配失败,3匹配失败,ab都匹配失败
# 接着匹配到(\w)是g,进入下一个分组,看到\1替换成g,从第2个g开始匹配重复:g至少出现2次则匹配成功,除去第一个g被第一个分组取走,还剩2个g,匹配成功,这是贪婪匹配,所以会一直吃下去吃到遇见不是g停止,打包成元组...

res = re.findall(r"(\w)(\1{2,})", "44888813abgggs")
li = [x[0]+x[1] for x in res]
print(li)        # ['8888', 'ggg']

位置匹配

  • ^:开头
  • $:结尾

这两个经常一起使用,用来限定位置 常用于检验手机号、用户名、email地址等

例如匹配手机号

# 手机位数正常的时候可以匹配成功
>>> print(re.findall(r"\d{11}", "13700001111"))
['13700001111']

# 但是手机位数不正常的时候也匹配成功了,所以要加以位置限制
>>> print(re.findall(r"\d{11}", "137000011112233445"))
['13700001111']

>>> print(re.findall(r"^\d{11}$", "13700001111"))
['13700001111']

>>> print(re.findall(r"^\d{11}$", "137000011112233445"))
[]