爬虫学习——(二)正则表达式

目录

一、常用匹配规则介绍

 二、常用匹配方法

1.match

2.search

3.findall

4.sub

5.compile

三、基础爬虫案例实战


一、常用匹配规则介绍

正则表达式常用匹配规则(参考学习网址):正则表达式 – 语法 | 菜鸟教程

 二、常用匹配方法

1.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)
#result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}.*Demo$',content)  #匹配所有字符
print(result)
print(result.group())  #返回匹配结果
print(result.span())   #输出范围

运行结果:

41
<re.Match object; span=(0, 25), match='Hello 123 4567 World_This'>
Hello 123 4567 World_This
(0, 25)
  • 匹配目标

要想在一段文字中提取一部分内容,可以使用括号()将想要提取的子字符串括起来。()实际上标记了一个子表达式的开头和结束的位置,被标记的每个子表达式依次对应每个分组,调用group方法传入分组的索引就可以获取提取结果,实例如下:

import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld',content)
print(result) 
print(result.group())  #输出完整的匹配结果
print(result.group(1)) #输出第一个被()包围的匹配结果
print(result.span())

运行结果:

<re.Match object; span=(0, 19), match='Hello 1234567 World'>
Hello 1234567 World
1234567
(0, 19)
  • 通用匹配

由于上面的正则表达式中比较复杂,只要出现空包字符都要\s匹配,出现数字就要\d匹配,在需要匹配内容较多时,这样的工作量较大。其实可以使用万能匹配".*",其中“.”可以匹配任意符,"*"代表匹配前面字符的无限次,两者组合就可以匹配任意字符了。接着上面例子,利用".*"改写一下正则表达式:

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

运行结果:

<re.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) 
print(result.group(1))

运行结果:

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

可见,在贪婪匹配下,得到的只有数字7。因为在贪婪匹配下,".* "会匹配尽可能多的字符,而且".* "后面是\d+,也就是至少一个数字,并没有指定具体几个数字,所以,".* "就尽可能匹配多个数字将123456都匹配了,只留下满足条件的一个数字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))

运行结果:

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

可见,在非贪婪匹配下,可以的到1234567整串数字。与贪婪匹配相反,非贪婪匹配则是匹配尽可能少的字符,在实际应用中,应该尽可能使用非贪婪匹配,以免出现匹配结果缺失的问题。

但是,如果匹配的是字符串结尾的内容,则使用".* "贪婪匹配会更好,因为它会匹配尽可能多的内容,将结尾内容匹配出来。而".*? "非贪婪匹配则匹配尽可能少的内容,具体实例如下:

import re
content = 'Hello 1234567 World_This is a Regex Demo'
result1 = re.match('^He.*?a\s(.*?)',content)
result2 = re.match('^He.*?a\s(.*)',content)
print('result1(非贪婪匹配)',result1.group(1))
print('result2(贪婪匹配)',result2.group(1))

运行结果:

result1(非贪婪匹配) 
result2(贪婪匹配) Regex Demo
  • 修饰符

re.I:使匹配对大小写不敏感

re.L:实现本地化识别(local-aware)匹配

re.M:多行匹配,影响^和$

re.S:使匹配内容包括换行符在内的所有字符

re.U:根据Unicode字符集解析字符。这个标志会影响\w、\W、\b和\B

re.X:该标志能够给予你更灵活的格式,以便将正则表达式书写得更易于理解

在遇到字符串中有换行符的情况,如果跟上面一样匹配,则会出现报错的情况:

import re
content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo$',content)
print(result.group(1))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_448/501554157.py in <module>
      4 '''
      5 result = re.match('^He.*?(\d+).*?Demo$',content)
----> 6 print(result.group(1))

AttributeError: 'NoneType' object has no attribute 'group'

上面运行结果报错,也就是正则表达式没有匹配到这个字符串,返回结果为None,而又调用的group方法,所以导致AttributeError

因为.*?是匹配除换行符以外的任意字符,由于遇到换行符,则匹配失败。而只需加一个修饰符re.S,即可修正错误

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

运行结果:

1234567
  • 转义匹配

如果目标字符串包含除换行符以外的任意字符如.,则可在其前面加上反斜杠“\”即可实现匹配,实例如下:

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

运行结果:

(百度)www.baidu.com

2.search

在上述match方法中,由于该方法是从字符串开头开始匹配的,意味着一旦开头不匹配,则整个匹配就失败了。match方法在使用时需要考虑目标字符串开头的内容,因此在做匹配时并不方便

search方法在匹配时会扫描整个字符串,然后返回第一个匹配成功的结果。也就是说,正则表达式可以是字符串中的一部分,不需要想match方法哪有匹配字符串开头。在匹配时,search方法会依次以每个字符作为开头扫描字符串,直到找到第一个符合规则的字符串,然后返回匹配内容;如果扫描完还没有找到符合规则的字符串,就返回None。将上述代码的match方法改成search方法,如下:

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

运行结果:

<re.Match object; span=(0, 40), match='Hello 1234567 World_This\nis a Regex Demo'>
1234567

为匹配方便,建议尽量使用search方法,下面再用search写几个正则表达式实例来实现相应信息提取:

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.*?active.*?singer="(.*?)">(.*?)</a>',html,re.S)
if result:
    print(result.group(1),result.group(2))

运行结果:

齐秦 往事随风

从结果可以看到,这是class为active的li节点内部的超链接包含歌手和歌名。如果吧active去掉又会怎样呢,改写代码如下:

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

运行结果:

任贤齐 沧海一声笑

去掉active标签后,从字符串开头开始搜索,此时符合条件的节点变成第二个li节点,后面的不再匹配,所以运行结果改变。这两次的匹配都用了re.S,使得.*?可以匹配换行,下面去掉re.S,改写代码如下:

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

运行结果:

beyond 光辉岁月

将re.S去掉后,匹配结果变成第四个li节点的内容,因为第二个和第三个li节点都包含换行符,而第四个li节点不包含换行符,故成功匹配。

3.findall

上述search方法,它能够返回与正则表达式相匹配的第一个字符串。但是如果想要获取与正则表达式相匹配的所有字符串,可以使用findall方法

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>'''
results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>',html,re.S)
print(type(results),results)
for result in results:
    print(result)  #输出位元组
    print(result[0],result[1],result[2])  #输出字符串

运行结果:

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

由结果可知,将search方法换成findall方法返回结果是列表类型,需要通过遍历来依次获得每组内容。总的来说,如果只想获取匹配到的第一个字符串,可以用search方法,如果需要提取多个内容,则使用findall方法

4.sub

除了使用正则表达式提取信息,还可以借助它修改文本信息。例如想将一串文本你的数字去掉,用repalce方法未免太繁琐,此时可利用sub方法实现,第一个参数传入正则表达式匹配需要修改的信息,第二个参数传入替换成的内容,第三个参数是原字符串。实例入下:

import re
content = "121fef342dkjdsd87f7hiud3334"
content = re.sub('\d+','',content)
print(content)

运行结果:

fefdkjdsdfhiud

5.compile

compile可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。实例如下:

import re
content1 = '2022/7/23晴'
content2 = '2022/7/24小雨'
content3 = '2022/7/25雷阵雨'
pattern = re.compile('\d{4}/\d{1}/\d{2}')
result1 = re.sub(pattern,'',content1)
result2 = re.sub(pattern,'',content2)
result3 = re.sub(pattern,'',content3)
print(result1,result2,result3)

三、基础爬虫案例实战

爬取的网址为:豆瓣读书,爬取的内容为:超链接、书名、作者、出版日期

import re
import requests
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}
content = requests.get('https://book.douban.com/',headers = headers).text  
pattern = re.compile('<li.*?cover">.*?href="(.*?)".*?title="(.*?)".*?more-meta">.*?author">(.*?)</span>.*?year">(.*?)</span>.*?</li>',re.S)
results = re.findall(pattern,content)
for result in results:
    url,name,author,date=result
    author = re.sub('\s','',author)
    year = re.sub('\s','',year)
    print(url,name,author,date)

正则表达式爬取网页内容效率会比较低,后续将继续学习pyquery和beautifulsoup等库爬取会更方便。

猜你喜欢

转载自blog.csdn.net/weixin_52024937/article/details/125960094
今日推荐