一 match()的不足
1 点睛
match()方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败了。
2 代码
import re
# 这里的字符串以Extra开头,但是正则表达式以Hello开头,
# 整个正则表达式是字符串的一部分,但是这样匹配是失败的。
content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
result = re.match('Hello.*?(\d+).*?Demo', content)
print(result)
3 结果
E:\WebSpider\venv\Scripts\python.exe E:/WebSpider/3_3.py
None
4 说明
因为match()方法在使用时需要考虑到开头的内容,这在做匹配时并不方便。它更适合用来检测某个字符串是否符合某个正则表达式的规则。
二 search()方法的引入
1 点睛
方法search(),它在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。也就是说,正则表达式可以是字符串的一部分,在匹配时,search()方法会依次扫描字符串,直到找到第一个符合规则的字符串,然后返回匹配内容,如果搜索完了还没有找到,就返回None。
2 代码
import re
# 这里的字符串以Extra开头,但是正则表达式以Hello开头,
# 整个正则表达式是字符串的一部分,但是这样匹配是失败的。
content = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'
# result = re.match('Hello.*?(\d+).*?Demo', content)
# 将match换成search
result = re.search('Hello.*?(\d+).*?Demo', content)
print(result)
3 结果
E:\WebSpider\venv\Scripts\python.exe E:/WebSpider/3_3.py
<_sre.SRE_Match object; span=(13, 53), match='Hello 1234567 World_This is a Regex Demo'>
4 说明
为了匹配方便,我们可以尽量使用search()方法。
三 实战
1 需要爬取的文本
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="邓丽君"><i class="fa fa-user"></i>但愿人长久</a>
</li>
</ul>
</div>'''
2 需求
可以观察到,ul节点里有许多li节点,其中li节点中有的包含a节点,有的不包含a节点,a节点还有一些相应的属性——超链接和歌手名。
首先,我们尝试提取class为active的li节点内部的超链接包含的歌手名和歌名,此时需要提取第三个li节点下a节点的singer属性和文本。
3 分析
此时正则表达式可以以li开头,然后寻找一个标志符active,中间的部分可以用.*?来匹配。接下来,要提取singer这个属性值,所以还需要写入singer="(.*?)",这里需要提取的部分用小括号括起来,以便用group()方法提取出来,它的两侧边界是双引号。然后还需要匹配a节点的文本,其中它的左边界是>,右边界是</a>。然后目标内容依然用(.*?)来匹配,所以最后的正则表达式就变成了:
<li.*?active.*?singer="(.*?)">(.*?)</a>
然后再调用search()方法,它会搜索整个HTML文本,找到符合正则表达式的第一个内容返回。
另外,由于代码有换行,所以这里第三个参数需要传入re.S。
4 代码
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="邓丽君"><i class="fa fa-user"></i>但愿人长久</a>
</li>
</ul>
</div>'''
import re
result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S)
if result:
print(result.group(1), result.group(2))
5 运行结果
E:\WebSpider\venv\Scripts\python.exe E:/WebSpider/3_3.py
齐秦 往事随风
6 说明
由于需要获取的歌手和歌名都已经用小括号包围,所以可以用group()方法获取。
可以看到,这正是class为active的li节点内部的超链接包含的歌手名和歌名。
7 去掉正则表达式中的 active
import re
# result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S)
# 去掉active
result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html, re.S)
if result:
print(result.group(1), result.group(2))
8 结果
E:\WebSpider\venv\Scripts\python.exe E:/WebSpider/3_3.py
任贤齐 沧海一声笑
9 说明
把active标签去掉后,从字符串开头开始搜索,此时符合条件的节点就变成了第二个li节点,后面的就不再匹配,所以运行结果就变成第二个li节点中的内容。
10 去掉re.S
import re
# result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S)
# 去掉active
# result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html, re.S)
# 去掉re.S
result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html)
if result:
print(result.group(1), result.group(2))
11 结果
E:\WebSpider\venv\Scripts\python.exe E:/WebSpider/3_3.py
beyond 光辉岁月
12 说明
可以看到,结果变成了第四个li节点的内容。这是因为第二个和第三个li节点都包含了换行符,去掉re.S之后,.*?已经不能匹配换行符,所以正则表达式不会匹配到第二个和第三个li节点,而第四个li节点中不包含换行符,所以成功匹配。
由于绝大部分的HTML文本都包含了换行符,所以尽量都需要加上re.S修饰符,以免出现匹配不到的问题。