正则表达式学习之路

正则应用的地方非常广泛,不管是办公自动化还是爬虫正则都会帮我们很多忙,以前都是让deepseek帮忙写,这次要下一番功夫搞定它.

 

推荐一个正则表达式测试网站

 

https://regex101.com/

 

如果有一个字符串  比如

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)
复制代码

 

posted @   叶无齐  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通

python学习之路

[python随笔]
点击右上角即可分享
微信分享提示