正则表达式是处理字符串的强大工具,它有自己特定的语法结构,可以实现字符串的检索、替换、匹配验证。
打开开源中国提供的正则表达式测试工具https://tool.oschina.net/regex/
,输入带匹配的文本,然后选择常用的正则表达式,就可以得到相应的匹配结果。
运行界面
其实,这里就是使用的正则表达式匹配,也就是用一定的规则将特定的文本提取出来。
对于电子邮件来说可以使用
[\w!#%&'+/=?^_`~-]+)@(?:\w?.)+\w?
将它匹配出来。
上面的一大串字符看起来是不是一团糟,现在我就将常用的匹配规则列举出来。
模式 | 描述 |
---|---|
\w | 匹配字母、数字及下划线 |
\W | 匹配不是字母、数字及下划线的字符 |
\s | 匹配任意空白字符,相当于{\t\n\r\f} |
\S | 匹配任意非空白字符 |
\d | 匹配任意数字,等价于[0-9] |
\D | 匹配任意非数字字符 |
\A | 匹配字符串开头 |
\Z | 匹配字符串的结尾,如果存在换行,只匹配到换行前的字符串 |
\z | 匹配字符串的结尾,如果存在换行,同时还会匹配换行符 |
\G | 匹配最后完成匹配的位置 |
\n | 匹配换行符 |
\t | 匹配制表符 |
^ | 匹配一行字符串的开头 |
$ | 匹配一行字符串的结尾 |
. | 匹配除换行符外的任意字符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符 |
[...] | 用来表示一组字符单独列出,比如[amk]匹配a,m,k |
[^...] | 不在[]中的字符,比如^abc,表示匹配除了a,b,c之外的字符 |
* | 匹配0个或多个表达式 |
+ | 匹配1个或多个表达式 |
? | 匹配0个或1个前面正则表达式定义的片段(非贪婪匹配) |
{n} | 精确匹配n个前面的表达式 |
{n,m} | 匹配n到m次,由前面正则表达式匹配的片段(贪婪匹配) |
a|b | 匹配a或b |
( ) | 匹配括号内的表达式,也表示一个组 |
看完之后会不会有点晕呢?
不用担心,接下来我在这边会详细的说明这个规则的用法。
其实正则表达式不是Python独有的,它也可以在其他编程语言使用。在Python中使用re这个库,提供了正则表达式的实现,利用这个库,可以在Python中使用正则表达式。
这里先介绍一个常用的匹配方法——match(),向它传入需要匹配的字符串及正则表达式,就可以检测这个正则表达式是否匹配字符串。
match( )方法是从字符串起始位置匹配正则表达式,如果匹配就返回匹配成功的结果,如果不匹配就返回None。
示例如下
import re
content = 'Hello 123 456 World_This is a Regex Demo'
print(len(content))
result = re.match('^Hello\s\d\d\d\s\d{3}\s\w{10}', content)
print(result)
print(type(result))
print(result.group())
print(result.span())
这里声明了一个字符串,其中包含英文字符、空白符、数字等。
那么现在就对刚刚写出的正则表达式进行简单分析。
开头的^
表示匹配字符串的开头,也就是说以Hello开头;然后\s
匹配空白字符;\d
表示匹配数字;\d{3}
代表前面的规则匹配3次;\w
表示匹配数字、字母及下划线;{10}
表示前面的规则匹配10次。
你可以试着运行上面的这段代码,你会发现我们并没有将字符串匹配完全,不过依然可以进行匹配,只不过是匹配的结果短一点。
在match( )方法中,第一个参数是正则表达式,第二个参数是传入要匹配的字符串。
打印输出结果可以看到结果是SRE_Match对象,这证明成功匹配。该对象有两个方法:group( )方法可以输出打印内容;span()方法可以输出匹配的范围。
刚刚使用match( )方法可以匹配到字符串的内容,如果想要从字符串中提取一部分内容,可以使用( )括号,将想要提取的子字符串括起来,( )实际上标记了一个子表达式开始和结束的位置,被标记的每个子表达式会依次对应每一个分组,调用group( )方法传入分组的索引可以获取提取的结果。
示例如下
import re
content = 'Hello 123456 World_This is a Regex Demo'
print(len(content))
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result)
print(type(result))
print(result.group())
print(result.group(1))
print(result.span())
你可以试着编写并运行上面的示例代码你会发现,我们成功的获取到了123456。这里用的是group(1),与group()不同的是,后者获取完整的匹配结果,而前者会输出被( )包围的匹配结果,以后还会使用group(2)、group(3)获取匹配结果。
刚才我们写的正则表达式其实还是比较复杂的,出现空白符就用\s,出现数字就用\d匹配,这样的工作量还是比较大的。其实根本没有必要,可以使用万能匹配,那就是.*,其中 .(点) 可以匹配任意字符(换行符除外),(星号)表示匹配前面的字符无限次,所以它们组合在一起就可以匹配任意字符了。
示例如下
import re
content = 'Hello 123 456 World_This is a Regex Demo'
print(len(content))
result = re.match('^Hello.*Demo$', content)
print(result)
print(result.group())
print(result.span())
使用上面的通用匹配.*时,可能有时候匹配到的并不是我们想要的结果。看下面的例子:
import re
content = 'Hello 123456 World_This is a Regex Demo'
print(len(content))
result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))
print(result.span())
通过上面的代码,你会发现匹配的结果是7,但是这个不是不是我们想要的结果。
这里就涉及一个贪婪匹配与与非贪婪匹配。在贪婪模式下会尽可能的匹配多的字符。正则表达式中.*后面是\d+,也就是至少一个数字,并没有指定具体多少个数字。
因此,.*就匹配尽可能多的字符串,把12345都匹配了,留下满足\d的数字。
其实这里只需要使用非贪婪匹配就好了,非贪婪匹配的写法是.*? ,多了个?,可以来看看有什么样的效果。
import re
content = 'Hello 123456 World_This is a Regex Demo'
print(len(content))
result = re.match('^He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))
print(result.span())
运行上面的代码,你会清楚的看到已经获取到了123456。
非贪婪匹配是尽量匹配少的字符,当匹配到数字的时候就不往下匹配了,那么\d+便刚好可以匹配下去。
但是要注意,如果匹配的结果在字符串的结尾,那么.*?就匹配不到任何结果,因为非贪婪匹配尽可能少的内容。
正则表达式可以使用包含可选标志修饰符来控制匹配的模式,修饰符被指定为一个可选的标志。
示例如下:
import re
content = '''Hello 123456
World_This is a Regex Demo'''
print(len(content))
result = re.match('^He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))
print(result.span())
运行结果
None
Traceback (most recent call last):
File "D:/github/Python_scrapy/爬虫笔记+源码/第四章 提取数据/code/demo5.py", line 9, in <module>
print(result.group(1))
AttributeError: 'NoneType' object has no attribute 'group'
返回值为None,因此导致出现AttributeError
这个错误。原因是 .(点) 只能匹配除换行符号外的任意字符。在上面的程序中,你会发现,中间多了个换行符,因此匹配失败。
在这里只需要添加修饰符re.S,即可修正这个错误。
result = re.match('^He.*?(\d+).*Demo$', content, re.S)
这个re.S经常在网页匹配中用到,在HTML中经常会有节点的换行。
下面列举一些常见的修饰符
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(local-aware)匹配 |
re.M | 多行匹配影响^和$ |
re.U | 根据Unicode字符集解析字符,这个标志影响\w、\W、\b和\B |
re.X | 该标志通过给予你更灵活的格式使正则表达式写的得更易于理解 |
re.S | 匹配包括换行在内的所有字符 |
我们知道正则表达式中定义了不少的匹配模式,如:匹配换行符以外的其他字符,但是如果目标字符串了面包含 .(点),那该怎么办呢?
这里就需要转义匹配了。
代码示例:
import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result.group())
运行上面的代码,你会发现成功匹配源字符串。
前面提过,match( )方法是从字符串的开头处开始匹配的,一旦字符串的开头不匹配,那么整个字符串就失效了。
因为match( )方法进行匹配时需要考虑是否符合从开头位置匹配,这样对我们来说不是特别的方便。
这里有另外一种方法,那就是search( )方法,它在匹配的时候会扫描整个字符串,直到找到符合匹配规则的第一个字符串。
search( )与match( )使用方法相似。
前面提到过search( )的使用方法,它可以匹配符合规则的第一个字符串,但是想要匹配符合规则的全部字符串就需要借助findall( )方法。该方法会搜索整个字符串,然后匹配所有符合规则的字符串,用法与search( ) 和match( )相同。
除了使用正则表达式匹配字符串之外,还可以使用正则表达式来修改文本,比如想要把一个字符串中的所有数字全部去除,如果使用字符串的replace()方法就会显得很繁琐,这里可以借助sub( )方法,具体代码如下所示:
import re
content = 'sdsd55wee66err33'
result = re.sub('\d+', '', content)
print(result)
运行上面的代码,你会发现,已经将字符串中的所有数字都去除掉了。
前面所提过的方法都是用来处理字符串的,现在介绍一下compile( )方法,这个方法可以将正则字符串编译成正则表达式对象。以便在后面的匹配中复用,具体代码如下所示:
import re
content1 = '2020-12-29 02:35'
content2 = '2020-12-30 03:35'
content3 = '2020-12-31 01:35'
pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern, '', content1)
result2 = re.sub(pattern, '', content2)
result3 = re.sub(pattern, '', content3)
print(result1, result2, result3)
运行上面的代码,你会发现时与分
都被去掉了,并且匹配规则只写了一次。
同样的compile( )方法也可以传入修饰符re.S、re.I等等,这样在match( )、search( )、findall( )都不需要额外在传。