正则表达式-贪婪与非贪婪
只因贫道学习爬虫,爬网页的时候总也得不到想要的结果,故而baidu一番,但是晦涩难懂,便根据自己心得留下只言片语,静等有缘人
介绍:
我所理解的贪婪和非贪婪模式,是用于正则中多次匹配元素时的取值方式。
以下使用贪吃蛇的方式说明
1. 贪婪模式
只要能完成正则表达式,能吃多少吃多少,不怕胖
2. 非贪婪模式
只能能完成正则表达式,我能少吃就少吃。减肥
所以在被量词修饰的正则表达式中由于这两种模式的不同就有可能会出现不同的结果。
贪婪模式:
* 匹配上一个元素零次或多次
+ 匹配上一个元素一次或多次
? 匹配前面的元素零次或一次
{n} 匹配上一个元素恰好n次
{n,} 匹配上一个元素至少n次
{n,m} 匹配上一个元素至少n次,但不多于m次
因为本次讨论的是贪婪和非贪婪,所以其中确定次数的量词就没有谈论的必要,‘?’和‘{n}’不讨论
非贪婪模式:(在贪婪限定符后多加一个?)
*? 匹配上一个元素零次或多次,但次数尽可能少
+? 匹配上一个元素一次或多次,但次数尽可能少
{n,}? 匹配上一个元素至少 n 次,但次数尽可能少
{n,m}? 匹配上一个元素的次数介于 n 和 m 之间,但次数尽可能少
例子
例子主要以 *
限定符进行介绍,因 *
相当于{0,}
。而+
道理是相同的。
我将以下面的p标签进行演示这两种模式,根据例子讲解原理
<div>
<p>第一段文字
</p>
<p>第二段文字
</p>
<p>第三段文字,注意这里没有p的闭合标签,这是错误的html
代码(python)
import re
class RegexTest:
def greedy(self,text):
# 贪婪模式
regix = '<p>(.*)</p>'
greedy_pattern = re.compile(regix,re.S)
result = greedy_pattern.findall(text)
print('----------贪婪模式------------')
print(result)
def ungreedy(self,text):
# 非贪婪模式
regix = '<p>(.*?)</p>'
ungreedy_pattern = re.compile(regix,re.S)
result = ungreedy_pattern.findall(text)
print('-----------非贪婪模式----------')
print(result)
if __name__ == '__main__':
text = ' <div><p>第一段文字</p><p>第二段文字</p><p>第三段文字,注意这里没有p的闭合标签'
RegexTest().greedy(text)
RegexTest().ungreedy(text)
打印结果:
----------贪婪模式------------
['第一段文字</p><p>第二段文字']
-----------非贪婪模式----------
['第一段文字', '第二段文字']
分析
代码中:
正则标识式 贪婪模式:<p>(.*)</p>
非贪婪模式:<p>(.*?)</p>
意思是匹配以<p>
开头,以</p>
结尾的字符串,但是得到的结果却是不同的。
其中 re.S参数的意思为全文匹配
结果中:
- 贪婪模式获得的列表只有一个元素,非贪婪模式获得两个元素
- 贪婪模式将
<p></p>
标签当成元素取了出来
为什么呢?
首先不论是贪婪还是非贪婪模式,正则表达式在匹配时都是一个字符一个字符对比。
贪婪模式 <p>(.*)</p>
原则:能拿多少拿多少
- 首先正则匹配
<
,text
中第一个<
匹配上了(放篮子1),然后匹配正则中的p
,但是text
中第二个是d
,匹配失败(将篮子1的<
扔掉),重新匹配<
。进行相同的操作直到找到了第一个<p>
。 - 匹配
.*
,这个限制符的意思是只要是单字符我都要放篮子2,所以<p>
后面的所有字符都被放到了篮子2。 - 但是发现正则表达式后面还有
</p>
没匹配呢,所以.*
开始从篮子2中将text
里的字符一个一个拿出来让最后的</p>
匹配。(顺序是先进后出原则) -
</p>
开始从.*
一个一个拿出来的text
对比,直到匹配上了最后一个</p>
- 第一次匹配结束,然后进行下一次匹配,从第一次循环的结束位置的下一个字符开始规则循环1-4,发现没有与 1 匹配的字符,结束整个匹配。
- 准确的说不应该把
</p>
看做一个整体,实际匹配的原理与 1 类似。暂且看成整体方便理解贪婪模式
结果:只有一个元素的列表,将中间的</p>直接取了出来
----------贪婪模式------------
['第一段文字</p><p>第二段文字']
非贪婪模式 <p>(.*?)</p>
原则:能不拿就不拿
- 首先正则匹配
<
,text
中第一个<
匹配上了(放篮子1),然后匹配正则中的p
,但是text
中第二个是d
,匹配失败(将篮子1的<
扔掉),重新匹配<
。进行相同的操作直到找到了第一个<p>
。 - 然后到
.*?
开始匹配了,因为*
的意思是 0个或多个,所以.*?
让</p>
先匹配,但是</p>
发现与第
匹配不上,最后让.*?
放进了篮子2 - 再向下匹配
.*?
继续让</p>
先匹配(循环2)····如果</p>
能匹配上就结束本次匹配,进行下次匹配循环,如果</p>
匹配不上就放进.*?
的篮子2里,继续本次循环。
结果:得到两个元素的列表
-----------非贪婪模式----------
['第一段文字', '第二段文字']
总结:
两种模式概括就是:
贪婪模式:我不管后续的规则,符合我要求的字符我都先拿走,直到出现不符合我的要求或者字符拿完停止,如果后续规则不够了,我再一点一点掏出来。
非贪婪模式:先满足我最低的要求,后面的字符,不管符不符合我的要求,我都先问问后面的规则,如果符合后面规则就结束我的需求,不再获取字符。
*?
和+?
是类似,区别在于+?
是只要我要先拿到一个符合我条件的字符,后续字符都用后续的规则匹配。