编程的时候经常需要处理字符串,大部分时候基本的字符串处理函数就可以解决问题。但遇到一些稍复杂的任务,要处理的字符串不是特定的,而是有一定的模式或规则的,就可以考虑使用正则匹配。把最近学的正则表达式知识粗浅的总结一下,欢迎指正。
正则表达式
正则表达式验证网站:https://regexr-cn.com
后面的示例都需要提前导入 re
模块:
import re
正则表达式常用于如下场景。
- 查找:从大量信息中快速提取指定内容。
- 替换:将指定格式的文本进行正则匹配查找,找到之后进行特定替换。
- 验证:表单提交时,进行用户名密码的验证。
先看几个例子:
# 导入模块
import re
# 搜索你想要的内容,并取出来
ret = re.search(r'\d{4}', '从1988到2022,从2023到2050')
print(ret.group()) # 1988
# 获取多个
ret = re.findall(r'从\d{4}', '从1988到2022,从2023到2050')
print(ret) # ['从1988', '从2023']
# 用()提取局部值
ret = re.findall(r'从(\d{4})', '从1988到2022,从2023到2050')
print(ret) # ['1988', '2023']
# 判断字符串是否包含你想要的内容
ret = re.search(r'(error|failure)', 'System log: Fatal error!')
print(ret.group()) # error
# 替换你想替换的内容
ret = re.sub(r'(blue|red)', 'nice','blue socks and red shoes')
print(ret) # nice socks and nice shoes
表达式
基本的正则表达式包含如下要素:(1)字符类(2)数量限定符(3)位置限定符(4)特殊符号。
(1)字符类
代码 | 说明 | 举例 |
---|---|---|
. | 匹配一个除换行(“\n”和"\r")外的任何字符。要匹配. 本身,要加转义符\ | abc. 可以匹配 abc6 或 abcd |
[] | 匹配括号中的任意一个字符 | [abc]d 可以匹配 ad,bd,cd;[0-9] 可以匹配 0,1,2,…,9 |
- | 在[]内表示字符范围内的任意一个字符 | [0-9a-fA-F] 可以匹配一位16进制数字 |
^ | 在[]内表示除括号内字符的其他任意一个字符(在句子的开头表示匹配开始位置) | [^xy]1 不能匹配x1,y1,可以匹配 a1,b1等 |
\w | 匹配字母或数字或下划线或汉字 | |
\W | 匹配一个 非 字母数字下划线或汉字 | |
\s | 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 | |
\S | 匹配一个 非 不可见字符,即匹配一个可见字符。等价于 [^\f\n\r\t\v]。 | |
\d | 匹配一个数字字符,等价于[0-9]。 | |
\D | 匹配一个 非 数字字符。等价于[^0-9] |
(2)数量限定符
语法 | 说明 | 举例 |
---|---|---|
* | 重复零次或更多次 | [0-9][0-9]* 匹配至少一位数字 |
+ | 重复一次或更多次 | [0-9]+ 匹配至少一位数字 |
? | 重复零次或一次,前一个字符出现或不出现 | ax? 匹配 a 或 ax |
{n} | 重复n次 | [1-9][0-9]{2} ,匹配 100 到 999 的整数 |
{n,} | 重复n次或更多次 | [1-9][0-9]{2,} ,匹配 100 及以上的整数 |
{n,m} | 重复n到m次 | [1-9][0-9]{1,2} ,匹配 10 到 999 之间的整数 |
{,m} | 最多重复m次 | [1-9][0-9]{,2} ,匹配 1 到 999 之间的整数 |
(3)位置限定符
语法 | 说明 | 举例 |
---|---|---|
^ | 匹配整个字符串(句子)的开始(在字符类 “[]” 里表示非(不匹配)的意思) | |
$ | 匹配整个字符串(句子)的结束 | ^\d\d\d$ 匹配三个全部都为数字的字符串,“123” |
\< | 匹配单词开头 | \<th 匹配 this,但不匹配 ethernet,tenth |
\> | 匹配单词结尾 | p\> 匹配 leap,但不匹配 parent,sleepy |
\<\> | 匹配词(word)的开始(\<)和结束(\>)。 | \<the\> 匹配"for the wise"中的"the",但不能匹配"otherwise"中的"the" |
\b | 匹配一个边界(单词+空格或换行符)。用于字符串开头或结尾 | er\b 匹配 never 中的 er,但不能匹配 verb 中的 er。 |
\B | 匹配 非 单词开头或结尾位置。匹配非单词边界。 | er\B 能匹配 verb 中的 er,但不能匹配 never 中的 er。 |
(4)特殊字符
语法 | 说明 | 举例 |
---|---|---|
\ | 转义字符。将普通字符和特殊字符之间进行互转。 | ‘\.’ 想匹配 . 这个特殊字符,需要转义。 |
() | 将一部分括起来组成一个单元,对单元使用数量限定符 | ([0-9]{1,3}\.){3}[0-9]{1,3} 匹配 IPv4 地址。最后一个数字后面没有. 。 |
| | 两个子表达式之间表示或的关系 | n(o|an) 匹配 no 或 nan |
(5)懒惰匹配
当限定符后紧跟一个 ‘?’ ,表示懒惰匹配
语法 | 说明 | 举例 |
---|---|---|
*? | 重复任意次,但尽可能少重复 | 如下示例 |
+? | 重复1次或更多次,但尽可能少重复 | |
?? | 重复0次或1次,但尽可能少重复 | |
{n,m}? | 重复n到m次,但尽可能少重复 | |
{n,}? | 重复n次以上,但尽可能少重复 |
# a.*b 表示以 a 开头,中间尽量多的字符,以 b 结尾
ret = re.search('a.*b','aaaabbbb')
print(ret.group()) # aaaabbbb
# a.*?b 表示以 a 开头,中间尽量少的字符,以 b 结尾
ret = re.search('a.*?b','aaaabbbb')
print(ret.group()) # aaaab
分组
语法 | 作用 | 举例 |
---|---|---|
(表达式) | 搜索时匹配一个分组 | 如下示例 |
(表达式1|表达式2) | 分组内多个表达式之间,取或 | 如下示例 |
(?:表达式) | 搜索时匹配,但不计入分组 | 如下示例 |
(?P<name>表达式) | 分组别名。group时可以根据别名取数 | 如下示例 |
(?=表达式) | 前向(右)肯定断言 | 如下示例 |
(?!表达式) | 前向否定断言 | |
(?<=表达式) | 后向(左)肯定断言 | 如下示例 |
(?<!表达式) | 后向否定断言 | 如下示例 |
分组
(表达式)
使用()实现分组机制,除了获得整个匹配,还能在匹配中选择每一个分组。
import re
html = '''
</div>
<div class="small_wrap j_small_wrap">
<a rel="noreferrer" href="#" οnclick="return false;" class="small_btn_pre j_small_pic_pre" style="display:none"></a>
<a rel="noreferrer" href="#" οnclick="return false;" class="small_btn_next j_small_pic_next" style="display:none"></a>
<div class="small_list j_small_list cleafix"></div>
<div class="small_list_gallery"></div>
</div>
</div>
'''
# 添加 ”()“,增加分组功能,匹配到整个字符串后,仅提取括号内目标值
res = re.findall('<div class="(.*?)">', html)
print(res) #['small_wrap j_small_wrap', 'small_list j_small_list cleafix', 'small_list_gallery']
# 如果去掉 ”()“,提取整个匹配字符串
res = re.findall('<div class=".*?">', html)
print(res) # ['<div class="small_wrap j_small_wrap">', '<div class="small_list j_small_list cleafix">', '<div class="small_list_gallery">']
分组内的或表达式
(表达式1 | 表达式2 | 表达式3)
使用()实现分组机制时可以使用 | 关系
import re
# 提取文件后缀
res = re.findall('(\.png|\.mpeg|\.bmp)', "pic.bmp and pic1.png")
print(res) # ['.bmp', '.png']
# 提取文件名和文件后缀
res = re.findall('(\w+)\.(png|mpeg|bmp)', "pic.bmp and pic1.png")
print(res) # [('pic', 'bmp'), ('pic1', 'png')]
非捕获分组
(?:表达式)
使用()实现分组机制,但只想取部分括号里的数据。
import re
res = re.findall('(?:\w*)-(\d*)', "abcd-12, asw-345")
print(res) # ['12', '345']
res = re.findall('(\w*)-(\d*)', "abcd-12, asw-345")
print(res) # [('abcd', '12'), ('asw', '345')]
分组取别名
(?P<name>表达式)
为分组取别名,方便引用
import re
sentence = 'cats are fast'
regex = re.compile('(?P<animal>\w+) (?P<verb>\w+) (?P<adjective>\w+)')
matched = re.search(regex, sentence)
print(matched.groupdict()) #{'animal': 'cats', 'verb': 'are', 'adjective': 'fast'}
print(matched.group('adjective')) # fast
print(matched.group(0)) #cats are fast
print(matched.group(1)) # cats
print(matched.group(2)) # are
print(matched.group(3)) # fast
print(matched.group(4)) # 错误 no such group
断言
(?=表达式)
往前面的文字(向右)看是否匹配括号里的内容,但实际不获取,只做比较
import re
string = "I'm singing while you're dancing."
# \b\w+(?=ing\b),表示匹配以 ing 结尾的单词的前面的部分(除ing以外的部分)。
ret = re.findall(r'\b\w+(?=ing\b)', string)
print(ret) # ['sing', 'danc']
(?<=表达式)
往后面的文字(向左)看是否匹配括号里的内容,但实际不获取,只做比较
import re
string = "ID:1234, Graduate:2014, Birth:1988"
# (?<=Birth):(\d{4}),表示匹配前面为 Birth 的 : 之后的4个数字
ret = re.search(r'(?<=Birth):(\d{4})', string)
print(ret.group(1)) # 1988
Python 函数
下面列举下 Python re 模块中常用的几个正则函数:
compile()
re.compile (pattern, flags=0)
把正则表达式语法转化成正则表达式对象,返回一个 re.Pattern 类型的值,供 match() 和 search() 这两个函数使用(其实这些函数也可以直接使用字符串)。
pattern:一个字符串形式的正则表达式
flags:可选,表示匹配的模式,模式如下
re.I:忽略大小写
re.L:表特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
re.M:多行模式
string_pattern = re.compile(r'\d{4}')
re.search(string_pattern, '从1988到2022')
或者
re.search(r'\d{4}', '从1988到2022')
search()
re.search (pattern, string, flags=0)
扫描整个字符串来查找匹配,一旦找到第一个匹配对象就停止工作,返回值为 match 对象(使用 match.group() 提取匹配字符),没找到返回None
year_pattern = re.compile(r'\d{4}')
string1 = '从1988到2022'
match2 = re.search(year_pattern, string1)
print(match2.group()) # 1988
match()
re.match (pattern, string, flags=0)
从字符串开头的第一个字符开始匹配,也就是只报告从位置 0 开始的匹配情况。如果找到返回match对象(使用match.group()提取匹配字符),没找到返回None。
year_pattern = re.compile(r'\d{4}')
string1 = '从1988到2022'
match1 = re.match(year_pattern, string1)
print(match1) # None
match() 和 search() 都只返回一个匹配对象。findall() 会返回多个匹配对象。
findall()
pattern.findall (string)
re.findall (pattern,string)
匹配到全部以列表返回,返回值为字符串数组,失败返回None
string = '从1988到2022'
string_pattern = re.compile(r'\d{4}')
res = string_pattern.findall(string)
print(res) # ['1998', '2022']
string = '从1988到2022'
string_pattern = re.compile(r'\d{4}')
res = re.findall(string_pattern, string)
print(res) # ['1998', '2022']
split()
re.split (pattern, string, maxsplit=0, flags=0)
按照匹配的字符串进行分割
string = '1cat2dog3pig4'
res = re.split(r'\d+', string)
print(res) #['', 'cat', 'dog', 'pig', '']
sub()
re.sub (pattern, repl, string, count=0, flags=0)
正则的替换,返回替换之后的字符串。3个必选参数,2个可选参数。
pattern,表示正则中的模式字符串
repl,用来替换的字符串
string,要被处理,要被替换的字符串
count,替换次数,默认0是全部替换
flags,
p = re.compile('(blue|red)')
print(p.sub('nice','blue socks and red shoes’)) # nice socks and nice shoes
print(p.sub('nice','blue socks and red shoes’, count=1)) # nice socks and red shoes
# 把数字替换成字符串
st = "hello 2022"
st = re.sub("([0-9]+)","panda",st)
print(st) # hello panda
# 把连续2个a替换成1个a
st = "hello aabbaa"
st = re.sub("(a{2})","a",st)
print(st) # hello abba
flags
语法 | 描述 | 举例 |
---|---|---|
re.I | 不区分大小写 | |
re.L | 做本地化识别(locate-aware)匹配 | |
re.M | 多行匹配,影响 ^ 和 $ | |
re.S | 使 . 匹配包括换行符在内的所有字符 | |
re.U | 根据 Unicode 解析字符,影响 \w, \W, \b, \B. |
# 忽略大小写
res = re.search('[a-z]+', 'aSdf\dx', flags=re.I)
print(res.group()) # 结果为 aSdf,如果去掉 re.I,则返回 a。
# 多行模式,其中\n是换行符
res = re.search('^df', '\ngqwqw\ndfas\nadf\nasdf', flags=re.M)
print(res.group()) # 结果为 df,如果去掉 re.M,则返回值为 None。
# 匹配任意字符
res = re.search('.', '\ngqwqw\ndfas\nadf\nasdf', flags=re.S)
print(res.group()) # 结果为 “换行符\n”,也就是一个空白行。如果去掉 re.S,则匹配到 g 。
常用正则表达式
只能输入数字:"^[0-9]*$"。
只能输入n位的数字:"^\d{n}$"。
只能输入至少n位的数字:"^\d{n,}$"。
只能输入m~n位的数字:。"^\d{m,n}$"
只能输入零或非零开头的数字:"^(0|[1-9][0-9]*)$"。
只能输入有两位小数的正实数:"^[0-9]+(.[0-9]{2})?$"。
只能输入有1~3位小数的正实数:"^[0-9]+(.[0-9]{1,3})?$"。
只能输入非零的正整数:"^\+?[1-9][0-9]*$"。
只能输入非零的负整数:"^\-[1-9][0-9]*$"。
只能输入长度为3的字符:"^.{3}$"。
只能输入由26个英文字母组成的字符串:"^[A-Za-z]+$"。
只能输入由26个小写英文字母组成的字符串:"^[a-z]+$"。
只能输入由数字和26个英文字母组成的字符串:"^[A-Za-z0-9]+$"。
只能输入由数字、26个英文字母或者下划线组成的字符串:"^\w+$"。
验证用户密码:"^[a-zA-Z]"w{5,17}$"正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。
只能输入汉字:"^[\u4e00-\u9fa5]{0,}$"
验证Email地址:"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"。
验证InternetURL:"^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"。
验证电话号码:"^(\d{3,4}-)?\d{7,8}$"正确格式为:“XXX-XXXXXXX”、“XXXX- XXXXXXXX”、“XXX-XXXXXXX”、“XXX-XXXXXXXX”、“XXXXXXX” 和 “XXXXXXXX”。
验证身份证号(15位或18位数字):"^\d{15}|\d{18}$"。
参考: