正则表达式

正则表达式是处理字符串的强大工具,

一、实例引入

进入网站

输入待匹配的文本:Hello, my phone number is 010-86432100 and email is cqc@cuiqingcai.com

输入正则表达式:

[a-zA-Z]+://[^\s]*

在网页右边选择“匹配Email地址”,就可以看到下方出现了文本中的

E-mail。

对于URL来说,就可以使用下面的正则表达式去匹配

[a-zA-Z]+://[^\s]*

a-z代表匹配任意小写字母,A-Z代表匹配任意大写字母

^匹配一行字符串的开头,\s表示匹配任意空白字符,

*代表匹配前面的字符任意多个。

常用的匹配规则

模式

规则

\w

匹配字母、数字、下划线

\W

匹配不是字母、数字、下划线

\s

匹配任意空白字符,等价于[\t \n \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

()

匹配括号内的表达式,也表示一组

二、match()

match(正则表达式, 待匹配的字符串)

match()是从字符串开头进行匹配,如果开头就错误则匹配失败。

可以检测字符串是否匹配正则表达式,成功返回匹配成功的结果,失败返回None。

import re

content = 'Hello 123 4567 World_This is a Regex Demo'
print(len(content))
result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}', content)
print(result)
print(result.group())
print(result.span())


'''
^Hello 
\s 任意一个空白字符
\d 任意一个数字
\d{4} 任意四个数字
\w{10} 任意数字、字母、下划线10个

group():输出匹配到的内容
span():输出匹配范围


结果:
41
<_sre.SRE_Match object; span=(0, 25), match='Hello 123 4567 World_This'>
Hello 123 4567 World_This
(0, 25)
'''

匹配目标

使用 ( )括号

如果想从字符串中提取出一部分内容,该怎么办?

可以使用( )括号,将想提取的子字符串括起来。

()实际上标记了一个子表达式的开始和结束的位置,被标记的每个子表达式会依次对应每个分组,调用group()方法传入分组的索引即可获取提取的结果。

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())


'''
结果:
<_sre.SRE_Match object; span=(0, 19), match='Hello 1234567 World'>
Hello 1234567 World
1234567
(0, 19)
'''

通用匹配

上面出现空格就写\s,出现数字就写\d,这样工作量非常大。完全没有必要这么写,可以使用万能匹配。

万能匹配:.*(点星)。点:可以匹配任意字符除换行符,星:代表前面匹配的字符无限次

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello.*Demo$', content)
print(result)
print(result.group())
print(result.span())


'''
. 匹配任意字符除换行符
* 前面的匹配无限次
$ 匹配字符串的结尾,在这里表示Demo后面就是结尾

结果:
<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
Hello 1234567 World_This is a Regex Demo
(0, 40)
'''

贪婪与非贪婪

import re

content = 'Hello 1234567 World_This is a Regex Demo'

result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))

'''
结果:
<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
7


通过上面的结果发现,匹配的结果并不是我们想要的结果。

这里依然想获取中间的数据部分,所以中间是(\d+)。

而数字两侧内容比较杂乱,所以想省略就写成了.*。最后,组成 ^He.*(\d+).*Demo$
'''

贪婪匹配:

在贪婪匹配下,.*会匹配尽可能多的字符。

正则表达式中.*后面是\d+,也就是知道匹配一个数字,并没有指定具体多少个数字。

因此.*就尽可能多的匹配字符。在上面的案例中就把123456匹配了,给\d+留下了一个可满足条件的7。

非贪婪匹配:

按照上面的例子。非贪婪匹配的写法是:.*?,多了一个问号。

非贪婪匹配就是尽可能少的匹配字符。当.*?匹配到Hello后面的空白字符串时,再往后的字符就是数字

了,而\d+恰好可以匹配,那么这里.*?就不再进行匹配,交给\d+去匹配后面的数字。

import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*?(\d+).*?Demo$', content)
print(result)
print(result.group(1))

'''
结果:
<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
1234567
'''

需要注意一下,如果匹配的结果在字符串的结尾,那么非贪婪模式的.*?就有可能匹配不到任何内容。因为非贪婪模式是尽可能少的匹配内容。

import re

content = 'http://weibo.com/comment/KEraCN'
result1 = re.match('http.*?comment/(.*?)', content)
result2 = re.match('http.*?comment/(.*)', content)
print('result1非贪婪模式:', result1.group(1))
print('result2贪婪模式:', result2.group(1))


'''
结果:
result1非贪婪模式: 
result2贪婪模式: KEraCN
'''

修饰符

import re

content = '''Hello 1234567 World_This 
is a Regex Demo'''

result = re.match('^He.*?(\d+).*?Demo$', content)
print(result.group(1))

'''
结果:
Traceback (most recent call last):
  File "C:/Users/22270/Desktop/爬虫学习/正则表达式.py", line 61, in <module>
    print(result.group(1))
AttributeError: 'NoneType' object has no attribute 'group'
'''

当运行上面的代码就会报错。

原因是.*?中的 点是匹配除换行符之外的任意字符。在This后面我们加了一个换行,当点遇到换行符的时就不再进行匹配了。

解决办法也很简单。增加一个修饰符

import re

content = '''Hello 1234567 World_This 
is a Regex Demo'''

result = re.match('^He.*?(\d+).*?Demo$', content, re.S)
print(result.group(1))

'''
结果:
1234567
'''

re.S这个修饰符的作用是:使点匹配包括换行符在内的所有字符。

这个re.S在网页匹配中经常用到。因为HTML节点经常会有换行,加上它,就可以匹配节点与节点之间的换行了。

修饰符

修饰符

描述

re.I

使匹配对大小写不敏感

re.L

做本地化识别(locale-aware)匹配

re.M

多行匹配,影响^和$

re.S

使 . 匹配包括换行符在内的所有字符

re.U

根据Unicode字符集解析字符。影响\w \W \b和\B

re.X

该标志通过给予你更灵活的格式以便你将正则表达式写的更易于理解


转义匹配

使用\来进行转义

import re

content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result)

'''
结果:
<_sre.SRE_Match object; span=(0, 17), match='(百度)www.baidu.com'>
'''

当遇到用于正则表达式匹配模式的特殊字符的时候,咋前面加反斜杠转义一下即可。

例如:.可以使用\.来匹配。

三、search()

search(正则表达式, 待匹配的字符串)

与match()的不同:

  • match()方法要求必须从字符串的开头进行匹配,如果字符串的开头不匹配,整个匹配就失败了
  • search()并不要求必须从字符串的开头进行匹配,也就是说,正则表达式可以是字符串的一部分

search()在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。也就是说,正则表达式可以是字符串的一部分,在匹配时,serch()方法会依次扫描字符串,直到找到第一个符合规则的字符串,然后返回匹配内容。如果搜索完还没有找到,就返货None

import re

content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
result = re.search('Hello.*?(\d+).*?Demo', content)
print(result)


'''
结果:
<_sre.SRE_Match object; span=(13, 53), match='Hello 1234567 World_This is a Regex Demo'>
'''

练习一下:

import re

html = '''
<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>
'''

result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S)
if result:
    print(result.group(1), result.group(2))
    

'''
结果:
齐秦 往事随风
'''

四、findall()

如果想要获取匹配正则表达式的所有内容。可以使用findall()方法了

import re

html = '''
<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>
'''

results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)
print(results)
print(type(results))
for result in results:
    print(result[0], result[1], result[2])
    

    
'''
结果:
[('/2.mp3', '任贤齐', '沧海一声笑'), ('/3.mp3', '齐秦', '往事随风'), ('/4.mp3', 'beyond', '光辉岁月'), ('/5.mp3', '陈慧琳', '记事本'), ('/6.mp3', '邓丽君', '但愿人长久')]
<class 'list'>
/2.mp3 任贤齐 沧海一声笑
/3.mp3 齐秦 往事随风
/4.mp3 beyond 光辉岁月
/5.mp3 陈慧琳 记事本
/6.mp3 邓丽君 但愿人长久
'''

分析结果发现,返回的全部结果会放在一个列表中,每一条结果放在元组中。

如果只是获取第一个内容可以使用search()方法,获取多个结果可以使用findall()方法。

五、sub()

修改文本。

比如:想把一串文本中的所有数字去掉

sub(正则表达式,替换的内容,原字符串)

import re

content = '54fd78thaera284'
result = re.sub('\d+', '', content)
print(result)


'''
结果:
fdthaera
'''

按照上面的案例:

import re

html = '''
<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul>
</div>
'''

result = re.sub('<a.*?>|</a>', '', html)
print(result)
results = re.findall('<li.*?>(.*?)</li>', result, re.S)
for result in results:
    print(result.strip())

    
'''
结果:
<div id="songs-list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路有你</li>
<li data-view="7">
沧海一声笑
</li>
<li data-view="4" class="active">
往事随风
</li>
<li data-view="6">光辉岁月</li>
<li data-view="5">记事本</li>
<li data-view="5">
但愿人长久
</li>
</ul>
</div>

一路有你
沧海一声笑
往事随风
光辉岁月
记事本
但愿人长久
'''

经过sub()方法处理后a标签没有了,然后再通过findall()方法直接提取即可。

可以看到,在适当的时候,使用sub()方法可以起到事半功倍的效果。

六、compile()

可以将正则字符串编译成正则表达式对象,方便以后的重复使用。

import re

content1 = '2021-2-10 20:30'
content2 = '2019-5-02 20:30'
content3 = '2008-7-18 20:30'

pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern, '', content1)
result2 = re.sub(pattern, '', content2)
result3 = re.sub(pattern, '', content3)
print(result1)
print(result2)
print(result3)

'''
结果
2021-2-10 
2019-5-02 
2008-7-18 
'''

有三个日期,分别将三个日期中的时间部分去掉,这是可以借助sub()方法,sub方法第一个参数是正则表达式,但是没必要重复写3个同样的表达式,此时可以借助compile()方法将正则表达式编译成正则表达式对象,重复使用正则表达式对象就好了。

compile()方法还可以传入修饰符。