2.正则表达式使用基础:量词(一)

2.1 一般形式

  1. 上一节我们了解到字符组 [0-9] 或 \d 可以匹配单个数字字符。现在我们使用正则来验证更复杂的字符串,比如国内邮政编码。很自然,\d 匹配单个字符,那么我们可以这样写:
  re.search(r"^\d\d\d\d\d\d$", "110016")  !=None   # => True
  1. 这样写的话,太麻烦了, \d 重复了6次,不够灵活方便。为此,正则表达式提供了量词,上面的匹配格式可以简写为 \d{6}。意思就是 匹配 6个数字。

    下面举几个简单的例子:

   re.search(r"^\d{6}$", "100861") !=None  # => True

   re.search(r"^\d{6}$", "1A0861") !=None  # => False
  1. 量词还可以不确定长度,通用形式是 {m,n},注意中间逗号后面不跟空格。它限定之前的元素能够出现的次数,m是下线,n是上限。均为闭区间。\d{4,6},表示这个数字字符的长度最短是4个字符,最长是6个字符。

    量词的限定出现次数一般都有明确下限,如果没用,则默认为0。有些语言可以省略0,写作 {,n},但是这样不是通用的,建议写作 {0,n}。

    下表展示了量词的一般书写形式:

  • {n}之前的元素必须出现n次。

  • {m,n}之前的元素最少出现m次,最多出现n次。

  • {m,}之前的元素最少出现m次,出现次数无上限。(注意:并非真正意义上的无上限,隐式上限是65536 )

  • {0,n}之前的元素可以不出现,也可以出现,最多出现n次。

2.2 常用量词

  1. {m,n} 是通用形式上的量词,正则表达式还有3个常用量词,分别是 +、?、*,它们的形态虽不同于 {m,n},但是功能也相同,可以看作是量词的简记法。

  2. 常用量词书写形式:

  • * 等价于 {0,} 意为可能出现,也可能不出现,出现次数无上限。

  • + 等价于 {1,} 意为至少出现1次,出现次数无上限。

  • * 等价于 {0,1} 意为至多出现1次,也可能不出现。

大部分情况下只需要表示上面这三种意思,所以常用量词的使用频率高于 {m,n},例子如下:匹配 traveler 和 traveller (美式和英式)

re.search(r"^traverll?er", "traveler") !=None  # =>True

re.search(r"^traverll?er", "traveler") !=None  # =>True

量词也广泛用于解析HTML代码,匹配各种标签及标签中的内容。例如匹配所有 html tag的正则表达式:<[^>]+>;匹配 open tag , <[/][>]*>;匹配 close tag ,</[^>]+>;匹配 self-closing tag,<[^>/]+/>

2.3 数据提取

  1. 之前使用的python正则查询函数都是 search(),如果匹配成功,返回一个MatchObject的对象,这个对象包含了匹配的信息,比如匹配的结果。可以使用
re.search(r"^traverll?er", "traveler").group(0)  来获取匹配结果。
  1. 这里再介绍一个python的方法,findall(pattern,string),如果匹配成功,返回的是一个数组。例如:使用findall()来匹配两个邮政编码。注意:search 和 findall 都是python正则表达式库的函数,其他语言不一样。
re.findall(r"\d{6}", "zipcode1:123456, zipcode2:987654")

得到的结果就是 ['123456', '987654'],注意这里我们需要匹配6个数字,所以不需要在正则表达式外面加上 开始 和 $结尾来限定。

2.4 点号

  1. 上一章提到过, "." 号可以匹配任意字符,但是换行符 \n 这种不能匹配。要匹配任意字符可以 [\w\W] 这种。因为点号几乎可以匹配任意字符,所以实际运用中很多人图省事,随意使用 .* 或者 .+ ,结果却事与愿违。下面我们看一个例子:

    我们希望匹配的字符串内容是 "quoted string",待匹配的字符串也是 "quoted string" ,注意这里匹配包含前后双引号。我们这样写没问题:

re.search(r"\".*\"","\"quoted string\"").group(0)   # =>   "quoted string"   
  1. 这样得到的结果没问题,那么我们再变一下。现在我们改变一下待匹配字符串,"quoted string" and another",然后我们用同样的 pattern 去匹配这一段字符串,得到的结果却是 "quoted string" and another",这个结果不是我们想要的 "quoted string" 。
  • 这里作简单解释。在正则表达式 "." 中,点号 "." 可以匹配任何字符, * 表示可以匹配的字符串长度没有限制 ,所以 . 在匹配过程结束以前,每遇到一个字符(除去无法匹配的\n), .*都可以匹配,但是到底匹配这个字符,还是忽略它吗,交给之后的 " 来匹配呢?
  • 答案是取决于所使用的量词。正则中的量词分为几类,之前介绍的量词都可以归为一类,叫做匹配优先量词(也有译为贪婪量词)。意思就是在拿不准是否要匹配的时候,优先尝试匹配,并且几下这个状态,以备“反悔”。
    来分析正则表达式 ".*" 对字符串 "quoted string的匹配过程
  • 开始," 匹配 " ,然后轮到 q , .* 可以匹配它,也可以不匹配,因为使用的量词都是匹配优先量词,所以 .先匹配q,并且记录下这个状态【q也可能是.不该匹配的】;
  • 接下来是字符 u, .* 可与匹配它,也可与不匹配,因为使用的量词都是匹配优先量词,所以 .先匹配g,滨记录下这个状态【g也可能是.不该匹配的】;
  • …………
  • 最后是末尾的 " , .* 可与匹配它,也可与不匹配,因为使用的量词都是匹配优先量词,所以 .先匹配" ,滨记录下这个状态【" 也可能是.不该匹配的】;
    这个时候,字符串后已经没有字符了,但正则表达式中还有 " 没有匹配,所以只能查询之前保存的备用状态,看看能不能退回几步,按照 " 的匹配。查询到最近状态是: 【" 也可能是.不该匹配的】。于是让 . 反悔 对 " 的匹配,把字符串 " 交给 正则里面的 " 来匹配,测试发现正好匹配,所以整个匹配宣告成功。这个反悔的过程,专业术语叫做回溯(backtracking)
    image.png

至于 "quoted string" and another" 也是一样的道理,最开始 .* 匹配了 字符串 * 后的所有内容,后来发现正则中还有 " 未匹配,因此回溯最近的 一个 " ,交给匹配字符李的 " 进行匹配。

image.png
  1. 因此要准确匹配 "quoted string" and another" 字符串中的 "quoted string" ,就不能图省事使用 "." 来匹配,需要使用 "[^"]"
    image.png

2.5 忽略优先量词

  1. 但某些时候,也会需要使用到 .* 匹配,比如匹配 <script type="text/javascript">...</script> ,script标签左右的开关标签好写, 但是js内部的代码可以出现任意字符,所以这个时候还是得 用 .* 来匹配。.* 不能匹配换行符,所以这里用 [\s\S]* 来匹配。
str1 =  """
    <script type="text/javascript">
    alert("1");
    </script>
    <br />
    <script type="text/javascript">
    alert("2");
    </script>
"""
print(re.search(r"<script type=\"text/javascript\">[\s\S]*</script>",str1).group(0))

输出
image.png
  • 表达式是匹配成功了,但是最后的结果不正确。 上述匹配规则会一次性匹配2段JS代码,包括中间的换行符 <BR />。
    换个角度,通过改变 [\s\S]* 的匹配策略来解决问题:在不确定是否需要匹配的场合,先尝试不匹配的选择,测试正则表达式中后面的元素,如果失败,再退回来尝试 .* 匹配,如此就没有问题了。
  • 这就是 忽略优先量词模式(懒惰量词 Lazy Quantifier)。如果不确定是否需要匹配,懒惰量词会选择“不匹配“状态,再尝试表达式中之后的元素,如果尝试失败,再回溯,选择之前保存的匹配状态。
re.search(r"<script type=\"text/javascript\">[\s\S]*?</script>",str1)

再 * 后面增加了 一个 ? 号。这样就能提取出所有 js标签及代码。
匹配优先量词与忽略优先量词逐一对应。所有的匹配优先量词后加 ? 即可。
例如 * 对应 *? 。

忽略优先量词可以提取 c 语言中的护士、提取网页中超链接tag、解析表格等。

  • 使用正则表达式去拆解 linux 路径
print(re.search(r"^.*/"), "/usr/local/bin/pyhton")
  • 拆解windows路径
    print(re.search(r"^.*\"),"c:\program files\")

2.6 转义

  • 前面我们知道了,*、+、? 等作为量词的字符具有特殊意义,但某些情况下我们就要匹配它们本身,则再他们前面加 \ ,例如 +。
  • 如果是 {m,n},则 {m,n}
  • ?? , ??
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容