正则表达式学习之路
正则应用的地方非常广泛,不管是办公自动化还是爬虫正则都会帮我们很多忙,以前都是让deepseek帮忙写,这次要下一番功夫搞定它.
推荐一个正则表达式测试网站
如果有一个字符串 比如
content = '''是怎么来的测试3.2天/数据 ? 就是古人 记录天11体的运行测试13天/数据规律 几千年的记录 数据
归纳总结 (天体运测试7天/数据行对 环境 对人的54对微观测试8天/数据的影响)后形成的 古人就测试2天/数据是以自我2感悟
数据的影响)后形成的 古人就数据的影响)后形成的 古人就数据的影响)后形成的 古人就
总结22归纳解析 天地对这整个测试1.9天/数据世界3的影响 而后发现测5试3天/数据的规律 才写出的 学说
包括科学也是测试4天/数据一样 只是科学用了4.3一个标尺 公式 让人人都可测试4天/数据以理解 并可重塑
'''
如果我们要找到其中所有天/数据前的数据,用字符串操作的方法我们需要这样写.

lines = content.splitlines() #将文件按行拆分 for line in lines: pos = line.find('天/数据') #查找每行中是否有 天/数据 if pos < 0 : #如果查找到的话,pos会显示所在字符串的位置,没查找到就返回-1 continue #如果没找到就跳过这一行 pos2 = pos - 1 #如果找到了 那就从前面一个位置开始肯定是数字的最后一位 while line[pos2].isdigit() or line[pos2] == ".": pos2 = pos2 - 1 #往前找一位,如果前面一位还是数字,或者是.号,那么继续往前找 findnum = line[pos2+1:pos] #利用字符串分割我们可以找到需要的数据
真代码比较繁琐,而且只能找到每行中第一个符合条件的数据,如果一行内有多个数据,那就需要更加繁琐的判断代码.
而如果用正则表达式我们只需要

import re #首先引入正则表达式 p = re.compile(r'([\d.]+)天/数据') for i in p.findall(content): print(i)
如果只是简单的要找到字符串 我们只需要用

p = re.compile(r'总结')
在正则表达式中,有一些特殊的符号,分别是 ' . * + ? \ [] ^ $ {} | () '
我们逐个对这些特殊的符号进行一下解释
首先我们学习 . 这个符号
如果我们有一段文本
content = ''' 苹果是绿色的 橘子是黄色的 番茄是红色的 茄子是紫色的'''
. 这个符号表示匹配任何一个除了换行符之外的字符,比如我们如果用代码
p = re.compile(r'.色') # r 是python表示后面所有的特殊字符都只当成文本处理,不作为python语法中的符号来使用.
#re.compile表示 将这个正则表达式编译成一个对象,有了这个对象,我们就可以直接用这个正则表达式来处理各种内容.类似于先将一个字符串赋值为一个变量 p 然后我们可以用p.split等各种方法来对这个字符串进行各种操作
p.findall(content) #表示用P这个对象,findall方法表示找到所有符合表达式的文本 content表示要搜索的文本内容 findall返回的是一个列表 储存着所有找到的符合条件的内容
这个正则代表的意思是 一个任意字符,和色字连在一起,那么就会匹配到 [绿色 黄色 红色 紫色]
* 号 表示重复前一个对象 匹配任意次数 比如 色* 就是任意次色 .* 就是任何字符任意次 包括0次
content = ''' 苹果是绿色的 橘子是黄色的 番茄是红色的 茄子是紫色的
葫芦是'''
p = re.compile(r'是.')
#如果是这样 我们应该匹配到的是 是绿 是黄 是红 是紫 而最后一行不会被匹配到,因为按规则 葫芦是 后面没有字符了 而我们的要求是 是后面加任意一个字符 必须要有字符
#但是如果这样匹配
p = re.compile(r'是.*')
#那就会匹配到 是绿色的 是黄色的 是红色的 是紫色的 注意了 还会匹配到 最后一行的是
#注意了 因为我们加了 *号 也就是匹配任意个字符 0个也是任意个的一种
p.findall(content)
+ 号 表示重复前一个对象 匹配任意次数 和 * 几乎一致 ,唯一的区别是 他不包括0次!
content = ''' 苹果是绿色的 橘子是黄色的 番茄是红色的 茄子是紫色的 葫芦是''' p = re.compile(r'是.+') #会匹配到 是绿色的 是黄色的 是红色的 是紫色的 最后一行不会被匹配到 p.findall(content)
? 号 表示匹配前一个对象 0或者1次.
content = ''' 苹果是绿色的 橘子是黄色的 番茄是红色的 茄子是紫色的 葫芦是''' p = re.compile(r'是.?') #会匹配到 是绿 是黄 是红 是紫 是 最后一行的是同样会被匹配到 p.findall(content)
{ } 花括号表示匹配前一个元符号或者字符指定次数
content = ''' 红彤彤,绿油油,黑乎乎,绿油油油油''' p = re.compile(r'绿油{3,4}') #会匹配到 绿油油油油 但是前面的绿油油不会被匹配到 因为最少3个最多4个
#我们也可以直接 p = re.compile(r'绿油{3}') 就是表示只要3次的
p.findall(content)
而花括号{} 如果指定大于N次 要怎么做呢
p = re.compile(r'绿油{3,}') #这就表示 3次及3次以上
关于匹配模式 贪婪和非贪婪
#如果我们有一段代码 我们要取出其中的所有html标签 source = '<html><head><title>Title</title>' p = re.compile(r'<.*>')
#我们的设想是会分别取出 <html> <head> <title> </title> 但是实际上我们只会得到 <html><head><title>Title</title> 这就是因为贪婪匹配了的结果
在正则表达式中 ' * ' 和 '+' 都是默认贪婪模式的 他们会尽量匹配多的内容 而如果我们不想要他尽量多匹配,可以加上 ' ? ' 让他非贪婪模式
p = re.compile(r'<.*?>') #这样就会得出 <html> <head> <title> </title> 加了?号以后 ,程序就会往尽量少的位置去匹配符合条件的内容
同时?也可以用在花括号后面 {} 让他非贪婪匹配
1 2 3 4 5 6 7 8 9 | content = ''' 12345678 123456789 ''' p = re. compile (r '\d{8,9}' ) #这里会默认会把12345678 和 123456789都匹配到 但是如果我们只想匹配8位,那么可以 p = re. compile (r '\d{8,9}?' ) |
反斜杠 \ 可以用来转义 元字符 比如我们需要查找的内容里就含有 . 那该如何是好呢
content = '''苹果.是绿色的 橙子.是橙色的 香蕉.是黄色的''' #如果我们想查找 苹果. 橙子. 香蕉. p = re.compile(r'.*.')
#这个正则表达式看似正确实则根本不会按照我们需要的目标去匹配,因为程序无法理解我们这个 . 是什么意图
p = re.compile(r'.*\.')
#但是加了\后,程序就可以理解 这个 .就是作为 . 而不是元字符的匹配任何除了换行符外的字符
除了转义元字符外 \ 还有很多特殊作用
比如
\d 匹配0-9之间任意一个数字字符,等价于表达式 [0-9]
\D 匹配任意一个非0-9数字的字符,等价于表达式 [^0-9]
\s 匹配任意一个空白字符,包括 空格、tab、换行符等,等价于表达式 [\t\n\r\f\v]
\S 匹配任意一个非空白字符,等价于表达式 [^ \t\n\r\f\v]
要单独匹配特殊的键 1. \t:制表符(Tab 键) 2. \n:换行符 3. \r:回车符 4. \f:换页符 5. \v:垂直制表符 而空格,直接打一个键盘上的空格就可以 也可以用[ ]来代表空格
\w 匹配任意一个文字字符,包括大小写字母、数字、下划线,等价于表达式 [a-zA-Z0-9_]
缺省情况也包括 Unicode文字字符,如果指定 ASCII 码标记,则只包括ASCII字母
\W 匹配任意一个非文字字符,等价于表达式 [^a-zA-Z0-9_]
\b 主要用来匹配边界,比如
1 2 3 4 5 6 7 8 9 10 11 12 | text = ''' code.tt code asd code.aser asdf.code codeasd alsdjcode ''' p = re. compile (r '\bcode\b' ) find = p.findall(text) print (find) #得出的结果是 ['code', 'code', 'code', 'code'] 可以把所有有分隔的code都取出来,而连在一起的则不会被取到 |
反斜杠也可以用在方括号里面,比如 [\s,.] 表示匹配 : 任何空白字符, 或者逗号,或者点
其中需要特别讲解一下的就是 \w 通常也匹配所有中文字符 比如
text = ''' 王亚辉 tony 张春兰 ''' p = re.compile(r'\w{3,5}') find = p.findall(text) print(find) #得出的结果是 ['王亚辉', 'tony', '张春兰']
而如果我们只想匹配英文怎么办呢,比如注册用户名,我们通常会允许大小写字母,数字,下划线.通常不允许中文,只需要加上定义字符 re.ASCII
p = re.compile(r'\w{3,5}',re.A) #或者 p = re.compile(r'\w{3,5}',re.ASCII)
#按这个匹配去findall 就会得出 ['tony']
方括号 [ ] 可以匹配几个字符之一,比如我们有下面的内容
text = ''' 姜哲,13812120814,25 小亚,15252747998,30 王安石,1d05782171,28 张二晓,17752879521,30 ''' #我们要匹配出里面所有的电话号码 p = re.compile(r'1[357]\d{9}') #首先是1开头 1 ,随后第二个数字是3,5,7其中1个[357],随后是数字\d,并且限定是9个{9}
#方括号匹配不仅可以具体制定值,也可以指定范围 比如 [3-6] 表示3456都可以 [a-e]等于 a,b,c,d,e
方括号[ ] 还需要注意的一点是 ,元字符在方括号中就无效了
text = ''' 姜哲,1.3812120814,25 小亚,1+5252747998,30 王安石,1d05782171,28 张二晓,1+7752879521,30 '''
#如果我们要把 1. 1+ 都找出来 我们是不是想这样
p = re.compile(r'1[\.\+]') #其实我们只需要这样就可以,元字符在方括号中就只是普通字符了 p = re.compile(r'1[.+]')
方括号 [ ] 里面如果加 ^ ,则表示非方括号中指定范围,就是反过来的意思
text = '''abc123456789''' p = re.compile(r'[1-9]+') #这表示找到所有数字 我们可以获取到123456789 #但是如果我们 这样 p = re.compile(r'[^1-9]+') #这就会得到abc find = p.findall(text) print(find)
而如果 ^ 不在方括号内 代表的是 从开头位置 以及多行模式 re.M
text = '''001-苹果价格-60 002-橙子价格-70 003-香蕉价格-80''' pattern = re.compile(r'\d+') find = pattern.findall(text) print(find) #这里会得出结果 ['001', '60', '002', '70', '003', '80'] 但是我们想要的是开头的编号001等怎么办呢 pattern = re.compile(r'^\d+') #但是,当我们打印这个正则搜到的结果,得到的却是 ['001'] 为什么呢 因为^代表了开头,而不是每一行的开头,我们需要指定多行模式,这样^就会从每一行的开头去找 pattern = re.compile(r'^\d+',re.M) #得出结果 ['001', '002', '003']
有开头就肯定有结尾 $ 符号则代表结尾位置,我们还是用上面的例子来举例
text = '''001-苹果价格-60 002-橙子价格-70 003-香蕉价格-80''' pattern = re.compile(r'\d+$',re.M) find = pattern.findall(text) print(find) #很容易 我们得出了 ['60', '70', '80'] 这里要注意的是 ^代表开头,所以放在前面,$代表结尾,所以要放在后面.
括号()分组 我们一定有过以下的疑惑,如果我们通过一些特征来将想要的内容找了出来,但是同时这些特征也一同被我们囊括了进来,比如
content = '''苹果,是绿色的 橘子,是黄色的 西红柿,是红色的 茄子,是紫色的 ''' pattern = re.compile(r'.+,') find = pattern.findall(content) print(find) #我们确实找到了想要的 [' 苹果,', '橘子,', '西红柿,', '茄子,'] 但是每个都多了一个逗号,我们不想要这个逗号,那么需要一个一个再用字符串切割[:-1] 吗?
#其实我们只需要这样 条件还是不变,但是我们将我们需要的内容用括号()括住
pattern = re.compile(r'(.+),')
#就可以得出 ['苹果', '橘子', '西红柿', '茄子']
同样我们可以在一次正则中用多个括号()来选取多个内容 比如
content = '''张三,手机号码15945678901 李四,手机号码13945677701 王二,手机号码13845666901''' #这里我们想同时抓取出姓名和手机号码 pattern = re.compile(r'(.+),.+(\d{11})') find = pattern.findall(content) print(find) #这样我们就得到了一个个的元祖 [('张三', '15945678901'), ('李四', '13945677701'), ('王二', '13845666901')]
而 () 中我们可以指定多个对象,也就是python中的or的意思 用符号 |
1 2 3 4 5 6 7 8 9 10 11 | source = ''' a.avi a.txt as.bmp as.xlsx ww.doc ''' p = re. compile (r '(avi|txt|jpg|doc)' ) find = p.findall(source) print (find)<br> #可以很抓初所有符合条件的 结果是 ['avi', 'txt', 'doc'] |
非捕获分组 ?: 如果我们既需要分组的特征,又不想捕获这个分组,那就可以用
1 2 3 4 5 6 7 8 9 10 | source = ''' asd<font>提取我</font> asd<font>提取我2</azxx> ''' p = re. compile (r '<(?:\w+)>(.+?)</\w+>' ) find = p.findall(source) print (find) #我们在第一个分组中用了 ?: 那么这个分组将不会被捕获. |
分组回溯, 当我们提取数据时, 比如遇到如下情况
1 2 3 4 5 6 7 8 9 10 | source = ''' asd<font>提取我</font> asd<font>提取我2</azxx> ''' p = re. compile (r '<\w+>(.+)</\w+>' ) find = p.findall(source) print (find) #这里我们可以得到 ['提取我', '提取我2'] #但是,实际上 我们要提取的只有 提取我 而不是下面的错误的标签中包裹的信息,但是正则的规则是可以匹配到下面的内容的 |
这时候我们可以用分组的回溯功能 但是要记得,如果我们在一个分组中用了非捕获分组,那么这个分组不会被视为有效的分组,也就是说这个分组不会参与分组回溯.
1 2 3 4 5 6 7 8 9 10 | source = ''' asd<font>提取我</font> asd<font>提取我2</azxx> ''' p = re. compile (r '<(\w+)>(.+?)</\1>' ) find = p.findall(source) print (find) #后面的这个</\1> 中的 \1 则表示回溯第一个 ()分组中的内容 当然我们也可以用\2来回溯第二个分组 |
环视 也叫预搜索 先行断言 和后行断言
1.先行断言 -- 正向先行断言
(?=表达式) 指从某个位置往右看,必须匹配这个表达式
1 2 3 4 5 6 7 | source = '''我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你''' p = re. compile (r '喜欢(?=你)' ) find = p.findall(source) print (find) #这就表示 喜欢 右边必须有你 于是我们只找到了两个 我喜欢你 和喜欢你 |
2.先行断言 -- 反向先行断言
(?!表达式)指从某个位置从右看,不能符合这个表达式
1 2 3 4 5 6 7 | source = '''我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你''' p = re. compile (r '喜欢(?!你)' ) find = p.findall(source) print (find) #这就表示 喜欢 的右边,不能出现 你 于是匹配到了 我喜欢 我喜欢我 喜欢 |
3.后行断言 --- 正向后行断言
(?<=表达式) 指从某个位置往左看,要符合这个表达式
1 2 3 4 5 6 7 | source = '''我喜欢你 我喜欢 喜欢我 我不喜欢你 喜欢你''' p = re. compile (r '(?<=我)喜欢' ) find = p.findall(source) print (find) #表示 喜欢的左边要有 我 这就匹配到了 我喜欢你 我喜欢 |
4.后行断言 --- 反向后行断言
(?<!表达式) 指从某个位置往左看,不可以符合这个表达式
1 2 3 4 5 6 7 | source = '''我喜欢你 我喜欢 喜欢我 我不喜欢你 喜欢你''' p = re. compile (r '(?<!我)喜欢' ) find = p.findall(source) print (find) #很显然这就匹配了 喜欢我 我不喜欢你 喜欢你 |
关于 点号 . 换行匹配问题 我们之前说过 .号匹配是不换行的 需要加上参数re.DOTALL
<div class="el"> <p class="t1"> <span> <a>Python开发工程师</a> </span> </p> <span class="t2">南京</span> <span class="t3">1.5-2万/月</span> </div> <div class="el"> <p class="t1"> <span> <a>java开发工程师</a> </span> </p> <span class="t2">苏州</span> <span class="t3">1.5-2/月</span> </div> pattern = re.compile(r't1\">.*?a>(.+?)</a')
find = pattern.findall(content)
print(find) #你会发现什么都取不到,因为.不换行 所以 .*a>匹配不到任何东西
#为了让.能跨行 我们加上DOTALL参数
pattern = re.compile(r't1\">.*?a>(.+?)</a',re.DOTALL)
#这样就可以正确的取到['Python开发工程师', 'java开发工程师']
除了利用正则表达式来提取文本,我们还可以用他来分割文本
python自己提供了split方法,但是这个方法通常会显得那么笨拙简单.而利用正则表达式的 re.split()方法则灵活多变起来
#比如我们有一段文本,分割符号有;有,也有空格,后面可能跟着0到N个空格 names = '关羽; 张飞, 赵云,马超, 黄忠 李逵' re_method = r'[;,\s]\s*' #[;,\s]表示 首先 他可能是;或者,或者空字符中的一个但是必须有一个 随后 \s*表示 他可能跟着0-N个空字符 re_split = re.split(re_method, names) print(re_split) #这里就可以取出 ['关羽', '张飞', '赵云', '马超', '黄忠', '李逵']
正则还可以用来替换文本,并且比python自带的replace要强大自由得多
names = ''' 下面是这学期要学习的课程: <a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a> 这节讲的是牛顿第2运动定律 <a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a> 这节讲的是毕达哥拉斯公式 <a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a> 这节讲的是切割磁力线 ''' #replace只能替换固定的内容,而我们如果想要批量替换类似的文本那么就这样 newStr = re.sub(r'/av\d+/', '/cn345677/' , names) print(newStr) #匹配所有 /av开头 后面是1到N个数字,并且以/结尾的内容,替换为/cn345677/
sub替换还可以调用函数作为替换的内容 比如我们并不是固定耀替换为 /cn345677/ 而是想在原数字的基础上加6
names = ''' 下面是这学期要学习的课程: <a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a> 这节讲的是牛顿第2运动定律 <a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a> 这节讲的是毕达哥拉斯公式 <a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a> 这节讲的是切割磁力线 ''' # 替换函数,参数是 Match对象 def subFunc(match): # Match对象 的 group(0) 返回的是整个匹配上的字符串, src = match.group(0) # Match对象 的 group(1) 返回的是第一个group分组的内容 number = int(match.group(1)) + 6 dest = f'/av{number}/' print(f'{src} 替换为 {dest}') # 返回值就是最终替换的字符串 return dest newStr = re.sub(r'/av(\d+)/', subFunc , names) print(newStr)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通