我们已经知道在上一条requests库来获取网页的源代码,得到HTML代码。但我们想要的数据是包含在HTML代码之中的,那么要怎么才能从HTML代码中获取想要的信息呢?正则表达式就是其中一个有效的方法。
1.实例引入
下面用几个实例先来看一下它的用法
打开开源中国提供的正则表达式测试工具:,输入待匹配的文本,然后选择常用的正则表达式,就可以得出相应的匹配结果了。
例如,这里输入如下待匹配文本:
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.总结
到此为止,正则表达式的基本用法都介绍完了,希望大家能仔细看完并且能深刻掌握理解,细节的部分到后面我会通过具体的实例来巩固这些方法。