python3 网络爬虫开发实战-正则表达式

3.3正则表达式

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())

运行结果:

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

正则表达式:

^Hello\s\d\d\d\s\d{4}\s\w{10}

开头的 ^ 时匹配字符串的开头,也就是以Hello 开头;\s 匹配空白字符串,用来匹配目标字符串的空格;\d 匹配数字,3个\d 匹配123;然后1个\s 匹配空格;4567 依然可以用4个\d 来匹配,但是这样比较繁琐,所以后面可以跟 {4}以表示匹配前面的规则4次;再紧接一个1个空格,最后\w{10} 匹配10个字母及下划线。
match() 方法中,第一个参数传入了正则表达式,第二个参数传入了要匹配的字符串。
SRE_Match对象有两个方法:group() 方法可以输出匹配到的内容;span() 方法可以输出匹配的范围。

匹配目标

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

import re

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

运行结果:

Hello 1234567 World
1234567
(0, 19)

group() 输出完整的匹配结果,group(1) 会输出第一个被() 包围的匹配结果。
如果正则表达式后面还有() 包围的内容,可以依次用group(2), group(3) 来获取。

通用匹配

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

import re

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

运行结果:

<_sre.SRE_Match object; span=(0, 41), match='Hello 123 4567 World_This is a Regex Demo'>
Hello 123 4567 World_This is a Regex Demo
(0, 41)

$用于匹配一行字符串的结尾

贪婪与非贪婪
import re

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

运行结果:

7
(0, 40)

贪婪模式下,.*会匹配尽可能多的字符。上面的正则表达式中.* 后面是\d+,也就是至少一个数字,并没有指定具体多少个数字,因此,.* 就尽可能匹配多的字符,把123456匹配了,给\d+ 留下一个可满足条件的数字7
其实,这里使用非贪婪匹配就好了。非贪婪匹配的写法是.*?,多一个?

import re

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

运行结果:

1234567
(0, 40)

贪婪模式是尽可能匹配多的字符,非贪婪模式是尽可能匹配少的字符。

在做匹配的时候,字符串中间尽量使用非贪婪匹配,以免出现匹配结果缺失的情况。
注意: 如果匹配的结果在字符串的结尾,.*?就可能匹配不到任何内容了,因为它会匹配尽可能少的字符。例如:

import re

content = 'http://weibo.com/comment/kEraCN'
result1 = re.match('http.*?comment/(.*?)',content)
result2 = re.match('http.*?comment/(.*)',content)
print(result1.group(1))
print(result2.group(1))

运行结果:


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\xxxxxxx\Desktop\tttest\regexp_test\match_test.py", line 7, in <module>
    print(result.group(1))
AttributeError: 'NoneType' object has no attribute 'group'

运行报错说明正则表达式没有匹配到这个字符串,返回结果为None ,而我们又调用了group() 方法导致AttributeError

.匹配的是除换行符之外的任意字符,当遇到换行符时,.*?就不能匹配了,这里只需要加入一个修饰符 re.S ,即可修复这个错误:

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

此时运行结果:

1234567
修饰符描述
re.I使匹配对大小写不敏感
re.L做本地化识别(locale-aware) 匹配
re.M多行匹配,影响^ 和 $
re.S使 . 匹配包括换行在内的所有字符
re.U根据 Unicode 字符集解析字符。这个标志影响\w、\W、\b 和\B
re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解

在网页匹配中,较为常用的有 re.S 和 re.I。

转义匹配

如果目标字符串里面就包含. ,该怎么办?这就需要用到转义匹配了。
示例:

import re

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

match() 方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败了。

import re

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

运行结果:

Traceback (most recent call last):
  File "c:\Users\xxxxxx\Desktop\tttest\regexp_test\match_test.py", line 5, in <module>
    print(result.group())
AttributeError: 'NoneType' object has no attribute 'group'

这里的字符串以Extra 开头,但是正则表达式以 Hello 开头,整个正则表达式是字符串的一部分,但是这样匹配是失败的。

因为 match() 方法在使用时需要考虑到开头的内容,这样做匹配时并不方便。它更适合用来检测某个字符串是否适合某个正则表达式的规则。

search() 方法,在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。
把上面代码中的match() 方法修改成 search() :

import re

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

运行结果:

<_sre.SRE_Match object; span=(14, 54), match='Hello 1234567 World_This is a Regex Demo'>
1234567

再看一个实例:

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.mpe" 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>'''

尝试提取 class 为 active 的 li 节点内部的超链接包含的歌手,此时需要提取第三个 li 节点下 a节点的singer 属性和文本

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.mpe" 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))

运行结果:

齐秦 往事随风

如果去掉正则表达式中的active

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

运行结果:

任贤齐 沧海一声笑

如果去掉正则表达式中的re.S

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

运行结果:

beyond 光辉岁月
findall()

search() 方法可以返回正则表达式的第一个内容,如果想要获取匹配正则表达式的所有内容,这时需要借助 findall() 方法。

依然是上面的HTML文本,如果想要获取所有 a 节点的超链接、歌手和歌名,就可以将search() 换成 findall() 。如果有返回结果的话,就是列表类型,所以需要遍历来依次获取

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

运行结果:

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

返回列表中的每个元素都是元组类型,使用对应的索引依次取出即可。

sub()

除了使用正则表达式提取信息,有时候还需要借助它来修改文本。比如想要把一串文本中的数字都去掉,可以借助sub() 方法。
示例:

import re

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

运行结果:

aKyroiRixLg

在前面的HTML文本中,如果想获取所有 li 节点的歌名,直接用正则表达式提取可能比较繁琐,比如写成这样:

results = re.findall('<li.*?>\s*?(<a.*?>)?(\w+)(</a>)?\s*?', html, re.S)
for result in results:
    print(result[1])

运行结果:

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

此时借助sub() 方法就比较简单了。可以先用 sub() 方法将 a 节点去掉,只留下文本,然后再利用findall() 提取:

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

运行结果:

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

个人理解:通过百度得知 | 的作用相当于分组

re.sub('<a.*?>|</a>', '',html)

无论时匹配到<a.*?> 还是匹配到,都会替换为空!

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

compile()

compile() 方法可以将正则字符串编译成正则表达式对象。示例:

import re

content1 = '2016-12-15 12:00'
content2 = '2016-12-17 12:55'
content3 = '2016-12-22 13:21'
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)

运行结果:

2016-12-15  2016-12-17  2016-12-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值