爬虫基本库的使用之正则表达式

我们已经知道在上一条requests库来获取网页的源代码,得到HTML代码。但我们想要的数据是包含在HTML代码之中的,那么要怎么才能从HTML代码中获取想要的信息呢?正则表达式就是其中一个有效的方法。

1.实例引入


​​​​​​下面用几个实例先来看一下它的用法
打开开源中国提供的正则表达式测试工具:http://tool.oschina.net/regex/,输入待匹配的文本,然后选择常用的正则表达式,就可以得出相应的匹配结果了。
例如,这里输入如下待匹配文本:
Hello, my phone number is 010-86432100 and email is cqc@cuiqingcai.com,    and my website is
https://cuiqingcai.com
这段字符串中包含一个电话号码,一个E-mail地址和一个URL,接下来就尝试用正则表达式将这些内容提取出来

打开开源正则匹配网址,在网页右侧选择“匹配Email地址”,就可以看到下方出现了文本中的E-mail,如下图:

 如果选择‘匹配网址URL’,可以看到下方出现了文本中的URL,如下图:

 其实,这里使用了正则表达式匹配,也就是用一定的规则将特定文本提取出来。

那么我将列出所有常见的匹配规则,如下图:

 看完这表确实会感觉有点晕晕的,下面我会详细的讲解一些常见规则的用法。

2.匹配方法之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\d\s\d{4}\s\w{10}

开头的^表示字符串的开头,也就是以Hello开头;然后\s表示匹配空白字符,用来匹配目标字符串里Hello后面的空格;\d表示匹配数字,3个\d用来匹配123;紧接着的一个\s表示匹配空格;目标字符串的后面还有4567,我们其实可以不用重复\d四遍,而是用\d后面跟{4}的形式代表匹配四个数字;后面又是一个空白字符,最后\w{10}则表示匹配十个字母及下划线。我其实没有把整个字符串匹配完全,不过这样也是可以的,只是匹配结果短一点而已。

在match方法中,第一个参数是传入了正则表达式,第二个参数是传入了要匹配的字符串。

输出结果中span方法是输出匹配的范围,group方法是放回匹配的内容。

(1).匹配目标

用match方法确实可以实现匹配,但是如果想要提取字符串中一部分内容,该怎么办呢?

这个时候我们可以使用()来将想要提取的子字符括起来,实例如下:

​
import re

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

​

输出结果如下:

 可以看到,我们成功的得到了1234567。这里用的是group(1),它与group()有所不同,后者也会输出完整的匹配结果,前者会输出第一个被()包围。假设正则表达式后面还有被()包围的内容,那么可以依次用group(2)、group(3)等获取。

(2).通用匹配

上面写的正则表达式其实比较复杂,只要出现空白字符就需要写\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())

这里我们使用.*来代替中间部分,并在最后一个结尾加一个结尾字符串。运行结果如下:

 (3).贪婪与非贪婪

使用通用匹配.*匹配到的内容有时候不是我们想要的内容。看下面的例子:

import re

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

这里我们依然想获取目标字符串中间的数字,所以正则表达式中间写的依然是(\d+)。而数字两侧由于内容比较杂乱,所以想省略来写,于是都写成.*。

运行结果如下:

 为什么这个只得到了7一个数字?

那么这里就得涉及到贪婪匹配与非贪婪匹配的问题。在贪婪匹配下,.*会匹配尽可能多的字符。正则表达式中.*后面是\d+,也就是至少一个数字,因为.*尽可能多的匹配,那么它把123456都匹配了,只给\d+留下一个可满足条件的数字7,因此最后得到的内容也就是7。

其实这里只要使用非贪婪匹配就好了,非贪婪的匹配的写法是.*?,比通用匹配多了一个?,那么它可以起到什么样的效果呢?我们再用实例来看一下:

import re

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

结果如下:

 此时变成功的获取到了1234567了。原因可想而知,贪婪匹配是匹配尽可能多的字符,非贪婪匹配就是匹配尽可能少的字符。当.*?匹配到Hello后面的空白字符时,再往后的字符就是数字了,而\d+恰好可以匹配,于是这里.*?就不在匹配了,而是交给\d+去匹配。

所以说,在做匹配的时候,字符串中间尽可能使用非贪婪匹配,也就是用.*?代替.*,以免出现匹配缺失的情况。

但是注意,如果匹配的结果是在字符串结尾,.*?有可能匹配不到任何结果了,因为它是尽可能匹配少的字符。例如:

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

结果如下:

 

可以观察到,.*?没有匹配任何结果,而.*则是尽量匹配多内容,成功得到匹配结果。

(4).修饰符

在正则表达式中,可以用一些可选标志修饰符来控制匹配的模式,修饰符被指定为一个可选的标志,我们可以用实例来看一下:

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

这样运行的话就会出现报错,也就是说正则表达式没有匹配到这个字符串,返回结果为None,而我们又调用了group方法,导致AttributeError。

那么,中间content是有一个换行符的,加了一个换行符就匹配不到了,因为这里匹配的内容是除换行符之外的任意字符,当遇到换行符的时候,.*?就不能匹配了,所以导致匹配失败。这里只需要加一个修饰符re.S,即可修正这个错误。

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

这个修饰符的作用就是匹配包括换行符在内的所有字符。此时的运行结果如下:

1234567

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


(5).转义匹配

我们知道正则表达式定义了许多匹配模式,如,用于匹配除换行符之外的任意字符。但如果目标字符串里面就包含.这个字符,那就要用到转义字符了。

例如:

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

当目标字符串中遇到用作正则匹配模式的特殊字符时,在此字符前面加反斜线\转义一下即可。

3.匹配方法之search

前面讲的是match方法,它是从字符串的开头开始匹配的,意味着一旦开头不匹配,整个匹配就失败。

所以match方法使用时得考虑字符串开头的内容,因此在匹配的时候并不是很方便。

而search方法,它在匹配的时候会扫描整个字符串,然后会返回第一个匹配成功的结果。在匹配时,search方法会依次以每个字符串作为开头扫描字符串,直到找到第一个符合规则的字符串,然后返回匹配结果。扫描完没有找到符合规则的字符串,就返回None。

看下面的例子:

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

运行结果如下:

这样就可以得到匹配结果了 。

学习到了这种方法的话,咱们来看几个实例。

首先,准备一段待匹配的HTML文本,接下来写几个正则表达式实例实现相应信息的提取。

html='''
<div id="basketball">
    <h2 class="title">篮球</h2>
    <p class="instuction">篮球明星列表</p>
    <ul>
        <li class="active">
            <a href="1.mp3">詹姆斯</a>
        </li>
        <li data-view="13">
            <a href="2.mp3">哈登</a>
        </li>
        <li data-view="35">杜兰特</li>
        <li class="active">库里</li>
    </ul>
</div>
</body>
</html>
'''

1.提取class为active的文本和里面a标签的href值

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

运行结果如下:

2.提取含有data-view的文本

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

运行结果如下:

 

这里使用search方法会返回第一个符合条件的匹配目标

3.不使用re.S看看结果

 运行结果如下:

 因为上面一个有data-view属性里面包含换行符,所以用.*?匹配不到,只有加上re.S才能对换行符进行匹配。

 4.匹配方法之findall

 介绍完了search的用法后,它可以返回与正则表达式相匹配的第一个字符串。如果想要获取与正则表达式相匹配的所有字符串,该如何处理?这里就要借用findall。

还是用HTML文本,如果想要获取里面所有的a节点的href值和文本值。可以将search方法换成findall方法。返回结果是列表类型,需要通过遍历来依次获取每组内容。代码如下:

html='''
<div id="basketball">
    <h2 class="title">篮球</h2>
    <p class="instuction">篮球明星列表</p>
    <ul>
        <li class="active">
            <a href="1.mp3">詹姆斯</a>
        </li>
        <li data-view="13">
            <a href="2.mp3">哈登</a>
        </li>
        <li data-view="35">杜兰特</li>
        <li class="active">库里</li>
    </ul>
</div>
</body>
</html>
'''

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

运行结果如下:

 可以看到在每个列表中的每个元素都是元组类型,所以我们可以用索引值来取个条目。

总结一下,如果只想匹配到第一个字符,可以用search方法;如果需要提取多个内容,可以使用findall。

5.修改方法之sub

除了使用正则表达式来提取信息,有时候还需要借助它来修改文本。例如:

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

那么这里使用re方法来消除数字,这里往sub方法里面加入的第一个参数是匹配所有的数字,第二参数传入的是把数字都替换成空的字符串,第三个参数就是原字符。

结果如下:

 6.编译方法之compile

 前面所说的都是用来处理字符串的方法,最后再介绍一下compile方法,这个方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。实例代码如下:

import re

content1 = '2022-09-15 23:00'
content2 = '2022-09-12 12:00'
pattern = re.compile('\d{2}:\d{2}')
result1 = re.sub(pattern,'',content1)
result2 = re.sub(pattern,'',content2)
print(result1,result2)

运行结果如下:

 

那么使用compile就是处理一下对象,将匹配到的对象用pattern来接收,再使用sub来进行修饰。

7.总结

到此为止,正则表达式的基本用法都介绍完了,希望大家能仔细看完并且能深刻掌握理解,细节的部分到后面我会通过具体的实例来巩固这些方法。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值