@chanvee
2015-04-16T22:20:58.000000Z
字数 7447
阅读 10739
Python
数据挖掘
正则表达式是一门非常强大的关于文本模式匹配的语言。本文将对正则表达式就行一个简单的介绍,同时展示如何在python中使用正则表达式。python中模块're'提供了对正则表达式的支持。
在python中,正则表达式的查询通常如下所示:
import re
match = re.search(pat, str)
re.search()方法表示了一个正则表达式并利用里面的模式pat对字符str进行匹配。如果匹配到了,search()将会返回一个match对象,否则则返回None。因此,在search后面经常会跟一些if-else语句来判断匹配如否成功。下面的例子表示在字符串中匹配模式'word':后面连接3个字母(具体见下):
str = 'an example word:cat!!'
match = re.search(r'word:\w\w\w', str)
# If-statement after search() tests if it succeeded
if match:
print 'found', match.group() ## 'found word:cat'
else:
print 'did not find'
代码 match = re.search(pat, str)
存储了匹配和搜索的结果在变量'match'中。然后利用if判断语句来测试是否匹配成功 -- 如果匹配成功,match.group()
表示的就是匹配到的字符(e.g. 'word:cat');如果匹配不成功,则没有匹配到的字符。
Python中在匹配模式字符前加上'r'表示 “raw” string,其会在不改变反斜杠(即不用对其转意)的情况下通过(特别是在linux和windows系统的路径不同的时候特别有用),因此建议在写正则表达式时在前面加上'r'作为一个习惯。
The power of regular expressions is that they can specify patterns, not just fixed characters. Here are the most basic patterns which match single chars:
正则表达式的强大之处在于它们可以指定一些模式(patterns),而不仅仅是修改字符。下面给出了匹配单个字符最常用的几种模式:
- a, X, 9, < -- 普通字符,仅仅匹配字符本身;有的字符不仅会匹配字符本身,还有一些其他特殊的意义:
. ^ $ * + ? { [ ] \ | ( )
(具体见下)- . (点号) -- 匹配除'\n'的任意字符
- \w -- (小写w) 匹配任意的‘word’型的字符:字母或者数字或者下划线
[a-zA-Z0-9_]
;值得注意的是‘word’型只匹配单个字符而不是一个单词的‘word’; \W (大写 W)
匹配任意的'non-word'非word的字符- \b -- boundary between word and non-word(word型和non-word型的分界)
- \s -- (小写 s) 匹配单个空白字符 -- 空格,换行,返回,tab 形式为
[ \n\r\t\f]
. \S (大写 S) 匹配任意的非空白字符的字符- \t, \n, \r -- tab, newline, return
- \d -- 十进制的数字 [0-9]
- ^ = start, $ = end -- 分别匹配字符开始和结束位置
- \ -- 对字符进行转义. 比如说, 用 . 匹配. 或者用 \ 匹配\;如果你不确定一个字符是否有特殊含义,比如说'@',你可以在其前面加上反斜杠,‘\@’,来保证把它当做一个字符来对待
The basic rules of regular expression search for a pattern within a string are:
通过正则表达式来搜索一个字符串是否含有匹配的模式的基本准则是:
match = re.search(pat, str)
成功, match 则非 None特别的 match.group()
表示匹配到的文本
## Search for pattern 'iii' in string 'piiig'.
## All of the pattern must match, but it may appear anywhere.
## On success, match.group() is matched text.
match = re.search(r'iii', 'piiig') => found, match.group() == "iii"
match = re.search(r'igs', 'piiig') => not found, match == None
## . = any char but \n
match = re.search(r'..g', 'piiig') => found, match.group() == "iig"
## \d = digit char, \w = word char
match = re.search(r'\d\d\d', 'p123g') => found, match.group() == "123"
match = re.search(r'\w\w\w', '@@abcd!!') => found, match.group() == "abc"
更有趣的是你可以通过 +
和 *
来指定pattern中的repetition:
对于repetition来说,首先它会找到文本最左边(leftmost)符合pattern的文本,也即第一处匹配到的地方,其次它会尽可能远的进行匹配(largest) -- i.e. +
和 *
会匹配到文本中最远的地方 (因此 +
和 *
是一种“贪婪”(gredy)的实现方式)。
## i+ = one or more i's, as many as possible.
match = re.search(r'pi+', 'piiig') => found, match.group() == "piii"
## Finds the first/leftmost solution, and within it drives the +
## as far as possible (aka 'leftmost and largest').
## In this example, note that it does not get to the second set of i's.
match = re.search(r'i+', 'piigiiii') => found, match.group() == "ii"
## \s* = zero or more whitespace chars
## Here look for 3 digits, possibly separated by whitespace.
match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') => found, match.group() == "1 2 3"
match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') => found, match.group() == "12 3"
match = re.search(r'\d\s*\d\s*\d', 'xx123xx') => found, match.group() == "123"
## ^ = matches the start of string, so this fails:
match = re.search(r'^b\w+', 'foobar') => not found, match == None
## but without the ^ it succeeds:
match = re.search(r'b\w+', 'foobar') => found, match.group() == "bar"
假设你想从字符串'xyz alice-b@google.com purple monkey'中找到邮箱地址。我们接下来通过一个动态的例子来证明正则表达式的特征,下面是一种匹配模式的尝试 r'\w+@\w+':
str = 'purple alice-b@google.com monkey dishwasher'
match = re.search(r'\w+@\w+', str)
if match:
print match.group() ## 'b@google'
这次匹配不能匹配到整个邮件地址,因为\w不能匹配到地址中的'-'和'.',我们接下来通过正则表达式的特征来进行修正。
Square brackets可以用来表示字符的集合,比如[abc] 匹配 'a' or 'b' or 'c', \w, \s 等同理。当那些具有特殊意义的字符比如说点号(.)在方括号内时,此时其就仅仅表示一个字符型的点号。对于邮箱地址这个问题,通过方括号的形式就可以很容易的在@附近把'.' 和 '-'加到字符集中,比如我们可以通过匹配模式 r'[\w.-]+@[\w.-]+'
来匹配整个邮箱地址:
match = re.search(r'[\w.-]+@[\w.-]+', str)
if match:
print match.group() ## 'alice-b@google.com'
(更多 square-bracket 特征)你可以利用一个短横线来表明范围,比如[a-z]匹配所有的小写字符。如果你不想让短横线表示范围,那么你可以把它放到最后来避免这个问题, e.g. [abc-]。再方括号表达式中的最前面加上^
表示取反,比如[^ab]意味着匹配任何不为'a' 或者 'b'的字符。
正则表达式中的"group" 允许你选出文本中匹配到的部分。假设在邮箱地址问题中我们是想分别提取出用户名和主机名,为了解决这个问题, 在用户名和主机名上的匹配模式上加上(),如:r'([\w.-]+)@([\w.-]+)'
可以解决这个问题。在这种情况下,括号不会改变匹配模式的规则,而是在匹配文本内部建立一个逻辑的"group"。对于一次成功的匹配,match.group(1)表示的是匹配文本中对应第1个左括号的匹配内容,match.group(2)则示的是匹配文本中对应第2个左括号的匹配内容。但是match.group()表示的仍然是整个匹配到的文本。
str = 'purple alice-b@google.com monkey dishwasher'
match = re.search('([\w.-]+)@([\w.-]+)', str)
if match:
print match.group() ## 'alice-b@google.com' (the whole match)
print match.group(1) ## 'alice-b' (the username, group 1)
print match.group(2) ## 'google.com' (the host, group 2)
使用正则表达式一个常见的流程是你为了寻找某个东西写一个匹配模式,然后在goups上加上括号来提取你想要的东西。
findall()
可能是re模块中最简单而又强大的函数了。上面我们所使用的re.search()
只能够找到第一个符合匹配模式的文本。findall()
可以找到所有的能够匹配上的文本并以列表的形式返回,列表中的每一个元素表示一个匹配。
## Suppose we have a text with many email addresses
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
## Here re.findall() returns a list of all the found email strings
emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com']
for email in emails:
# do something with each found email string
print email
对于文件,你可能习惯于写一个循环语句来对每一行进行遍历。如果用findall()
会更好的多。只需要把整个文件的文本内容导入到findall()
中,然后其返回一个列表即可(f.read()
返回以一个字符串的形式返回整个文件的文本内容):
# Open file
f = open('test.txt', 'r')
# Feed the file text into findall(); it returns a list of all the found strings
strings = re.findall(r'some pattern', f.read())
括号表达式()
和findall()
可以结合起来。如果匹配模式包含了两个或者以上的括号group,那么findall()
将不再以列表(list)形式返回,而是以元组(tuple)形式返回。每一个元组表示一次匹配,在元组中分别是group(1), group(2)..因此如果把括号加到右键匹配模式中,么findall()
将会返回一个列表的元组,每个元组包含用户名和主机名, e.g. ('alice', 'google.com')。
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
print tuples ## [('alice', 'google.com'), ('bob', 'abc.com')]
for tuple in tuples:
print tuple[0] ## username
print tuple[1] ## host
一旦你得到了元组的列表,你可以通过循环每一个元组来做一些你想要的运算。如果匹配模式中不包含括号,那么findall()
则返回前面所提到的列表形式。(可选的性质:有时候你在匹配模式中包含了(),但是有的内容并不是你想提取的,在这种情况下,在括号中的开头加上?:
, e.g. (?: )
,这样这个括号的内容将不会计入到结果中)。
正则表达式仅仅通过几个符号就包含了很多东西,但是正则表达式太紧凑了,因此你可能需要花费很多时间来进行调试。你可以通过调用你的匹配模式然后打印出匹配到的字符来节约的时间,比如通过在一个小的测试集上打印和测试。如果什么都匹配不到,你可以尝试减弱你的匹配模式,或者去除掉部分内容。但是当你匹配到的东西太多时,你可以逐步的增加条件来达到你想要的条件。
re 提供了一些可以修改匹配模式行为的函数。 在 search()
或者 findall()
等加上一个额外的可选的参数 flag, e.g. re.search(pat, str, re.IGNORECASE)
。
IGNORECASE -- 在匹配时忽略大小写, 此时'a'匹配'a'和'A'
DOTALL -- 允许点号(.)匹配换行符 -- 通常情况下点号匹配除换行符以外的字符。这可能是你困惑 -- 你可能认为.*
可以匹配任何东西,但是默认情况下它不会通过每一行的末尾。值得注意的是 \s
包含换行符, 因此当你想匹配包含换行符的空白符,你可以用\s*
^
和 $
匹配每一行的开始和结尾。 通常情况下 ^/$
只能够匹配整个字符串的开始和结尾的地方。假设你有一些带有标签的文本: <b>foo</b>
和 <i>so on</i>
假设你现在想要匹配每一个标签通过(<.*>)
-- 那么它会先匹配哪一个?
结果会有点令人惊讶, 由于 .*
的贪婪性导致 '<b>foo</b>
and <i>so on</i>'
会得到一个最大的匹配(尽可能远的匹配).
正则表达式有一个扩展就是在最后加上?
,如.*?
或者 .+?
,可以将它们转化为非贪婪的(non-greedy)。现在它们就会尽可能的停止。此时匹配模式(<.*?>)
会得到第一个匹配<b>
,第二个匹配</b>
,以此类推。
*?
扩展来源于 Perl, 正则表达式包括Perl's extensions统称为 Perl Compatible Regular Expressions -- pcre. Python 支持 pcre。许多命令行集如 utils 等. 都有一个是否接受 pcre 模式的标志。
一个古老但是广泛使用的技术用来解决“匹配除了某个特定字符的所有字符”是利用方括号表达式。对于上面的问题你可以不用.*
来匹配所有字符,而是利用[^>]*
来匹配除>
的字符。(在方括号中前面的 ^表示取非)。
re.sub(pat, replacement, str)
函数搜索给定的字符串中所有的例子并替换他们, 替换的字符串可以包含 '\1'
, '\2'
来表明是从原始的group(1), group(2)中来的。下面的例子是搜索所有的邮箱地址,并用yo-yo-dyne.com替换主机名。
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
## re.sub(pat, replacement, str) -- returns new string with all replacements,
## \1 is group(1), \2 group(2) in the replacement
print re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str)
## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher
英文原版:https://developers.google.com/edu/python/regular-expressions
Markdown原文:https://www.zybuluo.com/chanvee/note/87894