前一段用python
写了个简单的页面抓取程序,其中使用了正则表达式
,也重新学习相关的正则表达式知识.
主要从三个方面来理解:
- 正则表达式的基本知识
- 如何学习正则表达式
- python语言如何使用正则表达式
正则表达式的基本知识
正则表达式分为二种标准PCRE
和POSFIX
PCRE
主要得益于Perl语言
的发展,现在PHP
和Python
实现正则也是基于PCRE
POSIX把正则表达式分为两种:BRE(Basic Regular Expressions)与ERE(Extended Regular Expressions).现在的GUN软件对这二种标准做了很大程度的兼容,功能基本相同,只是语法不同
下面这张图很好的反应了正则表达式的特点和功能,以及BRE
和ERE
的比较
![posix-regexp-favor][201608-reg-1]
[201608-reg-1]: http://notes.newyingyong.cn/static/image/2016/201608-reg-1.png "posix-regexp-favor"
在使用awk
,shell
,sed
,vim
的时候要注意区别对待BRE
和ERE
如何学习正则表达式
- 学习正则表达式没有任何技巧,需要不断的实践
- 通过系统性的书去学习正则表达式,而不是随便从网上寻找对应知识,完整的了解正则表达式,包括不同表达式的流派,推荐书籍正则表达式必知必会
- 尝试理解不同语言实现正则的设计理念,比如
vim
,python
,php
- 正则表达式虽然很容易实现,但是准确性很重要,实现匹配很简单,但是如何排除不想匹配到的则比较困难
python语言如何使用正则表达式
关于反斜杠
python正则表达式中通过反斜杠符号取消元字符的特殊含义,这和字符串的反斜杠符号产生了冲突
比如为了匹配字符串\str
,可能需要这么写
p = re.compile('\\\\str');
m = p.match('\strok')
if m :
print m.group()
\\str
:为 re.compile 取消反斜杠的特殊意义
\\\\str
:为 \\section
的字符串实值取消反斜杠的特殊意义
为解决该问题,可以使用raw字符串表示('r').
p = re.compile(r"\\str")
m = p.match('\strok')
if m :
print m.group()
关于贪婪
正则匹配默认是贪婪的,会试着重复尽可能多的次数,通过?
符号可以修改为非贪婪模式
p = re.compile('<.*?>')
m = p.match('<html><head><title>Title</title>')
if m :
print m.group()
编译标志
通过编译标志可以修改正则表达式的一些运行方式
p = re.compile(r'str.*str' ,re.DOTALL|re.IGNORECASE )
m = p.search("str\nstR")
if m :
print m.group()
re.DOTALL:使.
特殊字符完全匹配任何字符,包括换行.
re.IGNORECASE:忽略大小写
假如没有设置MULTILINE
标志,^
它只是匹配字符串的开始,而在MULTILINE
模式里,^
也可以直接匹配字符串中的每个换行。
另外 match() 函数决定 RE 是否在字符串刚开始的位置匹配。
比较下面的代码:
print(re.findall('(hello.$)', 'hello1\nhello2\n))
print(re.findall('(hello.$)', hello1\nhello2\n',re.MULTILINE))
Match对象
在说Match对象
之前,先简单说下分组.
分组就是对于匹配出来的内容再进一步分隔,以便获取到更精准的内容.在Python
中通过
()
表示分组.通过Match对象
能够方便返回各个分组的内容.
个人理解在Python
中分组和Match对象
精密关联的
match()
和search()
匹配模式的时候返回Match对象,否则返回None
Match对象的几个方法和属性,比如group()
,groups()
,span()
group()
调用等同于group(0)
,返回整个匹配的字串
p = re.compile("\d+")
m = p.search("123,abc,456")
if m :
print m.group()
print m.group(0)
groups()
返回包含子分组的元祖
p = re.compile("(\d+,)(\D+)")
m = p.search("123,abc")
if m :
print m.group()
print m.groups()
print m.span(0)
print m.span(2)
print m.group(1,2)
print m.start()
无捕获组
精心设计的 REs 也许会用很多组,既可以捕获感兴趣的子串,又可以分组和结构化 RE 本身。在复杂的 REs 里,追踪组号变得困难。
假如想用一个组去收集正则表达式的一部分,但又对组的内容不感兴趣。可以用一个无捕获组: (?:...) 来实现这项功能。
比较下面的代码就能明白含义:
p = re.compile("(abc){2}")
m = p.search("abcabcdef")
if m :
print m.group()
print m.groups()
p = re.compile("(?:abc){2}")
m = p.search("abcabcdef")
if m :
print m.group()
print m.groups()
除了捕获匹配组的内容之外,无捕获组与捕获组表现完全一样;
可以在其中放置任何字符,可以用重复元字符如 "*" 来重复它,可以在其他组(无捕获组与捕获组)中嵌套它。(?:...) 对于修改已有组尤其有用,因为可以不用改变所有其他组号的情况下添加一个新组。
命名组
分组中,更重要和强大的是命名组;与用数字指定组不同的是,它可以用名字来指定。
命令组的语法是 Python 专用扩展之一: (?P<name>...)。名字很明显是组的名字。除了该组有个名字之外,命名组也同捕获组是相同的。
举个例子:
p = re.compile(r'(?P<word>\b\w+\b),\1')
m = p.match('lot,lot,cd')
if m:
print m.group('word')
逆向引用
逆向引用可以使用 (...)\1
这样的方式,也可以使用命名组逆向引用(主要用到的 Python 扩展就是 (?P=name) ,它可以使叫 name 的组内容再次在当前位置发现)。举个例子:
p = re.compile(r'(?P<word>\b\w+\b),(?P=word)')
p2 = re.compile(r'(?P<word>\b\w+\b),\1')
m = p.match('lot,lot,cd')
if m:
print m.group('word')
m = p2.match('lot,lot,cd')
if m:
print m.group('word')
前后查找
在学习正则表达式的时候,前后查找是学的最辛苦的一部分,学习的时候要理解其含义
(?=...)
表示假如匹配到模式,则返回...
前面的内容,且返回的内容不包含...
(?<=...)
表示假如匹配到模式,则返回...
后面的内容,且返回的内容不包含...
p = re.compile('.*(?=:)')
print(p.search("http://www.baidu.com").group())
p = re.compile('(?<=://).*')
print(p.search("http://www.baidu.com").group())
p = re.compile('(?<=<title>).*(?=</title>)')
print(p.search("<title>hello</title>").group())
前后查找取非则有很多作用:
p = re.compile('.*[.](?!bat$).*$')
print(p.search("file.log").group())
print(p.search("file.bat").group())
搜索和替换
找到所有模式匹配的字符串并用不同的字符串来替换它们。sub() 方法提供一个替换值,可以是字符串或一个函数,和一个要被处理的字符串。可以使用 \g<name>
,\g<number>
等来进行替换。
p = re.compile(r'(?P<word>\b\w+\b)')
print(p.sub(r"\g<word>ok",'Lots of punctuation'))
print(p.sub(r"\g<1>ok",'Lots of punctuation'))
print(p.sub(r"\1ok",'Lots of punctuation'))
替换的模式还支持函数的方式:
def hexrepl( match ):
value = int( match.group() )
return hex(value)
p = re.compile(r'\d+')
print (p.sub(hexrepl, 'Call 65490 for printing'))
如果替换是个函数,该函数将会被模式中每一个不重复的匹配所调用。在每次调用时,函数会被传入一个 MatchObject
的对象作为参数,因此可以用这个对象去计算出替换字符串并返回它
字符串分片
通过正则表达式将字符串分片。如果捕获括号在 RE 中使用,那么它们的内容也会作为结果列表的一部分返回,比较下面的代码:
p = re.compile(r'\W+')
print(p.split('This is a test'))
p2 = re.compile(r'(\W+)')
print(p2.split('This is a test'))
Python 中其他 RE 对象
(1)RegexObject对象:构建正则表达式对象
(2)Match对象