Skip to content

Latest commit

 

History

History
554 lines (481 loc) · 17.4 KB

Python_Part_02-Reg_Ex.md

File metadata and controls

554 lines (481 loc) · 17.4 KB

Title: Python 学习日记 - 正则表达
Author:Xueyong Lu
First Edition: March - 2023

Python学习日记 - Part 02

Regular Expression正则表达式

📘<<Part 01 | Part 03]>>

正则表达的作用是处理文本、提取期望的信息,在其他的很多语言中也是类似的表达,而不只是Python。当然也可以用在Python中提供了很多种字符串对象的内置处理方法,如**.split().find().index()**等。

1.1 一个例子

Python3 高级开发工程师 上海互教教育科技有限公司上海 - 浦东新区2万/月 02-18满员
测试开发工程师(C++/python)上海墨鹍数码科技有限公司上海 - 浦东新区2.5万/每月02-18未满员
Python3 开发工程师 上海德拓信息技术股份有限公司上海 - 徐汇区1.3万/每月02-18剩余11人
测试开发工程师(Python) 赫利普(上海)信息科技有限公司上海 - 浦东新区1.1万/每月02-18剩余5人
Python 高级开发工程师 上海行动教育科技股份有限公司上海 - 闵行区2.8万/月02-18剩余255人
python 开发工程师 上海优似腾软件开发有限公司上海 - 浦东新区2.5万/每月02-18满员

期望输出:

2.5
1.3
1.1
2.8
2.5

1.1.1通过Python内置函数及方法:

### 要求:提取每个职位的薪资 ###
content = ''' '''

# 将文本内容按照行放入列表
lines = content.splitlines()
# 对每一行文本进行分析:按照规律可知,期望薪资的单位是“万/每月”或者“万/月”,那么这两个字符串前的数字即为期望输出
for line in lines:
    # 用匿名函数 lambda x: ... 其中x为形参
    # lambda x: 当if为真时,运行第一个函数,否则则为第二个
    pos2 = (lambda x: line.find(x) if line.find(x) != -1 else None)('万/每月') or (
        lambda x: line.find(x) if line.find(x) != -1 else None)('万/月')

    if isinstance(pos2, int):
        idx = pos2 - 1
        # 只要是数字或者小数点,那么就继续向前查找
        while line[idx].isdigit() or line[idx] == '.':
            idx -= 1
            # 循环完成后,idx指向的下标就是数字前面的字符
            # 那么数字的起始索引值就是 idx+1
        pos1 = idx + 1
        print(line[pos1:pos2])

1.1.2 通过正则表达式:

import re
p = re.compile(r'([/d.]+万/每{0,1}月)') # r'([/d.]+万/每?月)'
for one in p.findall(content):
    print(one)

其中,.../每{0,1}月也可以写为.../每?月

re.compile(r'_')函数的参数就是正则表达式的字符串。上面的例子指定的搜索子串的特征为  ([/d.]+万/每{0,1}月) ,该函数会返回一个compile对象,compile对象的*.findall*方法返回所有返回的子串,并放在一个列表里。

正则表达式验证工具:RegEx验证

1.2 RegEx语法

正则表达式中包含两种字符,一种是直接进行相同匹配的字符普通字符;一种是特殊的字符,也被成为元字符(metacharacters),如:

. * + ? / [ ] ^ $ {} | ()

表示要匹配除了换行符以外的任何单个字符

苹果是绿色的
香蕉是黄色的
橙子是橙色的
乌鸦是黑色的

找出上面文本中的所有颜色:

import re
text = '''
        苹果是绿色的
        橙子是橙色的
        香蕉是黄色的
        乌鸦是黑色的'''
char_match = re.compile(r'(.色)')
for element in char_match.findall(text):
    print(element)

运行结果如下:

绿色
黄色
橙色
黑色

Process finished with exit code 0

1.2.2 任意次 *

表示匹配*前面子表达式任意次,包括0次

苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的

从以上文本中,找出每行“,”之后的内容,包括“,”本身。

import re
    text = '''
        苹果,是绿色的
        橙子,是橙色的
        香蕉,是黄色的
        乌鸦,是黑色的'''
str_match = re.compile(r'(,.*)')
for element in str_match.findall(text):
    print(element)

(,.*)表示:从“,”开始,任意次数“*”的任意字符“.”

运行结果如下:

是绿色的是橙色的是黄色的是黑色的

1.2.3 任意非零次+

表示匹配+前面子表达式一次或多次,不包括0次

1.2.4 指定次{}

表示匹配前面子表达式指定次数X{a,b}要匹配的X元素出现至少a次,最多b次

text = '''
            张飒,158 4665 7365,43
            李嗣,178 6543 6789,26
            杨武,134 7965 7321,36
            王陆,165 7865 1354,34
            刘琦,173 6543 8654,45'''

如从以上文本中提取电话号码信息:

info_match = re.compile(r'(/d{3}./d{4}./d{4})')
for number in info_match.findall(text):
    print(number)

输出为:

158 4665 7365
178 6543 6789
134 7965 7321
165 7865 1354
173 6543 8654

1.2.5 贪婪模式和非贪婪模式

例如要把下面字符串中的所有html标签提取出来:

source = '<html><head><title>Title</title>'

得到如下列表:

['<html>', '<head>', '<title>', '</title>']

利用<.*>表示在<>中出现的任意次数的任意字符来表示一个html标签,理论上是可行的,下面是实际运行的结果:

<html><head><title>Title</title>

原因是,在正则表达式中 '*', '+', '?'都是“贪婪的”,它们会尽可能多地匹配内容,所以用<.*>就表示从第一个<一直匹配到了最后一个>。要得到期望输出,需采用非贪婪模式,在*后面加?,变成<.*?>。(控制“尽可能多”还是“尽可能少”)

source = '<html><head><title>Title</title>'
    tag_match = re.compile(r'(<.*?>)')
    print(tag_match.findall(source))
    for tag in tag_match.findall(source):
    	print(tag)

输出变为:

['<html>', '<head>', '<title>', '</title>']
<html>
<head>
<title>
</title>

1.2.6 元字符的转义

对==元字符==本身进行匹配

text = '''
		苹果.是绿色的
		橙子.是橙色的
		香蕉.是黄色的'''

提取上面文本中所有在.之前的字符

extract_info = re.compile(r'(.*/.)')
 for element in extract_info.findall(text):
     print(element)

输出如下结果:

     苹果.
     橙子.
     香蕉.

**注意在文本中如果包括tab缩进和空格,在用.*也能够被匹配。*(见正则表达:.)

匹配==某种类型==的字符

转义表示 等同表达 描述 补充
/d [0-9] 任意==数字字符==
/D [^0-9] 任意==非数字==字符
/s [/t/n/r/f/v] 任意==空白==字符: 空格、Tab、换行等
/S [^/t/n/r/f/v] 任意==非空白==字符
/w [a-zA-Z0-9] 任意==文字字符==:字母、数字、下划线 re.compile(r'(/w{2,4})',re.A) 参数re.A表示匹配的是ASCII码字符
/W [^a-zA-Z0-9] 任意==非文字==字符

转义匹配的不同re.方法见下表:

Flag 含义
ASCII, A 使个别转义匹配/w, /b, /s, /d只对ASCII字符进行匹配
DOTALL, S 使.匹配任意字符,包括换行
IGNOREXCASE, I 大小写不敏感case-insensitive匹配
LOCALE, L local-aware匹配
MULTILINE, M multi-line匹配,影响^$
VERBOSE, X(for 'extended') 启用冗长verbose REs,便于组织和理解

1.2.7 指定字符[]

匹配几个指定的字符之一

text = '''
        张飒,138 4665 7365,43
        李嗣,178 6543 6789,26
        杨武,114 7965 7321,36
        王陆,235 7865 1354,34
        刘琦,1b3 6543 8654,45
        孙八一, 156 4657 1231,30'''

对以上信息提取有效的电话号码:开头为1,并且第二位为3-8

def RegEx_square_brackets():
    import re
    Number_match = re.compile(r'(1[3-8]/d./d{4}./d{4})')
    for number in Number_match.findall(text):
        print(number)

输出为:

138 4665 7365
178 6543 6789
156 4657 1231

元字符在[]内不再具有特殊含义,而仅仅代表字符本身:

r'([a-h][.+*])

在方括号[]里使用^符号,表示

strin = 's4d3a5sd4+da.546s87@%#Ad%645'

对于以上字符串,有以下要求:

  • 提取字符串中的所有数字字符
  • 提取字符串中的所有非数字字符
  • 提取字符串中的所有字母
  • 提取字符串中的所有特殊字符
import re
    strin = 's4d3a5sd4+da.546s87@%#Ad%645'
    # 提取上述字符串中的数字
    numbers = re.compile(r'([/d])')
    # 提取上述字符串中的非数字
    non_numbers = re.compile(r'([^/d])')
    # 提取上述字符串中的字母
    char_match = re.compile(r'([a-zA-Z])')
    # 提取上述字符串中的特殊字符
    spec_char = re.compile(r'([^a-zA-Z0-9])')
    print(numbers.findall(strin))
    print(non_numbers.findall(strin))
    print(char_match.findall(strin))
    print(spec_char.findall(strin))

输出为:

字符串中包括的数字为: ['4', '3', '5', '4', '5', '4', '6', '8', '7', '6', '4', '5']
字符串中包括的非数字为: ['s', 'd', 'a', 's', 'd', '+', 'd', 'a', '.', 's', '@', '%', '#', 'A', 'd', '%']
字符串中包括的字母为: ['s', 'd', 'a', 's', 'd', 'd', 'a', 's', 'A', 'd']
字符串中包括的特殊字符为: ['+', '.', '@', '%', '#', '%']

1.2.8 起始位置和单行/多行模式

^除了表示以外,还能用于表示文本的开头

text = '''001-苹果价格-10
002-橙子价格-12
003-香蕉价格-16'''

获取每一类水果的标号:

start_tags = re.compile(r'(^/d+)')
    for tag in start_tags.findall(text):
        print(tag)

输出为: 001,也就是只输出了第一行

要分别输出每一行的标签,需要用到多行模式re.M

text = '''001-苹果价格-10/n002-橙子价格-12/n003-香蕉价格-16'''

上述方法适用于在前面def了新的函数,造成text的异常缩进,因为多行模式并不能处理缩进。

start_tags = re.compile(r'^/d+', re.M)
    for tag in start_tags.findall(text):
        print(tag)

输出为:

001
002
003

$用来表示单行/多行模式的末尾匹配

上例获取价格:

end_price = re.compile(r'(/d+$)', re.M)
for price in end_price.findall(text):
    print(price)

输出结果

10
12
16

注意的是,如果是单行模式,但文本是多行,那么用$匹配定位到的是整个文本的最后一行。

1.2.9 组选择()

用以选择==某一类符合特征==的字符串

苹果苹果是绿色的
橙子橙子是橙色的
香蕉香蕉是黄色的

从以上文本中提取水果类型,不包括:用组选择方法:r'(.*),'。逗号作为匹配标识符放在()外面,在匹配时不被选择。

import re
text = '''苹果,苹果是绿色的/n橙子,橙子是橙色的/n香蕉,香蕉是黄色的'''
fruits_match = re.compile(r'(.*),')
for fruit in fruits_match.findall(text):
    print(fruit)

输出结果为:

苹果
橙子
香蕉

当有多个组时:在上述文本中,分别提取水果==类型==和==颜色==,并输出结果:

fruits_match_multigroup = re.compile(r'(.*),.*(.色)')
for fruit_multi_group in fruits_match_multigroup.findall(text):
    print(fruit_multi_group)

结果输出为:

('苹果', '绿色')
('橙子', '橙色')
('香蕉', '黄色')

在多个组选择时,输出的结果为每一行匹配结果的==元组==,而不再是列表。

1.3 正则表达式的方法

1.3.1 切割字符串

字符串对象提供的.split()方法仅适用于简单的字符串分割情形,对于更加复杂、灵活的应用场景,可以用正则表达式中的切割方法re.split()

提取下面字符串中的名字:

text = '''关羽;张飞,赵云、马超,黄忠,   马岱/n  魏延,严颜'''
import re
    nameList = re.split(r'[;,、,/s]/s*', text)
    print(nameList)

输出结果为:

['关羽', '张飞', '赵云', '马超', '黄忠', '马岱', '魏延', '严颜']

上面正则表达式r'[;,、,/s]/s*'表示:首先对[...]中的符号进行匹配切割,然后加上后面的/s任意个空白字符(空格、换行、Tab等)。

1.3.2 Sub替换

def RegEx_subFunc(match):
    # Match对象的.group(0)返回的是整个匹配到的字符串
    src = match.group(0)

    # Match对象的.group(1)返回的是第一个组的内容
    number_subfunc = int(match.group(1)) + 6
    dest = f'av{number_subfunc}' # 字符串连接

    print(f'{src}替换为{dest}')
    # 返回的值就是最终替换的字符串
    return dest
def RegEx_match_replace():
    links = '''

    下面是这学期要学习的课程:

    <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>
    这节讲的是切割磁力线
    '''
    
    print_str = re.compile(r'av(/d*)/')  # r'/av(/d+?)/'
    print(print_str.findall(links))
    
    newStr = re.sub(r'av(/d*)', RegEx_subFunc, links)
    print(newStr)

输出结果为:

['66771949', '46349552', '90571967']
av66771949替换为av66771955
av46349552替换为av46349558
av90571967替换为av90571973


    下面是这学期要学习的课程<a href='https://www.bilibili.com/video/av66771955/?p=1' target='_blank'>点击这里边看视频讲解边学习以下内容</a>
    这节讲的是牛顿第2运动定律

    <a href='https://www.bilibili.com/video/av46349558/?p=125' target='_blank'>点击这里边看视频讲解边学习以下内容</a>
    这节讲的是毕达哥拉斯公式

    <a href='https://www.bilibili.com/video/av90571973/?p=33' target='_blank'>点击这里边看视频讲解边学习以下内容</a>
    这节讲的是切割磁力线
    

📘<<Part 01 | Part 03]>>