通过Python学习正则表达式(regular expression)
虽然我之前也学习过正则表达式,当然是通过Java语言来学习的。但是苦于使用场景不多,所以在学习完毕后,总是遗忘,当然也怪自己做的笔记不够完善啦,所以今天就借着学习Python的同时,也重温一下正则表达式(准确的说,正则表达式也是学习爬虫的基础呀)
当然因为正则表达式的知识点也不少,所以我这里呢,只重点说一些常见的方法和知识
正则表达式实在是太灵活了,一段时间不用,就很容易忘记,所以,多用,多用,要多用!
- 前提概要
- 什么是正则表达式
- 正则语法
- 元字符
- 运算符优先级
- Python函数
- re.findall
- re.sub
- re.search
- re.match
- Python实例
- 参考资料
前提概要
什么是正则表达式?
正则表达式式一个特殊的字符序列,可以通过正则表达式去判断一个字符串是否与我们所设定的字符序列模式相匹配,还可以做一些快速检索文本,替换文本的操作
菜鸟教程
构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
- 检查一串数字是否是电话号码
- 检查一串字符串是否符合邮箱地址格式
- 将一个文本里指定的单词替换成另外一个单词
正则语法
元字符
元字符是区别于普通字符的一种语法或表示符号,属于特殊的字符,像我们平时的字符a,b,c,d
等就是普通字符,*,\
等符号就是元字符
非打印字符
即一些非打印的字符,即看不见的字符,如空格,换行,回车,换页等符号
字符 | 描述 |
---|---|
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\s | 匹配任意空白字符,包括空格,制表符,换页符等,等价于[\f\n\r\t\v] |
\S | 匹配任意非空白字符,等价于[^\f\n\r\t\v] |
\t | 匹配一个制表符 |
\v | 匹配一个垂直制表符 |
限定符(词量)
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配,比如*
,+
等
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}, 即* 号前面的o可以出现0次或多次 |
+ | 匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,},即+ 号前面的o可以出现1次或多次 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 、 “does” 中的 “does” 、 “doxy” 中的 “do” 。? 等价于 {0,1},即? 号前面的o可以出现0次或1次 |
{n} | n 是一个非负整数(包括0)。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o |
{n,} | n 是一个非负整数(包括0)。至少匹配n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ‘o*’ |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格 |
定位符(边界匹配)
定位符使正则表达式固定到行首,行尾,在一个单词内,在一个单词的开头或者一个单词的结尾
字符 | 描述 |
---|---|
^ | 匹配输入字符串的开始位置,代表匹配子串必须在整个字符串的头部,如在[]括号内使用,则是取反的意思 |
$ | 匹配输入字符串的结束位置,代表匹配子串必须在整个字符串的尾部 |
\b | 匹配一个单词边界,即字与空格间的位置,代表匹配字符在单词的边界,字符匹配但不在边界也属于不匹配 |
\B | 非单词边界匹配,代表匹配字符不在单词的边界 |
普通字符集合的符号
字符 | 描述 |
---|---|
. | 匹配除换行符\n外的任意单个字符 |
\d | 匹配一个数字字符,等价[0-9] |
\D | 匹配一个非数字字符,等价 [^0-9] |
\w | 匹配一个字母(含大小写),数字,下划线中的一个字符,等价[A-Za-z0-9_] |
\W | 匹配非字母、数字、下划线中的一个字符。等价于 ‘[^A-Za-z0-9_]’ |
[a-z] | 匹配小写字母的一个字符 |
[^a-z] | 匹配非小写字目的所有字符中的一个字符 |
语法符号
字符 | 描述 |
---|---|
() | 小括号,标记一个子表达式的开始和结束位置,小括号内部的元素都是且 关系 |
[] | 中括号,中括号内部是要匹配的字符集合,中括号中的元素都是或 关系 |
{} | 大括号,大括号内部表示限定符 |
| | 代表或的意思,x|y 代表匹配x或y |
当然还有其他的一些元字符表示,但是这里只列举出常见,常用的一些字符表示
运算符优先级
正则表达式的运算符优先级表示:
- 同优先级,从左到右,优先级逐渐降低
- 不同优先级,从上到下,优先级逐渐降低
字符 | 描述 |
---|---|
\ | 转义符 |
(), (?:), (?=), [] | 圆括号和方括号 |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \任何元字符、任何字符 | 定位点和序列 |
| | 替换,"或"操作 |
正则特性
贪婪与非贪婪匹配
python语言中,默认是贪婪匹配
贪婪匹配:
import re
string = 'python 1111java678php'
result = re.findall('[a-z]{3,6}',string)
print(result)
"""
output:
['python', 'java', 'php']
"""
非贪婪匹配:
import re
string = 'python 1111java678php'
result = re.findall('[a-z]{3,6}?',string)
print(result)
"""
output:
['pyt', 'hon', 'jav', 'php']
"""
- 非贪婪表示法就是在
*
,+
,?
,{n,m}
限定符后面加上一个?
。这个要区别?
作为限定符的用法
Python函数
findall()
import re
string = 'Java | Golang | Python | Php | C# | C++ | golang | C++ | Golang'
result = re.findall('\w{3,6}', string)
print(result)
"""
output:
['Java', 'Golang', 'Python', 'Php', 'golang', 'Golang']
"""
findall()
的作用是,在目标字符串string
中查找符合正则表达式描述的所有字符子串(贪婪模式)findall()
返回的是一个list
列表
finditer()
import re
string = 'Java | Golang | Python | Php | C# | C++ | golang | C++ | Golang'
result = re.finditer('\w{3,6}', string)
for a in result:
print(a.group())
"""
output:
Java
Golang
Python
Php
golang
Golang
"""
finditer()
也是在目标字符串string
中遍历符合正则表达式的字符子串finditer()
返回的是一个迭代器,需要遍历输出,result
是re.Match
类型
sub()
忽略大小写替换字符串
import re
string = 'Java | Golang | Python | Php | C# | C++ | golang | C++ | Golang'
string = re.sub('Golang|Python', 'Java', string, 0,re.I)
print(string)
"""
output:
Java | Java | Java | Php | C# | C++ | Java | C++ | Java
"""
sub()
的作用是替换,在目标字符串中,所有匹配正则表达式的子串都被某字符串所替代count
参数默认为0,代表无限替换flags
是匹配模式,re.I是忽略大小写模式
search()
import re
string = 'Java | Golang | Python | Php | C# | C++ | golang | C++ | Golang'
result = re.search('\w{3,6}', string)
print(result)
"""
output:
<re.Match object; span=(0, 4), match='Java'>
"""
re.search()
的作用是从目标字符串寻找一个匹配正则表达式的子串,前面不匹配就往后面匹配- 不同于
re.match
, match是只要首部不匹配,就不再匹配,直接返回None
match()
import re
string = 'Java | Golang | Python | Php | C# | C++ | golang | C++ | Golang'
result = re.match('\w{3,6}', string)
print(result)
"""
output:
<re.Match object; span=(0, 4), match='Java'>
"""
re.match
尝试从目标字符串的起始位置匹配正则表达式,如果匹配则返回一个匹配子串,如果不匹配,不再往后继续,直接返回None
Python例子
把字符串中的数字全部提取出来
import re
number = 'ASDFC134WF423FDASFasdf6567e19j2e812je80j'
result = re.findall('\d',number)
print(result)
"""
output:
['1', '3', '4', '4', '2', '3', '6', '5', '6', '7', '1', '9', '2', '8', '1', '2', '8', '0']
"""
判断一串数字是否符合QQ号的格式
我们这里做一个简单的QQ号格式,开头不为0的6~8位的数字
错误示范:
import re
number = 106768908
result = re.findall('[1-9]\d{5,7}', str(number))
print(result)
"""
output:
['10676890']
"""
- 发现匹配上了,这就是有问题的,我们的目的是利用正则判断某串数字是否符合QQ号的格式。而不是在这串数字中寻找符合QQ号格式的子串。当然曲线救国的方式就是拿符合条件的子串与原字符判断是否相等,但是太多余
- 所以我们要做全字符串的匹配,而不是寻找,所以就要用上边界匹配
正确示范:
import re
number = 106768908
result = re.findall('^[1-9]\d{5,7}$', str(number))
print(result)
"""
output:
[]
"""
- 前面加了
^
,代表寻找首部满足[1-9]\d{5,7}
的子串,再加上$代表寻找满足以[1-9]\d{5,7}
结尾的子串,综和其来就是全字符匹配
判断邮箱地址是否满足谷歌邮箱的格式
- 简单的非正式的判断,基本条件@前缀可以由字符和数字以及
.
组成,但是不能只有.
或以.
开头
import re
string = 'snailmann.me@gmail.com'
result = re.match('^[a-zA-Z0-9]+[.]?[a-zA-Z0-9]+@gmail\.com$', string)
print(result)
"""
output:
<re.Match object; span=(0, 22), match='snailmann.me@gmail.com'>
"""
找到两个字符串中间的所有字符
import re
string = 'snailmann.me@gmail.com'
result = re.findall('snailmann(.*)\.com',string)
print(result)
"""
output:
['.me@gmail']
"""
匹配中文字符
line = "I am 广东人"
result = re.findall('.*?([\u4E00-\u9FA5]+)', line)
print(result)
"""
output:
['广东人']
"""
[\u4E00-\u9FA5]
包含了所有中文字符- 因为()前边有个
.*
,所以为了达到我们想要的效果,我们需要在限定符后面加个?
,保证前面的正则是非贪婪匹配,如果不加?
,.*
则是贪婪匹配。最终我们要匹配的()内部的正则表达式则只能匹配到['人']
,这就不是我们要的效果了。当然这里只是为了说明贪婪匹配的影响,如果单单是向匹配中文字符,正则表达式直接[\u4E00-\u9FA5]+
即可