平凡

python正则的多行匹配问题

2018/08/31 Share

在正则表达式匹配一行字符串时,这种情况比较容易处理,直接写pattern就完事了。但如果想匹配多行字符串,那就比较容易翻车,里面有不少坑。这篇文章准备带你走出这些坑。


.*与re.DOTALL

这是常见的html代码:

paragraph = \
    '''
<p>
This is a paragraph.
It has multiple lines.
</p>'''

如果你准备写一个爬虫想匹配<p></p>之间的内容,于是你不假思索这样写:

re.search(r'<p>.*</p>', paragraph)

很遗憾,这将不会匹配到任何东西。
奇怪,明明正则写法(<p>.*</p>)看上去都对,为什么匹配不了呢?

这是因为:.操作符不会匹配\n!!!
(可参考这篇文章:http://www.runoob.com/python/python-reg-expressions.html)

现在可以默默拿出小本本记下:

  • .默认不匹配换行符
  • 当指定了re.DOTALL,则可以匹配换行符

DOTALLresearch、match、findall等方法的第三个参数指定,其实这个位置的参数全部是为了处理特殊的字符

所以,上面正则表达式应该这样写:

res = re.search(r"<p>.*</p>", paragraph, re.DOTALL)
print(res.group(0))

现在成功的匹配到了p标签的内容。
结论:使用DOTALL,可以使.*具有匹配多行的能力


re.MULTILINE与^$

现在又来了一个需求,让你匹配每行中以It has开头的整行句子。
对于这个paragraph:

paragraph = \
    '''xxxxx
    <p>
    This is a paragraph.
    It has multiple lines.
    </p>'''

显然只有倒数第二行满足要求。
于是你再次不假思索的写下正则匹配:

re.search(r'^It has.*', paragraph)

看上去一点毛病没有,但又匹配不到任何内容。
这是因为,在python里^$,虽然是匹配开头和结尾,但这是对整个字符串(entire string)而言。
文档中这样说的:

对于多行字符串,它也只是一个字符串,只有一个开头和结尾,所以我们想要使用^匹配每行的开头,很不幸不能如愿。

于是,re包里又有一个标志位来解决这个问题,那就是re.MULTILINE。它告诉python去匹配每行的开头和结尾
加上这样就能匹配到想要的内容了:

res = re.search(r'^It has.*', paragraph, re.MULTILINE)
print(res.group(0))

# result:
It has multiple lines.

总结:使用MULTILINE,可以使^$具有匹配多行的能力


hackerrank上的一道题目

题目描述

链接:https://www.hackerrank.com/challenges/ide-identifying-comments/problem
匹配c++里的注释,要考虑两种情况:

  • / …comment… /,可能跨越多行
  • // comment , 这种情况是匹配到该行结尾
    输出匹配到的注释。

题目非常人性化,不需要考虑复杂的嵌套,例如:

/* dfksa
* dfsfds // kengni
*/

大大降低了该题的难度。

这道题对于总结上面两个知识点非常有帮助。


分析

首先我们来分析匹配 每行中的//,而且只到行尾,所以使用$
//.*?$
我在做这题的时候,一开始忘了加re.MULTILINE,猜一下结果会怎样?
会匹配到第一个//后面所有的内容
对于下面的字符串:

hello="hello"
// comment
world="world" //world
print(hello, world)

直接匹配成:

// comment
world="world" //world
print(hello, world)

因为$这时直接找到了多行字符串的结尾。

所以有必要加re.MULTILINE了。


再来分析/* */这种注释:

如果使用贪心算法,会直接匹配到第一个/*及最后一个*/,结果不对。

一定要使用非贪心算法,python中非贪心匹配的操作符是.*?

所以正则表达式可以这样写:/\*.*?\*/

其中的\*表示转义。

但仅仅这样写就完了吗,现在我们知道,这样不会匹配到任何内容,因为换行符\n的存在。

所以还要加re.DOTALL来为.服务。

综上,完整的正则表达式为:

pattern = r"(/\*.*?\*/|//.*?$)"

res = re.findall(pattern, text, re.DOTALL | re.MULTILINE)

后记

bb了这么多,不知道你有没有看懂。
反正就总结出来就两句话:

  • 使用MULTILINE,可以使^$具有匹配多行的能力
  • 使用DOTALL,可以使.*具有匹配多行的能力

参考及翻译

https://www.thegeekstuff.com/2014/07/advanced-python-regex/
https://www.hackerrank.com/challenges/ide-identifying-comments/problem
http://www.runoob.com/python/python-reg-expressions.html

发表日期: August 31st 2018

版权声明: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. .*与re.DOTALL
  2. 2. re.MULTILINE与^$
  • hackerrank上的一道题目
    1. 1. 题目描述
    2. 2. 分析
  • 后记
  • 参考及翻译