正则表达式是一种语法,或者说是从较大文本中搜索、提取和操作特定字符串模式的语言。广泛应用于涉及文本验证、NLP和文本挖掘的项目中。
几乎每种语言都有相应实现,Python实现是标准模块re。本文通过一些实例介绍其基本语法。
正则表达式模式是用于表示一般性文本、数字或符号的特定语言,因此可用来提取符合这种模式的文本。
一个简单示例表达式为:‘\s+’
.
这里的\s
表示匹配任何空白字符,+
号表示至少1个或多个空白字符。因此在模式也会匹配\t
字符。
下面代码编译一个正在表达式:
import re
regex = re.compile('\s+')
首先导入re模块,然后定义匹配至少一个空白字符的表达式。
假设有下面一个字符串存储课程编号、编码和名称:
text = """101 COM Computers
205 MAT Mathematics
189 ENG English"""
共三条记录,但单词之间的空格数不等。我们需要分隔这三条记录至多个数字和单词,下面提供两种方法实现。
import re
regex = re.compile('\s+')
# 1. re.split('\s+', text)
re.split('\s+', text)
# 2. 使用 regex.split(text)
regex.split(text)
#> ['101', 'COM', 'Computers', '205', 'MAT', 'Mathematics', '189', 'ENG', 'English']
上面两种方式那种更有效拟?如果多次使用一个特定模式,最好不要每次都调用re.split方法,导致正则表达式编译多次。
下面我们需要抽取所有课程编号,也就是从文本中抽取101,205,189。
regex_num = re.compile('\d+')
regex_num.findall(text)
#> ['101', '205', '189']
代码中\d表示匹配任何数字,+号表示至少有一个数字。如果是*号表示0个或多个数字。最后findall方法抽取所有符合条件的数字作为list返回。
regex.search()用于在文本中搜索模式,但不想findall返回list,而是返回特定的匹配对象,其包括第一次模式匹配的开始位置和结束位置。
而regex.match()也返回匹配对象,但它要求模式出现在文本的开头。 请看示例代码:
import re
string_with_newlines = """something
someotherthing"""
print re.match('some', string_with_newlines) # matches
print re.match('someother', string_with_newlines) # won't match
print re.match('^someother', string_with_newlines, re.MULTILINE) # also won't match
print re.search('someother', string_with_newlines) # finds something
print re.search('^someother', string_with_newlines, re.MULTILINE) # also finds something
m = re.compile('thing$', re.MULTILINE)
print m.match(string_with_newlines) # no match
print m.match(string_with_newlines, pos=4) # matches
print m.search(string_with_newlines, re.MULTILINE) # also matches
替换文本使用regex.sub()方法。这里我们text中的每个课程代码后面有tab字符。
# define the text
text = """101 COM \t Computers
205 MAT \t Mathematics
189 ENG \t English"""
print(text)
#> 101 COM Computers
#> 205 MAT Mathematics
#> 189 ENG English
我们需要去掉所有多余的空格,并在一行显示所有记录。这里使用regex.sub方法替换\s模式为单个空格。
# replace one or more spaces with single space
regex = re.compile('\s+')
print(regex.sub(' ', text))
# or
print(re.sub('\s+', ' ', text))
#> 101 COM Computers 205 MAT Mathematics 189 ENG English
如果仅想去掉额外的空格并保留换行符,需要表达式排除换行符并包括所有其他空白符。这可以通过使用负向前瞻(?!\n)来实现。它检查即将到来的换行字符并将其从模式中排除。
# get rid of all extra spaces except newline
regex = re.compile('((?!\n)\s+)')
print(regex.sub(' ', text))
#> 101 COM Computers
#> 205 MAT Mathematics
#> 189 ENG English
正则分组是非常有用的特性,可以将所需的匹配对象提取为单独的项。假如需要抽取课程编号、编码、名称作为单独项,如果不使用分组:
text = """101 COM Computers
205 MAT Mathematics
189 ENG English"""
# 1. extract all course numbers
re.findall('[0-9]+', text)
# 2. extract all course codes
re.findall('[A-Z]{3}', text)
# 3. extract all course names
re.findall('[A-Za-z]{4,}', text)
#> ['101', '205', '189']
#> ['COM', 'MAT', 'ENG']
#> ['Computers', 'Mathematics', 'English']
我们使用了三个独立正则表达式实现,但有更好的方式,正则分组。因为记录模式相同,我们可以为所有记录构建统一模式,并把需要抽取的内容放在分组中,使用().
# define the course text pattern groups and extract
course_pattern = '([0-9]+)\s*([A-Z]{3})\s*([A-Za-z]{4,})'
re.findall(course_pattern, text)
#> [('101', 'COM', 'Computers'), ('205', 'MAT', 'Mathematics'), ('189', 'ENG', 'English')]
注意,num: [0-9]+、code: [A-Z]{3}和name: [A-Za-z]{4,} 模式都放在括号()中以形成组。
正则表示缺省匹配是贪婪匹配,即尝试匹配尽可能多的内容。下面是一段html内容,我们需要返回html标记。
text = "< body>Regex Greedy Matching Example < /body>"
re.findall('<.*>', text)
#> ['< body>Regex Greedy Matching Example < /body>']
结果提取了整个字符串,而不是匹配到’ > '的第一次出现,我本来希望出现在第一个body标签的末尾。这是regex默认的贪婪或“全盘接收”行为。
相反方式是懒匹配,即尽可能小方式匹配。在模式的结尾增加?表示:
re.findall('<.*?>', text)
#> ['< body>', '< /body>']
如果仅需要第一个匹配结果,则使用search代替:
re.search('<.*?>', text).group()
#> '< body>'
本文简要介绍了Python正则表达式,注意介绍了模式及其常用方法,同时也通过示例说明了懒匹配和分组匹配。