文章目录
正则表达式
定义
正则表达式是一个特殊的字符序列,他能够帮我们检测一个字符串是否与我们所设定的字符序列相匹配。
可以快速检索文本,实现替换文本的操作。
作用
1、检查一串数字是否是电话号码
2、检测一个字符是否符合email格式
3、把一个文本里指定的单词替换为另外一个单词
一、初识正则表达式
1、findall方法:检测一串字符串是否包含指定的字符
findall(“正则表达式(pattern)”,字符串),返回想要匹配的字符串列表,如果有就返回,如果没有返回空列表
;all代表的意思找寻全部的
# a = 'C|C++|JAVA|C#|Python|Javascript'是否包含python
# 利用python内置函数index,返回首次出现的位置
a = 'C|C++|JAVA|C#|Python|Javascript|Python'
print(a.index('Python')>-1)
# 利用In
print('Python' in a)
True
True
简单的判断可以优先使用python内置函数,现在我们使用re
import re
r = re.findall('Python', a)
print(type(r), r)
<class 'list'> ['Python', 'Python']
# 所以完整的解决
import re
a = 'C|C++|JAVA|C#|Python|Javascript|Python'
r = re.findall('Python', a)
if len(r) > 0:
print("字符串包含Python")
else:
print("No")
字符串包含Python
findall的返回结果几乎是没有意义的,原因是我们指定的是一个常量字符串,没有发挥正则表达式匹配的特性表达出来,正则最重要的是在于规则;如果我们把正则pattern设置为常量,是没有意义的。正则表达式存在的意义是在于规则
二、元字符与普通字符
1、 \d : 表示0-9的数字
a = ‘C0C++7JAVA8C#9Python8Javascript’,我现在问题变了,不在提取常量了,我想把字符串的数字提取出来。解决方式可以使用for循环遍历,但并不简洁
import re
a = 'C0C++7JAVA8C#9Python8Javascript'
re.findall('\d', a)
['0', '7', '8', '9', '8']
像“Python”这样一个具体的字符叫做普用字符;“\d”称之为元字符,我们学习正则就是学习各种各样的元字符
2、 \D:表示非数字字符(空格也属于非数字)
import re
a = 'C0C++7J8C#9Pth8Japt '
re.findall('\D', a)
['C', 'C', '+', '+', 'J', 'C', '#', 'P', 't', 'h', 'J', 'a', 'p', 't', ' ']
更多的元字符不需要强制记忆,用到啥去查就好。我们重点关注的是什么?是模式!什么是模式?比如如何匹配任意字符任意次,如何匹配任意字符至少一次等等的匹配规则
三、第一个模式:字符集
1、[ ]代表或者,中括号里面填入普通字符或元字符
现在有一个字符串,我想要找到单词的中间是c或者f的单词(关键点是“或者”)
;s = “abc, acc, adc, aec, afc, ahc”
import re
s = "abc, acc, adc, aec, afc, ahc"
re.findall('a[cf]c', s)
['acc', 'afc']
# 看一下这个效果;
import re
s = "abc, acc, adc, aec, afc, ahc"
re.findall('[cf]', s) # 其实中括号外面的两个普通字符有着定界的作用
['c', 'c', 'c', 'c', 'c', 'f', 'c', 'c']
2、^ 非:[^字符]
import re
s = "abc, acc, adc, aec, afc, ahc"
re.findall('a[^cf]c', s) # 匹配中间不是cf的字符
['abc', 'adc', 'aec', 'ahc']
3、 a-c:表示a到c
import re
s = "abc, acc, adc, aec, afc, ahc"
re.findall('a[c-f]c', s) # 匹配从c到f的字符
['acc', 'adc', 'aec', 'afc']
四、概括字符集
“\d”就是一个概括字符集,他可以表示0-9的数字,其实也可以用[0-9]来表示;“\D”表示非数字,也可以用[^0-9]
import re
s = '1sas9999djncfn'
print(re.findall('[0-9]', s))
print(re.findall('\d', s))
['1', '9', '9', '9', '9']
['1', '9', '9', '9', '9']
1、\w:匹配是数字+单词字符+下划线的字符串;\w = [A-Za-z0-9_]
import re
s = '1sas9999djnc @#¥%fn_'
print(re.findall('[0-9]', s))
print(re.findall('\w', s))
print(re.findall('[A-Za-z0-9_]', s))
['1', '9', '9', '9', '9']
['1', 's', 'a', 's', '9', '9', '9', '9', 'd', 'j', 'n', 'c', 'f', 'n', '_']
['1', 's', 'a', 's', '9', '9', '9', '9', 'd', 'j', 'n', 'c', 'f', 'n', '_']
我们匹配的只是单一的字符,不是字符串
2、\W:匹配非数字+单词字符+下划线的字符串
import re
s = '1sas9999djnc @#¥%fn_\n '
print(re.findall('\W', s))
print(re.findall('[^A-Za-z0-9_]', s))
[' ', ' ', '@', '#', '¥', '%', '\n', ' ']
[' ', ' ', '@', '#', '¥', '%', '\n', ' ']
注意的:\n:回车,空格,\t制表符属于非单词字符
3、\s:匹配空白字符,空格、回车\n,换行,制表符
import re
s = '1sas9999djnc @#¥%fn_\n \t \r'
print(re.findall('\s', s))
[' ', ' ', '\n', ' ', '\t', ' ', '\r']
4、\S:匹配非空白字符
import re
s = '1sas9999djnc @#¥%fn_\n \t \r'
print(re.findall('\S', s))
['1', 's', 'a', 's', '9', '9', '9', '9', 'd', 'j', 'n', 'c', '@', '#', '¥', '%', 'f', 'n', '_']
5、 . 表示除换行符\n之外其他所有的字符
五、数量词{3},{}前面的那个字符出现3次
之前我们的方式都是匹配的单个,但是如果我们线稿字符串的怎么办
import re
a = 'python 11 java8php'
re.findall('[a-z]',a)
['p', 'y', 't', 'h', 'o', 'n', 'j', 'a', 'v', 'a', 'p', 'h', 'p']
import re
a = 'python 11 java8php'
re.findall('[a-z][a-z][a-z]',a) # 这种方式是匹配了3个,但是要匹配100个,是不可取的。我们可以用{}表示
['pyt', 'hon', 'jav', 'php']
import re
a = 'python 11 java8php'
re.findall('[a-z]{3}',a)
['pyt', 'hon', 'jav', 'php']
但这也不是我们想要的,他只能让我们匹配3个字母字符,每个单词的范围是不一样的
import re
a = 'python 11 java8php'
re.findall('[a-z]{3,6}',a) # 匹配3,4,5,6都可以
['python', 'java', 'php']
{3,6}匹配3,4,5,6都可以,这个属于叫做贪婪操作,匹配到3的时候还会继续往后找,直到不满足他的条件为止
六、贪婪与非贪婪(?)(默认倾向于贪婪,倾向于匹配最多的)
# ?表示非贪婪的模式
import re
a = 'python 11 java8php'
re.findall('[a-z]{3,6}?',a)
['pyt', 'hon', 'jav', 'php']
七、*:匹配0次1次或者无限次; +:匹配1次或者无限次;?:匹配0次或者1次
import re
a = 'pytho0python1pythonn2 11 java8php'
re.findall('python*',a)
['pytho', 'python', 'pythonn']
为什么会出现这种情况,*作用的是他前面的那一个字符,现在*前面的是n,因此匹配n0次1次或者无限次
import re
a = 'pytho0python1pythonn2 11 java8php'
re.findall('python+',a)
['python', 'pythonn']
import re
a = 'pytho0python1pythonn2 11 java8php'
re.findall('python?',a)
['pytho', 'python', 'python']
?可以经常使用字符串去重,代替截取等操作,不要去贪婪与非贪婪的?搞混,因为非贪婪那里?前面的是一个匹配几次到几次,是一个范围,而我们的去重的这个前面并不是一个范围
八、边界匹配 ^ $
我要匹配一串数字是不是qq号,我规定QQ号是4位到8位之间,也许我们可以使用"\d{4,8}"来试一下
import re
qq = '10001'
re.findall('\d{4,8}',qq) # 成功
['10001']
import re
qq = '1000122222'
re.findall('\d{4,8}',qq) # 失败
['10001222']
因为他大于8的时候也可以匹配到,这个时候可以使用边界匹配,只能是在4-8的范围之内才可以匹配成功
import re
qq1 = '1000122222'
qq2 = '10000'
qq3 = '1'
print(re.findall('^\d{4,8}$',qq1))
print(re.findall('^\d{4,8}$',qq2))
print(re.findall('^\d{4,8}$',qq3))
[]
['10000']
[]
^:表示从开头进行匹配
$: 表示从后面开始匹配
import re
s = '100001'
print(re.findall('^000',s)) # 开始是1,无法匹配
print(re.findall('000$',s)) # 后面是1,无法匹配
[]
['000']
九、组
a = ‘PythonPythonPythonPython’
判断这个字符串是否包含三个python,如果是三个字母的话可以用python{3},可以重复n,但是这个是个字符串,我们可以利用组的概念(),括起来
import re
a = 'PythonPythonPythonPython'
re.findall("(Python){3}", a)
['Python']
[]中括号是或关系,()是且关系
十、findall的第三个参数:匹配模式参数re.findall(pattern, string, flags=0)
1、re.I:忽略大小写
s = 'pythonC#daas'
a = re.findall('c#',s) # 匹配不到,因为一个大写一个小写
b = re.findall('c#',s,re.I)
a,b
([], ['C#'])
可以同时用多种模式,多种模式之间用|表示
2、re.S: 改变概括字符集.的行为使得.匹配所有的字符包括换行符(在概括字符集中.表示除换行符的所有字符)
s = 'pythonC#\ndaa\ns'
a = re.findall('c#.{1}',s,re.I|re.S)
b = re.findall('c#.{1}',s,re.I) #.表示除换行符的所有字符
a,b
(['C#\n'], [])
十一、re.sub:正则替换(查找成功后进行替换)
re.sub(正则表达式,匹配成功后需要替换的字符串,原字符串, count(0:表示无限替换))
s = 'pythonC#javaC#C#C#'
a = re.sub("C#", "go", s, 0)
b = re.sub("C#", "go", s, 1)
a,b
('pythongojavagogogo', 'pythongojavaC#C#C#')
# python内置函数replace
s.replace('C#', "go")
'pythongojavagogogo'
re.sub强大的功能:第二个参数可以是一个函数
def convert(value):
pass
s = 'pythonC#javaC#C#C#'
a = re.sub("C#", convert, s, 1) # 原理:如果可以匹配到,c#将会作为convert的参数传递
a
'pythonjavaC#C#C#'
def convert(value):
return '887'
s = 'pythonC#javaC#C#C#'
a = re.sub("C#", convert, s, 1) # 原理:如果可以匹配到,c#将会作为convert的参数传递
a
'python887javaC#C#C#'
重要重要!!:动态的
def convert(value):
print(value)
# return
s = 'pythonC#javaC#C#C#'
a = re.sub("C#", convert, s) # 原理:如果可以匹配到,c#将会作为convert的参数传递
# a
<re.Match object; span=(6, 8), match='C#'>
<re.Match object; span=(12, 14), match='C#'>
<re.Match object; span=(14, 16), match='C#'>
<re.Match object; span=(16, 18), match='C#'>
如上,print的是一个对象,对象有一个方法叫做group可以来看一下
def convert(value):
match = value.group()
print(match)
s = 'pythonC#javaC#C#C#'
a = re.sub("C#", convert, s) # 原理:如果可以匹配到,c#将会作为convert的参数传递
# a
C#
C#
C#
C#
def convert(value):
match = value.group()
return "!!" + match +"!!"
s = 'pythonC#javaC#C#C#'
a = re.sub("C#", convert, s) # 原理:如果可以匹配到,c#将会作为convert的参数传递
a
'python!!C#!!java!!C#!!!!C#!!!!C#!!'
意义:我们替换的不是常量,我们想要利用不同的匹配结果实现不同的结果
我们可以举个例子:找出字符串中大于等于6的数字并将其替换成数字9,小于数字6替换成0
s = 'asn125738900276'
def convert(value):
matched = value.group()
# print(matched)
if int(matched) >= 6:
return '9'
else:
return '0'
re.sub('\d',convert,s)
'asn000909900099'
十二、re.match与re.search
s = 'asn125738900276'
a = re.match('\d', s) # 从字符串的首字母开始匹配,如果没有找到结果返回空
b = re.search('\d', s) # 搜索整个字符串
print(a)
print(b)
None
<re.Match object; span=(3, 4), match='1'>
s = '1asn125738900276'
a = re.match('\d', s) # 从字符串的首字母开始匹配,如果没有找到结果返回空
b = re.search('\d', s) # 搜索整个字符串
print(a)
print(b)
<re.Match object; span=(0, 1), match='1'>
<re.Match object; span=(0, 1), match='1'>
# 他们返回的都是对象,可以利用group得到匹配结果
a.group()
'1'
# 也可以利用span方法,返回源字符串的位置
a.span()
(0, 1)
另外,match和search匹配成功后立马就会停止下来,这也是和findall最大的不同
十三、重点:group分组
我想取出某两个特定字符之间的字符,比如s = ‘life is short, i use python’,我想要取出life 与 python之间的字符,我需要定界,他们之间都是一系列的单词,我们可以使用\w
s = 'life is short, i use python'
r = re.search('life\wpython', s)
print(r.group())
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-165-a4347ac3b2cc> in <module>
1 s = 'life is short, i use python'
2 r = re.search('life\wpython', s)
----> 3 print(r.group())
AttributeError: 'NoneType' object has no attribute 'group'
报错的原因是因为他没有匹配到,r为null,没有方法group(),因为\w只能表示一个字符,我们可以使用*
s = 'life is short, i use python'
r = re.search('life\w*python', s)
print(r.group())
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-166-c7729887cf37> in <module>
1 s = 'life is short, i use python'
2 r = re.search('life\w*python', s)
----> 3 print(r.group())
AttributeError: 'NoneType' object has no attribute 'group'
报错原因:\w表示的单词字符,不包括空格,我们可以用.,.可以匹配到除换行符外的所有字符
s = 'life is short, i use python'
r = re.search('life.*python', s)
print(r.group())
life is short, i use python
现在确实是匹配成功了,但是我不要life和python,我只要之间的东西。爬虫的时候我们经常要找到标签中间的内容,因此如何获得中间的内容非常重要,我们看似没有分组,但其实他只有一个,可以看成是一个分组。然后group后面是有参数的,我们可以传入0,1默认0
s = 'life is short, i use python'
r = re.search('(life.*python)', s)
print(r.group(0))
print(r.group(1))
life is short, i use python
life is short, i use python
s = 'life is short, i use python'
r = re.search('life(.*)python', s)
print(r.group(0)) # 0永远记录的完整的结果
print(r.group(1))
life is short, i use python
is short, i use
其实比较难以理解,但是findall同样可以实现,也更好理解
s = 'life is short, i use python'
r = re.findall('life(.*)python', s)
print(r)
[' is short, i use ']
s = 'life is short, i use python,i love python'
r = re.search('life(.*)python(.*)python', s)
print(1, r.group(0)) # 0永远记录的完整的结果
print(2, r.group(1))
print(3, r.group(2))
print(4, r.group(0, 1, 2))
1 life is short, i use python,i love python
2 is short, i use
3 ,i love
4 ('life is short, i use python,i love python', ' is short, i use ', ',i love ')
# groups
s = 'life is short, i use python'
r = re.search('life(.*)python', s)
print(r.groups(0)) # 0永远记录的完整的结果
print(r.groups(1))
(' is short, i use ',)
(' is short, i use ',)