一、正则基本知识
正则在python中是以c实现的,在后续的爬虫等工作都需要使用到该模块,主要搜索文本的内容。搜索非常速度。
https://www.regexpal.com/ 两个个在线的正则表达式,可以用来借鉴
1.1 正则常用的匹配规则
下面是列举出来一些常用的,更多的使用help(re)查看。
正则默认都是贪婪匹配的(尽可能的多,贪吃)。还有一种懒惰匹配(取到最少那次就不干活了)。
?是一个非常有意思的规则。 自己单独的时候,表示0或1个字符。
和贪婪的组合在一起的时候,就变成了控制贪婪变成懒惰匹配了。
. 代表任意字符,但是除了换行符
# 开头和结尾
^ 代表字符串的开始
$ 代表字符串的结尾 ,如果是多行文本,默认只匹配第一行的结尾。
# 任意字符,贪婪匹配
* 匹配0或多次,贪婪匹配,取到满足的最多
+ 匹配1或多次,贪婪匹配,取到满足的最多
? 匹配0或一次,贪婪匹配,取到满足的最多
?还可结合*,+,?做懒惰匹配
# ?组合-懒惰匹配
*? +? ?? 前面的任意字符,得到最少的那次
# 指定次数 匹配
{m} 匹配m次。
{m,n} 匹配m到n次,和切片的使用方法一致。也是贪婪匹配
{m,} m到无穷
{n,} 0 到n次
a{m} 匹配6个a
{m,n}? 更改成懒惰,得到最少的那次匹配
# 分组
() 下面会详细的讲解
# 字符集
[] 只取其中一个
[0-9] 代表 1到9,
[a-z] 代表 a到z
[a-zA-z0-9] 所有的字符和数字
[abc] 匹配任意一个
[^9] 除了9以外其他的所有
# 或
| 常常搭配字符集和分组使用,[a|b]:找到a了,就不找b了,反之相同。
1.2 转义字符 \
python的正则规则是以引号 包围的,里面的的一些字符 要代表本身的含义,或 转成其他意思,就需要这个\。
红色的为常用的。
\. 默认.是代表任意字符,我们可以让他转义为。本身
\A 只在字符串开头进行匹配。
\b 匹配位于开头或者结尾的空字符串
\B 匹配不位于开头或者结尾的空字符串
\d 匹配任意十进制数,相当于 [0-9]
\D 匹配任意非数字字符,相当于 [^0-9]
\s 匹配任意空白字符,相当于 [ \t\n\r\f\v]
\S 匹配任意非空白字符,相当于 [^ \t\n\r\f\v]
\w 匹配任意数字和字母,相当于 [a-zA-Z0-9_]
\W 匹配任意非数字和字母的字符,相当于 [^a-zA-Z0-9_]
\Z 只在字符串结尾进行匹配
\number 在分组中使用,类似占位符
二 、两种使用方法
2.1 方法1:compile
利用compile方法,生成一个re对象,好处是可以多次使用
import re
res = re.compile('\d{3}') # 第一步,把正则字符串进行编码,返回一个re对象,保存为一个常量
result = res.search('I am hui 123') # 第二步,把compile返回的对象,去匹配字符串,返回一个mactch对象
result.group() # 第三步,使用match对象的group方法,返回实际匹配的对象
正则支持链式编程
2.2 方法2:re.方法
直接使用re的方法,对于单一的使用比较方便,如果每次使用,每次都要定义。
import re
x = re.search('\d{3}', 'I am hui 123') # 直接使用match对象
x.group() # 使用match对象的group方法,返回实际匹配的对象
正则支持链式编程
三、函数
3.1 compile
compile(pattern, flags=0)
Compile a regular expression pattern into a regular expression object re.compile(pattern, flags=0)
把正则表达式的模式和标识转化成正则表达式对象,供 match() 和 search() 这两个 函数使用。
常用的flags:
re.I 忽略大小写
re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
re.M 多行模式
re.S 把‘.’切换成 包含换行符,默认 点 是不包含换行符号的。
re.X 为了增加可读性,忽略空格和’ # ’后面的注释
一个正则的注释的列子:
import re regex = re.compile(r'(\d{1,3}\.){3}\d{1,3}') # 错误写法 regex = re.compile(r''' (2[0-4]\d|25[0-5]|[01]?\d\d?\.) # 代表一组ip,包含后面的点 {3} # 表示3组 (2[0-4]\d|25[0-5]|[01]?\d\d?) # 最后一组数字 ''', re.X) print(regex.match('192.168.1.1')) print(regex.match('999.999.999.999')) # 非正常的ip
3.2 seach
搜索整个字符串,返回第一个匹配的对象-match对象
search(pattern, string, flags=0)
Scan through string looking for a match to the pattern, returning
a match object, or None if no match was found.
import re s = """ 1334-1234-113 133-1234-2123 135-4567-3456 de8ug@foxmail.com lilei@qq.com hmm.lee@goglr.com https://github.com https://taobcom.com """ target = '\d{4}' target = '\d{3}(-\d{4}){2}' regex = re.compile(target) result = re.search(regex, s) if result: print(result.group()) else: print('找不到')
3.2 match
匹配字符串的开头,符合则返回一个match对象。
注意match 和 seach的不同。两者都是只匹配一次,找到符合就返回,后面的就不再匹配。如果不符合就返回None
macth:只匹配字符串的开头,注意前面的空白字符,特别是/n 换行。
seach:搜索整个字符串。
import re
reg = re.compile('\d{3}')
s1 = '1234dasd' # 正常内容
s2 = '/n1234dasd' # 前面有换行符
s3 = ' 123' # 前面有空格
print(reg.match(s1))
print(reg.match(s2))
print(reg.match(s3))
结果:
<_sre.SRE_Match object; span=(0, 3), match='123'>
None
None
3.3 findall
匹配所有的字符串,到结束为之。返回一个列表。
import re
s = 'ferfewf234ed3de8ge4r3434rde8ger3r3rde8gede8ger34rf34r'
rege = re.compile('de8ge')
# rege = re.compile('de(8)ge') # 进行分组
result = rege.findall(s)
print(result)
3.4 spilit
使用正则的规则,来进行分割。类似字符串的spilt
import re s = 'ferfewf234ed3de8ge4r3434rde8ger3r3rde8gede8ger34rf34r' rege = re.compile('de8ge') # 抛弃了de8ge rege = re.compile('(de8ge)') # 以de8ge进行分割,但是会返回de8ge result = rege.split(s) print(result)
3.5 sub
字符串中也有字符串的替换,replace,。正则中使用sub来替换字符串的内容,比起字符串的内容,整体更灵活。
第一种使用分组进行替换
import re
s = '字符串格式: 10/01/2008, 12/25/2018'
re_date = re.compile(r'(\d+)/(\d+)/(\d+)') # r是raw的意思,原生字符串
x=re_date.sub(r'\3-\1-\2', s)
print(x)
第二种 进行字符替换
import re
s = """
pytyhon is hard to learn, 坚持下,
没多少了 ok?
"""
# print(s)
regex = re.compile('\s+')
print(regex.sub('', s))
四、扩展知识
4.1 贪婪匹配 与 懒惰匹配
?是一个非常有意思的规则。
1. 自己单独的时候,表示0或1个字符。
2. 和贪婪的组合在一起的时候,就变成了控制贪婪变成懒惰匹配了。
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
# 小案列,想匹配引号内部的,说话的内容
import re
text = 'Ipone say "yes." PC say "No."'
regex = re.compile('"(.*)"') # 使用贪婪匹配,会得到最长的那串
#regex = re.compile('"(.*?)"') # 加上?,变成懒惰匹配就正常了
print(regex.findall(text))
4.2 分组
分组是用()来表示的,分组在我个人感觉 有三种作用:
1. 将某些规律看成是一组,然后进行组级别的重复。
2. 分组之后,可以通过后向引用简化表达式。
3. 和find组合,查找指定的字符
第一个作用
用ip地址(这个是简单版本,ip的真实看上面)作为实例:
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}
但仔细观察,我们可以发现一定的规律,可以把.\d{1,3}看成一个整体,也就是把他们看成一组,再把这个组重复3次即可。表达式如下:
\d{1,3}(\.\d{1,3}){3}
这样一看,就比较简洁了。
第二个作用:
首先强调一个知识点,后向引用,赋值的是匹配后的结果,非正则表达式。
<title>.*</title> 可以看出,上边表达式中有两个title,完全一样,其实可以通过分组简写。表达式如下: <(title)>.*</\1> 这个例子实际上就是反向引用的实际应用。对于分组而言,整个表达式永远算作第0组,在本例中,第0组是<(title)>.*</\1>,然后从左到右,依次为分组编号,因此,(title)是第1组。 用\1这种语法,可以引用某组的文本内容,\1当然就是引用第1组的文本内容了,这样一来,就可以简化正则表达式,只写一次title,把它放在组里,然后在后边引用即可。 以此为启发,我们可不可以简化刚刚的IP地址正则表达式呢?原来的表达式为\d{1,3}(.\d{1,3}){3},里边的\d{1,3}重复了两次,如果利用后向引用简化,表达式如下: (\d{1,3})(.\1){3} 简单的解释下,把\d{1,3}放在一组里,表示为(\d{1,3}),它是第1组,(.\1)是第2组,在第2组里通过\1语法,后向引用了第1组的文本内容。 经过实际测试,会发现这样写是错误的,为什么呢? 小菜一直在强调,后向引用,引用的仅仅是文本内容,而不是正则表达式! 也就是说,组中的内容一旦匹配成功,后向引用,引用的就是匹配成功后的内容,引用的是结果,而不是表达式。 因此,(\d{1,3})(.\1){3}这个表达式实际上匹配的是四个数都相同的IP地址,比如:123.123.123.123。
第三作用:
和find组合,提取组的内容。 先匹配整个正则表达式,根据结果再次提取()里面的内容
import re
data = '''
○ 4.1日,共有4人面试,手机号分别是13812345678,15112345678,13812345678,15112345678
○ 4.5日,共有6人面试13812345678,15112345678,13812345678,15112345678,13812345678,15112345678
○ 4.7日,共有3人面试13812345678,15112345678,13812345678
○ 4.8日,共有5人面试15112345678,13812345678,15112345678,13812345678,15112345678
4.30日,共有6人面试13812345678,15112345678,13812345678,15112345678,13812345678,15112345678
'''
regex = re.compile('共有(\d+)人') # 先匹配整个正则表达式,根据结果再次提取()里面的内容
sum_people = sum([int(i) for i in regex.findall(data)])
print(sum_people)
分组命名:
对于后向引用采用编号的方式进行替换,容易混淆,采用命名的方式便于识别
import re
# regex = re.compile(r'(\d+)\.(\d+)') # 使用编号替换,但是容易混淆,下面的方式采用命名
# print(regex.sub(r'\1月\2日', memo_text))
regex = re.compile(r'(?P<month>\d+)\.(?P<day>\d+)')
print(regex.sub(r'\g<month>月\g<day>日', memo_text))
五、 常用一些正则
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
import re RE_PHONE = re.compile('\d{3}-\d{8}|\d{4}-\d{7}') def phone(str1: str)-> list: "输入一个字符串,从中返回一个列表" return RE_PHONE.findall(str1) def main(): s = """ 010-23293293deuju010-23223293 0111-3234123 """ print(phone(s)) if __name__ == '__main__': main()
匹配腾讯QQ号: [1-9][0-9]{4,} 腾讯QQ号从10000开始
只能输入汉字: ^[\u4e00-\u9fa5]{1,8}$
只能输入由数字和26个英文字母组成的字符串: “^[A-Za-z0-9]+$”
验证用户密码: “^[a-zA-Z]\w{7,17}$”正确格式为:以字母开头, 长度在8-18之间, 只能包含字符、数字和下划线。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
import re # 验证电话 RE_PHONE = re.compile('\d{3}-\d{8}|\d{4}-\d{7}') # 验证汉字 RE_CH = re.compile('^[\u4e00-\u9fa5]{1,8}$') # 验证密码 RE_PWD = re.compile('^[a-zA-Z]\w{7,17}$') def phone(text: str)-> list: "输入一个字符串,从中返回一个列表" return RE_PHONE.findall(text) def verify(regex: '正则表达式', text: str)-> list: "验证用户名和密码" if regex.match(text): return True else: return False def main(): s = """ 010-23293293deuju010-23223293 0111-3234123 """ # print(phone(s)) print(verify(RE_CH, '辉')) print(verify(RE_PWD, 'dewu231_*')) if __name__ == '__main__': main()