本章介绍python文本提取之re正则表达式,主要函数和知识点有:re.findall(),re.sub(),re.match(),re.search(),各种正则符号,打组,贪婪与非贪婪等。
目录
1. re.findall(pattern, string, flags=0)
2. re.sub(pattern, repl, string, count=0, flags=0)
3. re.match(pattern, string, flags=0)
4. re.search(pattern, string, flags=0)
为了测试正则表达式,首先生成一段假信息用于测试,不懂可以看我的faker篇。
import faker
f = faker.Faker('zh_CN')
def data_line():
name = f.name()
phone = f.phone_number()
job = f.job()
address_yb = f.address()
address = address_yb.split(' ')[0]
bm = address_yb.split(' ')[1]
data = f'姓名:{name} 电话:{phone} 工作:{job} 地址:{address} 邮政编码:{bm} '
return data
data = ''
for i in range(5):
data = data + data_line()
print(data)
关于正则表达式需要记住的一些符号,其实也不必特意去记,反而容易忘。多用,自然就记得了。为了方便,我把意义相反或者相近的写一块,用中文“,”隔开。
符号1,符号2 | 符号1解释。符号2解释 |
[],[^] | 字符种类,提取出现在[]中的任意字符。后者相反,都不提取 |
[0-9a-zA-Z],[^0-9a-zA-Z] | 所有0-9a-zA-Z元素。后者相反 |
\d,\D | \d所有的数字(相当于[0-9])。 \D所有的非数字(相当于[^0-9]) |
\w,\W | \w中文下划线数字英文(相当于[0-9a-zA-Z_])。\W与\w相反,特殊字符,如 $、&、空格、\n、\t等 |
\s,\S | \s所有空白的字符(如 \t\n\r\f\v,相当于[ \t\n\r\f\v])。\S相反,所有非空白字符 |
{min,max}、{num} | {min,max}只提取符合条件的连续min-max个元素。{num}只提取符合条件的连续num个元素 |
[]+,\d+ | 都为连续匹配,前者为连续匹配[]内任意字符,后者连续匹配所有数字 |
'excel*','excel+','excel?' | *前面的一个字符l匹配0次或者无限次。+前面的一个字符l匹配1次或者无限次。?前面的一个字符l匹配0次或者1次 |
^,$ | 指定从开头开始符合条件的数。指定结尾是符合条件的数(与第一个的区别是它只写在''的最前面或者最后面,而第一个是写在[]里的) |
re.I | 忽略大小写 IGNORECASE |
. .{num} .* .+ | .取一个元素(全部取完 每个元素分开)。 .{num}取前num位(一个)。 后二皆整体取全 |
(?) | ()打组,组内的?表示非贪婪模式 |
千言万语说不清,我们用re.findall()函数来实操一下。
1. re.findall(pattern, string, flags=0)
查找全部符合pattern条件的元素。
pattern:条件表达式
string:被提取的字符串
flags:控制正则表达式的匹配方式,如是否区分大小写,多行匹配等
(1)提取任意字符[]
import re
a = re.findall('[0-9]',data)
b = re.findall('[0-9]+',data)
print('输出a:\n',a)
print('输出b:\n',b)
这里先用re.findall()函数学习上面表格的内容,先学会条件表达式,再跟着学其他函数。(提醒鼠标滑得快的朋友,data在上面有定义过)
[]表示匹配[]里任意字符,这里[0-9]表示匹配0-9(包含0和9)中任意一个字符,说任意一个,即把匹配到的字符每一个作为列表的一个元素,如输出a。当在[]外面加上+后,表示连续匹配,遇到连续的0-9数字,它会作为一个整体去输出,如输出b。
不止0-9,如果想提取其他,也可以直接写在[]中,如想提取0-9、A-Z、a-z和省字:
a = re.findall('[0-9A-Za-z省]',data)
print(a)
这样就把含有0-9、A-Z、a-z和省字的都一一提取出来了,如果想要连续的,同理,在[]后加上+即可。
如果想要一一提取除0-9、A-Z、a-z和省字以外的元素,可在[]里面的开头加上^,如:
a = re.findall('[^0-9A-Za-z省]',data)
print(a)
想要连续匹配也同理,在后面加上+,如图2的b。
(2)提取数字/非数字
\d提取数字,\D提取非数字。
import re
a = re.findall('\d',data)
b = re.findall('\d+',data)
c = re.findall('\D+',data)
print('输出a:\n',a)
print('输出b:\n',b)
print('输出c:\n',c)
\d为提取数字,如a。\D为非数字,如b。+表示连续匹配,有个印象,详细的连续匹配后面再讲。
(3)提取中文、下划线、数字、英文
\w提取中文、下划线、数字、英文。大写字母\W相反。
por = '十多年了,I still love you li_wei.$#% *&'
a = re.findall('\w+',por)
b = re.findall('\W+',por)
print(a)
print(b)
(4)提取空白字符
\s提取所有空白字符,包括 \t、\n、\r、\f、\v和空格。大写字母\S则相反。
por = '十多年了,I still love you li_wei.$#% *& \n \r \t'
a = re.findall('\s+',por)
b = re.findall('\S+',por)
print(a)
print(b)
所谓的空白字符就是输出的时候表现为空白的字符,如 \t、\n、\r、\f、\v和空格。以下打印一下por,看看它是否“空白”:
por = '十多年了,I still love you li_wei.$#% *& \n \r \t'
print(por)
如图,\t、\n、\r、\f、\v和空格 都表现为空白,\s就是专门提取这些空白字符的。
(5)限定字符数量
{min,max}为表示前者表达式中提取符合条件的数的范围。{num}特定数量。
por = '湖&南省&潮州县&静安韦街&座'
a = re.findall('\w{2,3}',por)
print(a)
\w为提取中文下划线数字英文,后面指定了2-3位。在por中,“湖”字后面是“&”,由于“&”不符合中文下划线数字英文,所以“湖”符合条件,但是只有一个字,不符合{2,3},所以没有得到提取。后面“潮州县”符合中文下划线数字英文,也符合2-3位,所以提取。再后面“静安韦街”符合,但是超出了2-3位的条件,所以只取最多的3位,得到“静安韦”。
(6)连续匹配
+表示前面的条件匹配1次或无数次。*表示前面的条件匹配0次或无数次。?表示前面的条件匹配0次或1次。(看到例子就懂了)
por = 'wor word wordd worddd'
a = re.findall('word+',por)
b = re.findall('word*',por)
c = re.findall('word?',por)
print(a)
print(b)
print(c)
+表示前面的条件匹配1次或无数次。如a,+号前面的字符d匹配1次或者无限次,就是说,可以是 word , wordd , worddd , wordddd ...等,符合这些的,都能被提取出来。
*表示前面的条件匹配0次或无数次。如b,*号前面的字符d匹配0次或者无限次,0次就是就算没有d但是有 wor 也能提取出来,所以可以是 wor , word , wordd , worddd , wordddd ...等。
?表示前面的条件匹配0次或1次。如c,?号前面的字符d匹配0次或者1次,也就是说可以是 wor , word .只有这两个,por中出现这两个都能提取出来,而多余的d则不会提取。
(7)边界匹配
^表示指定开头,$表示指定结尾(难说清楚,直接看案例,随便写的号码哈)
edge_1 = '16915158890abcdef'
edge_2 = 'abcdef16915158890'
a = re.findall('^\d{11}',edge_1)
b = re.findall('^\d{11}',edge_2)
print(a)
print(b)
如图11,^\d{11}表示从头开始,匹配11位的连续数字。在a中,用^指定了开头,由于edge_1的开头符合\d即数字,且符合11位数,所以能提取。在b中,由于edge_2的开头为a,不符合\d,所以匹配不成功,放回空列表。
$也同理,指定结尾。
edge_1 = '16915158890abcdef'
edge_2 = 'abcdef16915158890'
a = re.findall('\d{11}$',edge_1)
b = re.findall('\d{11}$',edge_2)
print(a)
print(b)
如图,对于a,用$指定了结尾,由于结尾不符合\d{11},即不符合以11位数字结尾,所以a提取不出来。对于b,符合以11位数字结尾,所以能提取到。
请记住,对于指定开头,用^;对于指定结尾,用$。而不是用$去指定开头。注意区分第(1)点里的^,^写在[]里的最前面,表示[]里取反;而^写在整体的最前面,表示边界匹配。
(8)忽略大小写
flags = re.I 忽略大小写,该参数写在函数体里。
fi = 'excel Excel excEL word EXCEL WORD'
a = re.findall('excel',fi,flags=re.I)
print(a)
(9)匹配任意字符
在正则表达式中,. 表示除回车符 \n 以外的任何一个字符,起到通配符的作用。
fi = '今天我吃了鱼呀,\n今天我吃了鸡肉呀,\n今天我吃了鸡扒呀,\n今天我什么都没有吃'
a = re.findall('今天我吃了.*呀',fi)
print(a)
. 在此处代表了除回车 \n 以外的任意字符,加上连续匹配符*号, .* 在一起表示为0个或者无数个字符。在该案例下,前面有“今天我吃了”,后面有“呀”,表示处于这两个之间可以存在0个或者无数个字符,就是说符合 “今天我吃了......呀” 的句子都可以被提取出来。
在这可能有疑问,如何只提取中间......里的部分呢?这就需要给句子打个组,看10。
(10)打组
在正则表达式中,()表示打组,只输出()里的内容。
fi = '今天我吃了鱼呀,\n今天我吃了鸡肉呀,\n今天我吃了鸡扒呀,\n今天我什么都没有吃'
a = re.findall('今天我吃了(.*)呀',fi)
print(a)
直接在 .* 里加个()即可。(需要注意的是本案例的原句子是含有回车符 \n 的,假如没有回车符,打组会出现另外一个问题)
假如原句子没有回车,我们看看打组会出现什么问题:
在这,只提取到一个!框中的内容都全列在一起了,而不是我们想要得到的(我们想要得到图14那样,只含食物的)。这是因为 .* 不能匹配 \n,所以如图14那样存在 \n的句子,它在匹配时自动在 \n就暂停寻找,得到其中一个结果,然后在 \n后面重新找第二个,这样我们达到了预期。但是像图15那样没有 \n的句子,它除了 \n都匹配,所以它就一直匹配到底,导致粘在一起了。此类问题广泛出现在爬虫中,所以需要掌握非贪婪模式。
(11)贪婪与非贪婪
像图15,它就属于贪婪了。我们只需要在条件后面加上?号,即可启动非贪婪模式。
fi = '今天我吃了鱼呀,今天我吃了鸡肉呀,今天我吃了鸡扒呀,今天我什么都没有吃'
a = re.findall('今天我吃了(.*?)呀',fi)
print(a)
启动非贪婪模式后,它后面匹配到第一个“呀”就会暂停,得到了其中一个结果。接着再继续寻找下一个,知道检索完毕为止。这样就解决了图15的打组出现的问题。
注意,当?用在()组里时,代表启动非贪婪模式;当?用在非组里,就是连续匹配(0次或1次)的意思。
基本条件表达式pattern学习完毕,别开心,到这还没学完,re.findall(pattern, string, flags=0)里的pattern,还有re.sub(),re.match(),re.search()等函数,不过这些函数里面的参数我们在上面都学过了,所以学re.sub(),re.match(),re.search()很快,基本一看就过,下面我们继续。
2. re.sub(pattern, repl, string, count=0, flags=0)
正则替换,支持调用自定义函数。
pattern:被替换的字符串
repl:替换成什么str,支持调用自定义函数
string:字符串,句子
count:替换的次数,默认0为无数次
flags:控制正则表达式的匹配方式,如是否区分大小写,多行匹配等
import re
st = 'word word word excel ppt office Word WORD '
a = re.sub('word','1111',st)
b = re.sub('word','1111',st,count=1)
c = re.sub('word','1111',st,flags=re.I)
print('替换全部word:')
print(a)
print('\n替换一次word:')
print(b)
print('\n替换全部word,忽略大小写:')
print(c)
把word替换成1111,图中 a,b,c 分别列出了 替换全部、只替换一次 和 替换全部且忽略大小写 的情况。
正则表达式的替换优势于replace的原因是正则表达式可以调用自定义函数,此替换是高级替换,而replace不行。高级替换举例(.group()为组内元素的提取,后面会说到):
ss = '孙杨 98 李红 65 郭靖 85 扬程 43 彭凯文 70'
def rep_judge(x):
score_str = x.group()
score = eval(score_str)
if score >= 90:
return '优秀'
elif score >= 80:
return '良好'
elif score >= 60:
return '及格'
else:
return '不及格'
a = re.sub('\d+',rep_judge,ss)
print(a)
eval()函数为把字符串包含的数字(字符串类型)转为数字类型。高级替换还有很高级的功能,这里就不一一列举了。
3. re.match(pattern, string, flags=0)
从头匹配一个符合pattern的元素,匹配成功则返回一个对象,匹配不成功则返回None(开头不符合,直接None)
pattern:条件表达式
string:被提取的字符串
flags:控制正则表达式的匹配方式,如是否区分大小写,多行匹配等
ss_1 = 'A83C72D1D8E67'
ss_2 = '83C72D1D8E67'
a = re.match('\d',ss_1)
b = re.match('\d',ss_2)
print(a)
print(b)
a在ss_1的开头中匹配 \d,即一个数字,由于开头不是数字,所以直接匹配不成功,返回None;b在ss_2的开头中匹配 \d,匹配成功返回一个对象。如果开头不符合条件,直接返回None了,后面的都不关事,所以 re.match() 用于检验开头!!!
print(a.group())
b匹配成功,可以用.group()在对象中提取元素,如图20。其实 .group() 括号中也可以输入数字,表示提取第几个组的内容。由于re.match()只匹配一个,直接.group() 即可提取出来,所以在这里不研究group的参数,在 re.search()进一步讨论。
4. re.search(pattern, string, flags=0)
从头匹配符合pattern的元素,匹配成功则返回一个包含所有符合元素的对象,没有匹配到则返回None(开头不符合不要紧,接着匹配,把所有符合的都找到,打包在组中;都找不到就返回None)
参数与 re.match一样,就不重复阐述了。
ss = '123abc¥456'
a = re.search('([0-9]*)([a-z]*)(\W)([0-9]*)',ss)
print(a.groups()) # 全部 分组提取
print(a.group()) # 全部 一起提取(括号内为空或者0)
print(a.group(1))
print(a.group(2))
print(a.group(3))
print(a.group(4))
把数字、a-z、\W、数字 划分打组,然后一一提取。与图14不同的是,这里返回的是一个对象,而图14返回的是一个列表,返回对象需要用.group()提取组里的内容。
group()里的数字具体可参考图21,.groups()为提取全部,返回一个元组;.group(0)或.group()为提取全部,但是会合并在一起;.group(1)为提取第一个组,以此类推。
需要注意的是,如果传入.group(5),超出了组数的范围,则会报错 “ no such group ” 。