正则表达式详解

1、简介

正则表达式(Regular expressions 也称为 REs,或 regexes 或 regex patterns)本质上是一个微小的且高度专业化的编程语言。这个概念最初是由 Unix 中的工具软件(例如 sed 和grep)普及开的。

正则表达式是对 字符串操作 的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。给定一个正则表达式和另一个字符串,可以通过正则表达式从字符串中获取我们想要的特定部分。正则表达式灵活性、逻辑性和功能性非常强,可以迅速地用极简单的方式达到字符串额复杂控制,但对于共接触的人来说比较晦涩难懂。由于 正则表达式主要应用对象是文本,因此他在各种文本编辑器场合都有应用。

Python 的正则表达式引擎是用 C 语言写的,所以效率是极高的。可以帮我们处理 98.3% 的文本任务。

1.1 处理字符串主要有四大功能

  • 匹配 查看一个字符串是否符合正则表达式的语法,一般返回 true 或者 false

  • 获取 正则表达式来提取字符串中符合要求的文本

  • 替换 查找字符串中符合正则表达式的文本,并用相应的字符串替换

  • 分割 使用正则表达式对字符串进行分割。

2、字符匹配与获取

2.1 开始使用

python 中,使用re 库来调用正则匹配

import re

2.2 常用匹配函数

函数 解释
re.match(pattern, string, flags) 返回 string 中所有与 pattern 相匹配的 第一个 字符串的 Match 对象
如果字符串没有匹配,则返回 None。
只从字符串的开始匹配字符。要求 pattern 位于 string 的开头
re.search(pattern, string, flags) 返回 string 中所有与 pattern 相匹配的 第一个 字符串的 Match 对象
如果字符串没有匹配,则返回 None。
匹配整个字符串,直到找到一个匹配的对象
re.findall(pattern, string, flags) 返回 string 中所有与 pattern 相匹配的 全部字符串 对象
返回形式为数组
re.finditer(pattern, string, flags) 返回 string 中所有与 pattern 相匹配的 全部字串 对象
返回形式为迭代器
re.compile(pattern, flags) 函数根据一个模式字符串和可选的标志参数 返回一个正则表达式对象

pattern:匹配的正则表达式模板

string:输入要匹配的字符串

flags:标志位,用于控制正则表达式的匹配方式

2.2.1 Match object 方法

match() 和 search() 方法一旦匹配成功,就是一个 match object 对象,而 match object 对象有以下方法:

  • group() 返回被 RE 匹配的字符串,默认 group(0)
  • start() 返回匹配开始的位置
  • end() 返回匹配结束的位置
  • span()返回一个元组包含匹配 (开始, 结束) 的位置

2.2.2 compile

在直接使用字符串表示的正则表达式进行 search, match 和 findall 操作时,python 会将字符串转换为正则表达式对象。而使用 compile 完成一次转换之后,在每次使用模式的时候就不用重复转换。

当需要大量匹配时,为了节省匹配时间,可以使用 re.compile 先预处理。

import re

# 将正则表达式编译成 Pattern 对象
pattern = re.compile(r'\d+')

# 使用 Pattern 对象的 findall 方法在字符串中查找匹配的内容
result = pattern.findall('hello 123 world 456')
print(result)  #['123', '456']

编译后的 Pattern 对象提供了一系列方法用于匹配和查找文本,包括:

  • match(string): 从字符串的起始位置匹配正则表达式。

  • search(string): 扫描整个字符串并返回第一个成功的匹配。

  • findall(string): 返回字符串中所有非重叠匹配的列表。

  • finditer(string): 返回一个迭代器,产生字符串中所有非重叠匹配的 Match 对象。

  • split(): 根据匹配的子串将字符串分割。

  • sub(): 替换匹配的子串。

  • subn(): 替换匹配的子串并返回替换次数。

后面三个在第 3 章有介绍。

2.3 字符匹配模式

正则表达式 pattern 有两种匹配模式:

直接匹配:给啥匹配啥,完全匹配,不适用替代符(元字符)

元字符匹配:利用内置已定义的一类特殊字符,表达特定含义,进行匹配搜索。

2.3.1 直接匹配

import re
r=re.findall('Mi','Mike is Mifan,is him? ')
print(r)
# 输出结果如下
['Mi', 'Mi']

rs=re.search('Mi','Mike is Mifan,is him? ')
print(rs)   #输出:<re.Match object; span=(0, 2), match='Mi'>
print(rs.group())  # 输出:Mi

2.1.2 元字符样式

首先一定要记住以下元字符及其代表的含义:

上述元字符中,有的字符 一个符号可以匹配多次,但是我们很多时候需要它匹配到一定时候停下来,这个时候就涉及两个概念:

  • 贪婪模式:正则表达式一般趋向于最大长度匹配,默认形式。

  • 非贪婪模式:在满足匹配时,匹配尽可能短的字符串。匹配到一块字符(可以为空),再去匹配下一处。

举例说明:

import re

str = "Total CPU time == 62361263, Real Time = 64321"

match_res1 = re.search(r"Real Time.*(\d+)", str)    #默认模式, 贪婪模式
match_res2 = re.search(r"Real Time.*?(\d+)", str)   #非贪婪模式,加?

if match_res1:
    time = match_res1.group(1)
    print(time)   #贪婪模式输出:1

if match_res1:
   time = match_res2.group(1)
   print(time)   #贪婪模式输出:64321

贪婪模式下, .*会一致匹配任何字符直到最后,最后留了末尾 1 给到\d+,小括号的作用是界定返回值的范围

2.4 特殊字符扩展

还有反斜杠带来的扩展:

2.5 标志位

还有标志位 flags,用于控制正则表达式的匹配方式:

最常用的可能是用标志位来控制 忽略大小写

import re

str = "Total CPU time == 62361263, Real Time = 64321"

match_res = re.search(r"real time.*?(\d+)", str, re.IGNORECASE)

if match_res:
    time = match_str.group(1)
    print(time)  #输出:64321

re.IGNORECASE 表示忽略匹配模板了忽略了大小写,如上所示,我们仍能匹配返回到 64321

2.6 举例

import re
str='Mike is Mifan,his age is 180 years old,and his weight is about 140 pound.'

rs = re.search('is about (.*[.]$)',str)
print(rs)
print(rs.group())
print(rs.group(1))
// 输出结果如下
<re.Match object; span=(54, 73), match='is about 140 pound.'>
is about 140 pound.
140 pound.


r=re.findall('his age is (\d+)',str)
print(r)
// 输出结果如下
['180']

r=re.findall('his age is \d+',str)  // 与上对比,可说明 () 的作用
print(r)
// 输出结果如下
['his age is 180']

r=re.findall('his age is (\d+?)',str)    // 问号前的 \d+ 由于问号,整体变成非贪婪模式
print(r)
// 输出结果如下
['1']

r=re.findall('his age is (.+)',str)  //. 会匹配任何值,再加上 +,则会贪婪匹配后面所有内容
print(r)
// 输出结果如下
['180 years old,and his weight is about 140 pound.']

r=re.findall('\d+',str)
print(r)
// 输出结果如下
['180', '140']

r=re.findall('\d',str)
print(r)
// 输出结果如下
['1', '8', '0', '1', '4', '0']

r=re.findall('is about (.*[.]$)',str)   //[]里填其他字符无效,会取出空值
print(r)
// 输出结果如下
['140 pound.']


r=re.findall('his (.*)',str)
print(r)
rs=re.findall('his (.*) years',str)   // 这里的 '[空格]years' 有拦截效果
print(rs)
ra=re.findall('his (.*?) years',str)  
print(ra)
// 输出结果如下
['age is 180 years old,and his weight is about 140 pound.']
['age is 180']
['age is 180']
// 上面第三个匹配多一个问号的区别在于是非贪婪模式,匹配到一个 'his (.*?) years' 就停止匹配;
// 贪婪模式会输出所有符合 'his (.*) years' 的结果

r=re.findall('his (.*?) ',str)  // 注意括号后有空格,若去掉空格,则返回为空
print(r)
// 输出结果如下
['age', 'weight']

小心空格,一不小心就成了拦截符!,对非贪婪匹配结果有大影响。

3、字符替换与分割

3.1 方法使用

首先将正则表达式的字符串创建成模式对象,再来使用:

rx = re.compile(pattern, flags)

  1. rx.split(string,maxsplit)

    在正则表达式匹配的地方进行分割,并返回一个列表。

  2. rx.sub(replacement, string, count)

    返回替换后的字符串,这个字符串从最左边开始,所有 RE 匹配的地方都替换成 replacement

  3. rx.subn(replacement, string, count)

    返回值为一个包含有两个元素的元组:替换后的字符串,替换的数目

4、常用的数据格式匹配

邮箱地址(Email)

pattern = r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"

域名(domain)

pattern = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"

IP 地址

pattern = r"((?:(?:25[0-5]|2[0-4]\d|(?:1\d{2}|[1-9]?\d))\.){3}(?:25[0-5]|2[0-4]\d|(?:1\d{2}|[1-9]?\d)))"

电话号码

pattern = r"\d{3,4}-\d{7,8}"

手机号码

pattern = r"(?:13[0-9]|15[0-9]|17[0678]|18[0-9]|14[57]|09){1}[0-9]{8}"

身份证号码(18 位)

pattern = r"(?:[1-9]\d{5}[12]\d{3}(?:0[1-9]|1[012])(?:0[1-9]|[12][0-9]|3[01])\d{3}[0-9xX])"

中文字符

pattern = r"[\u4e00-\u9fa5]"

欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net