正则表达式


title: 爬虫系列之正则表达式
date: 2019-04-15 15:42:15
tags: 爬虫
categories: 爬虫
toc: true


什么是正则表达式
正则表达式是对字符串操作的一种逻辑公式,就是 事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符”,这个“规则字符” 来表达对字符的一种过滤逻辑。

正则并不是python独有的,其他语言也都有正则
python中的正则,封装了re模块

常用的匹配规则

\w      匹配字母数字及下划线
\W      匹配非字母数字下划线
\s      匹配任意空白字符,等价于[\t\n\r\f]
\S      匹配任意非空字符
\d      匹配任意数字
\D      匹配任意非数字
\A      匹配字符串开始
\Z      匹配字符串结束,如果存在换行,只匹配换行前的结束字符串
\z      匹配字符串结束
\G      匹配最后匹配完成的位置
\n      匹配一个换行符
\t      匹配一个制表符
^       匹配字符串的开头
$       匹配字符串的末尾
.       匹配任意字符,除了换行符,re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符
[....]  用来表示一组字符,单独列出:[amk]匹配a,m或k
[^...]  不在[]中的字符:[^abc]匹配除了a,b,c之外的字符
*       匹配0个或多个的表达式
+       匹配1个或者多个的表达式
?       匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n}     精确匹配n前面的表示
{m,m}   匹配n到m次由前面的正则表达式定义片段,贪婪模式
a|b     匹配a或者b
()      匹配括号内的表达式,也表示一个组

看完这些之后是不是有些晕呢?不要着急,后面我们会详细地讲解一下常见的规则的用法

常用的的匹配方法
1、re.match()
这是第一个匹配方法,它会尝试从字符串的起始位置匹配表达式,如果匹配,则会返回匹配的结果,否则就会返回None。示例如下:

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

运行结果如下:

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

这里首先声明了一个字符串,其中包含英文字母、空白字符、数字等。接下来,我们写一个正则表达式:^\w+\s(\d+)\s\d+
用它来匹配字符串,这个是以字符串开头,然后\s匹配空白字符,用来匹配目标字符串的空格;\d匹配数字,最后以数字结尾的。
而在**match()**方法中,第一个参数传入了正则表达式,第二个参数传入了要匹配的字符串。
打印输出结果,可以看到结果是SRE_Match对象,这证明成功匹配。该对象有两个方法:group()方法可以输出匹配到的内容,结果是Hello 123 4567 ,这恰好是正则表达式规则所匹配的内容;span()方法可以输出匹配的范围,结果是(0, 14),这就是匹配到的结果字符串在原字符串中的位置范围。
匹配目标
刚才我们用match()方法可以得到匹配到的字符串内容,但是如果想从字符串中提取一部分内容,该怎么办呢?就像最前面的实例一样,从一段文本中提取出邮件或电话号码等内容。

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

import re
 
content = 'Hello 123 4567 World_This is a Regex Demo'
print(len(content))
result = re.match('^\w+\s(\d+)\s\d+', content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())

运行结果如下:

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

贪婪和非贪婪匹配
使用上面的通用匹配.*时,可能有时候匹配到的并不是我们想要的结果。看下面的例子:

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

运行结果:

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

我们惊奇的发现,只匹配到了数字7,前面的数据并没有匹配,这是为什么呢?
这是因为贪婪匹配是尽可能多的匹配,当我们在使用.进行匹配时,它就会尽可能多得匹配,然后只剩下个7满足\d的条件即可,所以我们就匹配失败了,所以我们要怎样解决这个问题呢?当然就是非贪婪匹配.?,它与贪婪匹配,恰恰相反,它是尽可能少得匹配字符,示例如下:

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

运行结果:

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

这下我们就可以完美的匹配到全部的数字了

转义字符
当我们要匹配的内容中存在特殊字符的时候,就需要用到转移符号,例子如下:

import re

content= "price is $5.00"

result = re.match('price is \$5\.00',content)
print(result)
print(result.group())

运行结果:

<_sre.SRE_Match object; span=(0, 14), match='price is $5.00'>
price is $5.00

强调re.match是从字符串的起始位置匹配一个模式

2、re.search()
前面提到过,match()方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败了。因为match()方法在使用时需要考虑到开头的内容,这在做匹配时并不方便。它更适合用来检测某个字符串是否符合某个正则表达式的规则。
而re.search扫描整个字符串返回第一个成功匹配的结果,示例如下:

import re

content = "extra things hello 123455 world_this is a Re Extra things"

result = re.search('\d+.*?',content)
print(result)
print(result.group())

结果:

<_sre.SRE_Match object; span=(19, 25), match='123455'>
123455

上面例子的意思就是从给定的字符串中匹配出所有的数字

**注意:**所以为了匹配方便,我们会更多的用search,不用match,match必须匹配头部,所以很多时候不是特别方便

匹配演练:
从下面的网页表格中,匹配出beyond的光辉岁月。我们仔细一看会发现,下面的表单有空格或者换行符之类的出现,所以要匹配出我们想要的还是很容易的。

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>'''

具体的代码:

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

结果:

<_sre.SRE_Match object; span=(389, 448), match='<li data-view="6"><a href="/4.mp3" singer="beyond>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a>
beyond
光辉岁月

由于绝大部分的HTML文本都包含了换行符,所以尽量都需要加上re.S修饰符,以免出现匹配不到的问题。

3、re.findall()
前面我们介绍了search()方法的用法,它可以返回匹配正则表达式的第一个内容,但是如果想要获取匹配正则表达式的所有内容,那该怎么办呢?这时就要借助findall()方法了。该方法会搜索整个字符串,然后返回匹配正则表达式的所有内容。
还是上面的HTML文本,如果想获取所有a节点的超链接、歌手和歌名,就可以将search()方法换成findall()方法。如果有返回结果的话,就是列表类型,所以需要遍历一下来依次获取每组内容。代码如下:

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

运行结果如下:

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

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

如果只是获取第一个内容,可以用search()方法。当需要提取多个内容时,可以用findall()方法。

4、re.sub()
除了使用正则表达式提取信息外,有时候还需要借助它来修改文本。比如,想要把一串文本中的所有数字都去掉,如果只用字符串的replace()方法,那就太烦琐了,这时可以借助sub()方法。示例如下:

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

运行结果如下:aKyroiRixLg

5、re.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)

例如,这里有3个日期,我们想分别将3个日期中的时间去掉,这时可以借助sub()方法。该方法的第一个参数是正则表达式,但是这里没有必要重复写3个同样的正则表达式,此时可以借助compile()方法将正则表达式编译成一个正则表达式对象,以便复用。

运行结果如下:

2016-12-15  2016-12-17  2016-12-22 

另外,compile()还可以传入修饰符,例如re.S等修饰符,这样在search()、findall()等方法中就不需要额外传了。所以,compile()方法可以说是给正则表达式做了一层封装,以便我们更好地复用。

到此为止,正则表达式的基本用法就介绍完了,后面会通过具体的实例来讲解正则表达式的用法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值