Python爬虫正则表达式详解:re模块方法与爬虫应用实战指南
本篇文章主要给大家分享爬虫里的正则表达式。在 Python 基础阶段,就会简单地涉及到正则表达式。但在那个时候,不会将其与爬虫内容结合起来进行讲解。

本篇主要介绍内容如下:
正则表达式与re模块
re模块方法介绍
re模块在爬虫中的应用
正则表达式与re模块
什么是正则表达式?
正则表达式又称规则表达式,通常用于检索和替换符合特定模式的文本。它首先设定了一些特殊的字符及字符组合,然后通过组合这些“规则字符串”来对字符串进行过滤,以获取或匹配我们想要的特定内容。
它使用起来较为灵活,逻辑性较强且功能性也较强,能够迅速借助表达式从字符串里找到所需信息,不过对于初次接触的人而言,是比较晦涩且难以理解的。
正则表达式并非仅存在于 Python 中,其他语言也具备正则表达式。像 Java、JavaScript 等语言就有。倘若你具备其他语言的正则表达式基础,那么学习本节内容就会轻松许多。
正则表达式进行检索的步骤:
re模块
Python 借助 re 模块来提供对正则表达式的支持。在使用正则表达式之前,需要将该库进行导入。
import re
在我们正式介绍re模块的方法之前,先了解一下匹配规则:
规则较多是吗?不过只要记住篮框标注的那些就可以啦,因为这些是最常用的。大家多使用几次,就会渐渐变得熟练起来。
特别提示:在使用正则表达式时,要对特殊字符进行转义。因此,如果我们想要使用原始字符串,就需要在字符串前面加上一个 r 前缀。
re模块方法介绍
re模块的常用方法
使用 re 模块中的 compile()函数,将正则表达式以字符串形式进行编译,从而得到一个 Pattern 对象。
使用 Pattern 所提供的一系列方法,能够对文本进行匹配查找操作,最终会获得一个 Match 对象。
最后使用Match对象提供的属性和方法获得信息
首先认识一下compile 函数,它的一般使用形式如下:
import re
# 将正则表达式编译成 Pattern 对象
pattern = re.compile(r'\w{3}')
print(pattern)
Pattern 对象的一些常用方法主要有:
match方法:
match 方法从字符串的 pos 下标处开始进行匹配 pattern 。如果在 pattern 结束时已经完成匹配,就会返回一个 Match 对象。
如果在匹配过程中,pattern 不能够进行匹配,又或者在匹配还没有结束的时候就已经到达了 endpos,那么就会返回 None。此方法的原型如下:
match 函数可以使用 string 作为参数,也可以指定 pos 和 endpos 来确定匹配的范围;re.match 函数则需要 pattern 作为匹配模式,同时可以使用 string 作为待匹配的字符串,还可以通过 flags 来设置一些匹配的标志。
参数 string 指的是字符串;pos 代表着下标,pos 的默认值是 0,endpos 的默认值是 len(string);参数 flags 是在编译 pattern 时用来指定匹配模式的。
其三,re.S(re.DOTALL),表示支持点任意匹配模式。
import re
# 1. 得到pattern
pattern = re.compile('abc')
使用公式对象来匹配需要校验的字符串,进行 match 匹配操作,然后会返回一个匹配对象 match 对象。
match_obj = pattern.match('abcdef')
print(match_obj)
运行结果:
# 表示有匹配对象
import re
# 1. 得到pattern
pattern = re.compile('abc')
# 2. 使用公式对象匹配要校验的字符串 match 匹配,返回一个匹配对象match对象
match_obj = pattern.match('helloabc')
print(match_obj)
运行结果是:None
此时 helloabc 中实际上是有 abc 这个内容的,然而为何 Match 对象却是 None 呢?这是因为 Match 在进行匹配判断时,都是从字符串的开头开始进行判断的。如果一开始没有匹配成功,就会返回 None 。但是倘若代码改成下面这样的形式,就会返回一个 Match 对象。
import re
# 1. 得到pattern
pattern = re.compile('abc')
# 2. 使用公式对象匹配要校验的字符串 match 匹配,返回一个匹配对象match对象
match_obj = pattern.match('helloabc',5)
# 3. 打印对象
print(match_obj)
上面是为了方便大家理解正则的检索过程。然而,在实际使用中,我们能够直接通过 re.match(pattern,字符串)来进行检索。
比如(各位觉得结果会是什么呢?)
import re
r = re.match('abc', 'helabclo')
print(r)
结果为 None ,原因是依旧从开头开始进行比较。然而实际上,我们要检索的内容很有可能处于字符串的中间或者后面,并非每次都在前面。在这种情况下,我们就需要使用 search 方法了。
search 方法:
使用 search 意味着从任意位置开始进行查找,且仅进行一次匹配。需要注意的是,一旦找到匹配项,就不会继续查找后面可能存在的其他匹配项了。
它的一般使用形式如下:
搜索字符串(可以指定起始位置[pos]和结束位置[endpos])
其中,待匹配的字符串是 string ,pos 和 endpos 为可选参数,用于指定字符串的起始位置和终点位置,其默认值分别是 0 和字符串的长度 len 。
匹配成功时,就会返回一个 Match 对象;若没有匹配上,就会返回 None。
import re
r = re.search('abc', 'helabcloabc')
print(r)
此时返回的结果:
Match对象有几个常用的方法:
group()的作用是获取一个或多个分组匹配的字符串。如果想要获取整个匹配的子串,既可以直接使用 group(),也可以使用 group(0)。
span(): 返回匹配字符串的起始位置
start():用于获取分组匹配的子串在整个字符串中的起始位置,也就是子串第一个字符的索引。参数的默认值为 0。
end():用于获取分组匹配的子串在整个字符串中的结束位置。这个结束位置指的是子串最后一个字符的索引加 1。参数默认值为 0。
import re
r = re.search('abc', 'helabcloabc')
if r:
print(r.group())
print(r.span())
print(r.start())
print(r.end())
结果是:
abc
(3, 6)
如果我们的正则定义变得复杂一些,并且使用上面的匹配规则,那么使用方式也是这样吗?
import re
match = re.search(r'([a-z]+) ([a-z]+)', ) # 注意此时是区分大小写的
if match:
print(match.group(0))
print(match.group(1)) # 获取第一个分组的字符串
print(match.group(2)) # 获取第二个分组的字符串
print(match.groups())
结果是:
itty hellobaby
itty
hellobaby
('itty', 'hellobaby')
上面的正则表达式表明有两组由多个 a-z 之间的任意字符构成的字符串,且两组间有空格。match.groups()所表达的意思为(m.group(1), m.group(2), …),由于上述代码仅为两组,所以只能获取到 match.group(2),但若获取 match.group(3)则会出现报错。
可是问题再次出现,当 search 找到符合要求的字符串后就不会继续进行查找了,然而实际上后面依然存在符合正则的情况。
比如:hello world
如果想要获取所有的内容,那么可以考虑使用 findall()。通过对英语进行分析,人们能够知道它的意思。
findall方法
findall 方法的使用形式如下:
查找字符串中的所有匹配项,可指定起始位置和结束位置。可以通过指定起始位置和结束位置来限制查找范围。在指定的字符串中查找所有符合条件的内容。可以根据需要设置起始位置和结束位置来进行特定范围的查找。在给定的字符串中找出所有满足条件的部分。能够在指定的字符串范围内查找所有匹配的内容。
其中,待匹配的字符串是 string ,pos 和 endpos 是可选参数,用于指定字符串的起始和终点位置,其默认值分别为 0 和字符串的长度 len 。findall 会以列表形式返回全部能匹配的子串,若没有匹配,则返回一个空列表。例如,通过上面的代码使用 findall() 来查看获取的结果是什么?
import re
match_list = re.findall(r'([a-z]+) ([a-z]+)', 'hello Kitty hellobaby hello world') # 注意此时是区分大小写的
if match_list:
print(match_list)
结果:
["itty", "hellobaby"] 且 ["hello", "world"]
我们需要写一个正则表达式,这个正则表达式要能够检索出所有这样的字符串:字符串的开头和结尾是字母,而中间全部是数字。
import re
s = 'h88ex890loK123Jldkl90gd3o'
m = re.findall('[a-z][0-9]*[a-z]', s, re.I)
print(m)
结果:
如果要判断一个字符串是否以数字开头呢?我们可以使用 match 吗?可以使用 search 吗?可以使用 findall 吗?
import re
因为要注意是数字开头,所以我们主要进行开头的判断,因此我们使用 match 。
match = re.match(r'\d+.*','123admin') #\(d\)在匹配规则方面表示数字,“+”表示次数为大于等于 1,“.”表示任意字符,“*”表示长度是大于等于 0。
if match:
print('是数字开头的')
else:
print('不是数字开头的')
结果打印:是数字开头的,字符串换成:admin呢?
下面代码的结果会是什么呢?
验证用户名需包含字母、数字和下划线。其首字母不能是数字,并且长度必须在 6 位以上。
username = 'admin123'
m = re.match('[a-zA-Z_]\w{5,}$', username)
print(m.group())
finditer 方法
finditer 方法的行为与 findall 类似,它会搜索整个字符串并获取所有匹配的结果。不过,它返回的是一个迭代器,能够顺序访问每一个匹配结果(Match 对象)。大家可以把上面的代码修改为 finditer 来观察结果,这里就不再展示代码了。
split 方法
split 方法会依据能够匹配的子串来对字符串进行分割,分割之后会返回一个列表。它的使用形式具体如下:
split(string[, maxsplit])
其中,maxsplit 被用来指定最大的分割次数。如果不指定,就会将其全部进行分割。它与字符串的分隔情况类似,然而,它在此方面更加灵活。
比如:
import re
s = "hello Kitty 是一个广为人知的形象。hello baby 也是一个常用的表达。hello world 是编程中常用的入门语句。hello8hello 则是包含数字 8 的一种组合。"
m = re.split(r'[\s\d]+', s) 遇到空白字符\s或者数字\d时都会进行切割,多个空格也能被切割,并非仅仅一个空格或数字能被切割。
print(m)
此时得到的结果是:
["hello","Kitty","hellobaby","hello","world","hello","hello"] 包含多个 "hello" 相关的元素,其中有 "Kitty" 以及 "hellobaby" 等。
sub方法
sub 方法用于替换。它的使用形式如下:
sub(repl, string[, count])
repl 可以是字符串也可以是一个函数:
如果 repl 为字符串,就会用 repl 来替换字符串中每一个匹配的子串,然后返回替换后的字符串。此外,repl 能够以 id 的形式来引用分组,不过不能使用编号 0 。
如果 repl 为函数,此方法应仅接受一个参数,该参数为 Match 对象,并且要返回一个用于替换的字符串,返回的字符串中不能再引用分组。
count 用于指定最多替换次数,不指定时全部替换
import re
# 比如替换敏感词汇
s = '小明喜欢苍老师'
m = re.sub(r'(苍井空|苍老师)', '***', s) # 括号里面的表示一组,可以是这一组中的任何一个。
print(m)
# 将里面的分数都替换成100分
msg = 'python=99,c=98,html=90'
m = re.sub(r'\d+', '100', msg)
print(m)
当然也可以使用函数,比如分数都加1分
import re
def add(temp):
print(temp) # 此处打印便于查看
score = temp.group() # 获取匹配的内容
score = int(score) + 1
return str(score)
m = re.sub(r'\d+', add, msg)
print(m)
在使用过程中会涉及分组、贪婪和非贪婪模式,这里暂不展开,若有时间会给大家专门写一篇文章。
re模块在爬虫中的应用
接下来给大家分享一些常用的关于正则表达式抓取网络数据的技巧。
抓取标签间的内容
我们之前的几篇文章向大家分享了 urllib 模块和 requests 模块,这两个模块是用于获取网络资源的。我们获取的网络资源,除了 json 之外,都是与 HTML 标签相关的。我们通常需要做的就是获取这些标签的内容。
比如我们获取一下百度的title标题:
import re
import requests
url = "http://www.baidu.com/"
response = requests.get(url)
response.encoding='utf-8'
content = response.text
# 此处使用findall结合正则表达式完成
title = re.findall(r'(.*?) ', content)
print(title[0])
抓取超链接标签间的内容
import re
import requests
url = "http://www.baidu.com/"
response = requests.get(url)
response.encoding='utf-8'
content = response.text
# 定义正则表达式获取所有网页的超链接
res = r""
在 content 中,通过 re.findall 方法找到了 urls,这些 urls 符合 res 的匹配规则。
for u in urls:
print(u)
当然,若要获取超链接中的内容,我们是可以使用正则表达式的。只是,使用了分组的内容为()。
import re
import requests
url = "http://www.baidu.com/"
response = requests.get(url)
response.encoding='utf-8'
content = response.text
#获取超链接和之间内容
res = r'(.*?)'
使用 re.findall 函数在 content 中按照 re.S|re.M 的模式查找 res ,查找的结果存储在 texts 中
for t in texts:
print(t)
观察结果:
抓取标签中的参数
HTML 超链接有其基本格式为“链接内容”。现在要获取其中的 URL 链接地址,获取的方法如下。
import re
import requests
url = "http://www.baidu.com/"
response = requests.get(url)
response.encoding='utf-8'
content = response.text
# 定义正则表达式获取所有网页的超链接
res = r""
urls = re.findall(res, content)
# 将所有的超级链接拼接成字符串
all_urls = '\n'.join(urls)
# 定义正则表达式
res = r"(?<=href=)http:.+?(?=\>)|(?<=href=)http:.+?(?=\s)"
# 查找符合规则的超级链接
通过使用 re.findall 函数,在 content 中按照 re.I|re.S|re.M 的模式去查找 res,从而得到 urls 。
for url in urls:
print(url)
抓取图片超链接标签的URL
HTML 插入图片使用标签的基本格式是“”。要插入图片,需要获取图片的 URL 链接地址。下面的案例不仅获取了图片链接,还将图片保存到了本地。
import re
import requests
# 从网络获取一张图片的html标签
image = ''
# 使用正则表达式获取src后面的内容
m = re.match(r', image)
print(m.group(1))
image_path = m.group(1)
如果想要下载获取的图片链接,我们可以将 requests 与文件保存结合起来完成。
通过 requests 获取了 image_path 对应的内容并赋值给 response 。
# 获取响应信息的内容
result = response.content
# 获取图片名称
filename 等于 image_path 在 image_path 中从后往前找到最后一个斜杠(包括斜杠)之后的部分。'%')+1:]
path = os.path.join(r'images', filename)
# 保存到本地将图片
with open(path, 'wb') as wstream:
wstream.write(result)
print('文件下载结束!')
系列文章