【20天搞定Python爬虫】第五天:正则表达式真的很6,可惜你不会写
千锋python
2021年04月29日 17:05
收录于文集
共23篇

【千锋教育干货暴击】

如果你想更好的学习python乃至转行,弯道超车,快人一步!本课程零基础即可加入学习,抓住大数据、机器学习、人工智能时代的红利,开启你的第一行代码吧!

↓    ↓    ↓

千锋Python全套视频教程(700集)​

千锋教育Python教程全套_python零基础入门到精通(学完可达到Python工程师水平)​

cut-off

本篇文章主要给大家分享爬虫中的正则表达式,其实正则表达式在Python基础的时候就会简单的涉及到,但是在那里不会与爬虫内容结合起来讲解。本篇主要介绍内容如下:

  1. 正则表达式与re模块

  2. re模块方法介绍

  3. re模块在爬虫中的应用

正则表达式与re模块

什么是正则表达式? 正则表达式(Regular Expression,简称Regex或RE)又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本,首先设定好了一些特殊的字及字符组合,通过组合的“规则字符串”来对字符串进行过滤,从而获取或匹配我们想要的特定内容。

它使用起来比较灵活、逻辑性和功能性比较强,能迅速地通过表达式从字符串中找到所需信息,但对于刚接触的人来说,比较晦涩难懂。

正则表达式不仅仅在Python中有,其他语言也是有正则表达式的,比如Java、JavaScript等等。如果你有其他语言正则表达式基础,学习本节内容轻松很多。

正则表达式进行检索的步骤:

re模块 Python通过re模块提供了对正则表达式的支持,使用正则表达式之前需要导入该库。

代码块
JavaScript
自动换行
复制代码
import re
复制成功

在我们正式介绍re模块的方法之前,先了解一下匹配规则:

规则比较多是吧?但是记住标注的那些就可以了,因为这些最常用了。大家多用几次就会慢慢熟练了。

特别提示:正则表达式使用时,对特殊字符进行转义,所以如果我们要使用原始字符串,需在字符串前面加一个r 前缀。

re模块方法介绍

re模块的常用方法

  1. 使用 re模块下的compile() 函数将正则表达式的字符串形式编译为一个 Pattern 对象。

  2. 通过 Pattern 提供的一系列方法可以对文本进行匹配查找,最后得到一个Match对象

  3. 最后使用 Match 对象提供的属性和方法获得信息

首先认识一下compile 函数,它的一般使用形式如下:

代码块
JavaScript
自动换行
复制代码
import re
# 将正则表达式编译成 Pattern 对象
pattern = re.compile(r'\w{3}')
print(pattern)
复制成功

Pattern 对象的一些常用方法主要有:

  • match 方法:从起始位置开始查找,一次匹配

  • search 方法:从任何位置开始查找,一次匹配

  • findall 方法:全部匹配,返回列表

  • finditer 方法:全部匹配,返回迭代器

  • split 方法:分割字符串,返回列表

  • sub 方法:替换

  1. match方法

match方法是从字符串的pos下标处起开始匹配pattern,如果pattern结束时已经匹配,则返回一个Match对象;如果匹配过程中pattern无法匹配,或者匹配未结束就已到达endpos,则返回None。该方法原型如下:

代码块
JavaScript
自动换行
复制代码
match(string[, pos[, endpos]]) 或者 re.match(pattern, string[, flags])
复制成功

参数string表示字符串;pos表示下标,pos和endpos的默认值分别为0和len(string);参数flags用于编译pattern时指定匹配模式。

三个常见的匹配模式:(1) re.I(re.IGNORECASE):忽略大小写(括号内是完整写法) (2) re.M(re.MULTILINE):允许多行模式 (3) re.S(re.DOTALL):支持点任意匹配模式

代码块
JavaScript
自动换行
复制代码
import re
# 1. 得到pattern
pattern = re.compile('abc')
# 2. 使用公式对象匹配要校验的字符串  match 匹配,返回一个匹配对象match对象
match_obj = pattern.match('abcdef')
print(match_obj)
复制成功

运行结果:

代码块
JavaScript
自动换行
复制代码
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对象

代码块
JavaScript
自动换行
复制代码
import re
# 1. 得到pattern
pattern = re.compile('abc')
# 2. 使用公式对象匹配要校验的字符串  match 匹配,返回一个匹配对象match对象
match_obj = pattern.match('helloabc',5)
# 3. 打印对象
print(match_obj)
复制成功

上面👆只是方便大家理解正则的检索过程,但是实际使用中我们可以直接通过re.match(pattern,字符串)进行检索。

比如(各位觉得结果会是什么呢?)

代码块
JavaScript
自动换行
复制代码
import re
r = re.match('abc', 'helabclo')
print(r)
复制成功

结果是None,因为还是从头开始比较的,但是事实上我们要检索的内容很有可能在字符串的中间或者后面,不可能每次都在前面。此时我们就要使用search方法了。

  1. search 方法

使用search表示从任何位置开始查找,一次匹配,注意是一次匹配,如果后面还有匹配的也不会查找了。

它的一般使用形式如下:

代码块
JavaScript
自动换行
复制代码
search(string[, pos[, endpos]])
复制成功

其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。

当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。

代码块
JavaScript
自动换行
复制代码
import re
r = re.search('abc', 'helabcloabc')
print(r)
复制成功

此时返回的结果:

Match对象有几个常用的方法:

代码块
JavaScript
自动换行
复制代码
group(): 用于获得一个或多个分组匹配的字符串,当要获得整个匹配的子串时,可直接使用 group() 或 group(0)
span(): 返回匹配字符串的起始位置
start():用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0;
end():用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0
复制成功

代码块
JavaScript
自动换行
复制代码
import re
r = re.search('abc', 'helabcloabc')
if r:
    print(r.group())
    print(r.span())
    print(r.start())
    print(r.end())
复制成功

结果是:

代码块
JavaScript
自动换行
复制代码
abc
(3, 6)
3
6
复制成功

如果我们的正则定义复杂一些使用上面的匹配规则,使用方式也是这样吗?

代码块
JavaScript
自动换行
复制代码
import re
match = re.search(r'([a-z]+) ([a-z]+)', 'hello Kitty hellobaby hello world')  # 注意此时是区分大小写的
if  match: 
 print(match.group(0))
 print(match.group(1)) # 获取第一个分组的字符串
 print(match.group(2)) # 获取第二个分组的字符串
 print(match.groups()) 
复制成功

结果是:

代码块
JavaScript
自动换行
复制代码
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(),通过英语分析都能知道它是什么意思。

  1. findall方法

findall 方法的使用形式如下:

代码块
JavaScript
自动换行
复制代码
findall(string[, pos[, endpos]])
复制成功

其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。findall 以列表形式返回 ,是全部能匹配的子串,如果没有匹配,则返回一个空列表。比如上面的代码使用findall()看看获取的结果是什么?

代码块
JavaScript
自动换行
复制代码
import re
match_list = re.findall(r'([a-z]+) ([a-z]+)', 'hello Kitty hellobaby hello world')  # 注意此时是区分大小写的
if match_list: 
 print(match_list)
复制成功

结果:

代码块
JavaScript
自动换行
复制代码
[('itty', 'hellobaby'), ('hello', 'world')]
复制成功

比如我们要求写一个正则,用于检索字符串所有两头是字母,中间全部是数字的字符串。

代码块
JavaScript
自动换行
复制代码
import re
s = 'h88ex890loK123Jldkl90gd3o'

m = re.findall('[a-z][0-9]*[a-z]', s, re.I)

print(m)
复制成功

结果:

代码块
JavaScript
自动换行
复制代码
['h88e', 'x890l', 'oK', 'Jl', 'dk', 'l90g', 'd3o']
复制成功

如果是判断一个字符串是否是数字开头呢?我们使用match、search、findall?

代码块
JavaScript
自动换行
复制代码
import re
# 注意是数字开头,所以我们主要是判断开头,所以我们使用match
match = re.match(r'\d+.*','123admin')  # \d 在匹配规则上表示数字,+表示的是次数大于等于1,.表示任意字符,*表示长度是大于等于0
if match:
  print('是数字开头的')
else:
    print('不是数字开头的')
复制成功

结果打印:是数字开头的,字符串换成:admin呢?

下面👇代码的结果会是什么呢?

代码块
JavaScript
自动换行
复制代码
# 验证用户名 字母数字下划线  首字母不能是数字  长度必须6位以上
username = 'admin123'
m = re.match('[a-zA-Z_]\w{5,}$', username)
print(m.group())
复制成功

4.finditer 方法

finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。但它返回一个顺序访问每一个匹配结果(Match 对象)的迭代器。大家可以将上面的代码改成finditer观察结果,此处不再展示代码。

5.split 方法

split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:

代码块
JavaScript
自动换行
复制代码
split(string[, maxsplit])
复制成功

其中,maxsplit 用于指定最大分割次数,不指定将全部分割。跟字符串的分隔类似,但是这个更加灵活。

比如:

代码块
JavaScript
自动换行
复制代码
import re
s = 'hello Kitty    hellobaby hello world hello8hello'

m = re.split(r'[\s\d]+', s) # 表示遇到空白字符\s或者数字\d,都会切割,如果有多个空格也可以切割不仅是一个空格或者数字

print(m)
复制成功

此时得到的结果是:

代码块
JavaScript
自动换行
复制代码
['hello', 'Kitty', 'hellobaby', 'hello', 'world', 'hello', 'hello']
复制成功

6.sub方法

sub 方法用于替换。它的使用形式如下:

sub(repl, string[, count])

repl 可以是字符串也可以是一个函数:

  1. 如果 repl 是字符串,则会使用 repl 去替换字符串每一个匹配的子串,并返回替换后的字符串,另外,repl 还可以使用 id 的形式来引用分组,但不能使用编号 0;

  2. 如果 repl 是函数,这个方法应当只接受一个参数(Match 对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。

count 用于指定最多替换次数,不指定时全部替换

代码块
JavaScript
自动换行
复制代码
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分

代码块
JavaScript
自动换行
复制代码
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模块在爬虫中的应用

接下来给大家分享一下常用的正则表达式抓取网络数据的一些技巧。

  1. 抓取标签间的内容

我们前几篇文章给大家分享了urllib模块和requests模块是用来获取网络资源的两个模块,而我们获取的网络资源出了json的之外,都是跟HTML标签打交道。我们往往要做的就是获取标签的内容。

比如我们获取一下百度的title标题:

代码块
JavaScript
自动换行
复制代码
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'<title>(.*?)</title>', content)
print(title[0])
复制成功

2.抓取超链接标签间的内容

代码块
JavaScript
自动换行
复制代码
import re  
import requests  
url = "http://www.baidu.com/"  
response = requests.get(url)
response.encoding='utf-8'
content = response.text
# 定义正则表达式获取所有网页的超链接
res = r"<a.*?href=.*?<\/a>"
urls = re.findall(res, content)
for u in urls:
    print(u)
复制成功

当然如果想获取超链接中的内容我们也可以使用正则表达式,只不过使用了分组的内容就是()

代码块
JavaScript
自动换行
复制代码
import re  
import requests  
url = "http://www.baidu.com/"  
response = requests.get(url)
response.encoding='utf-8'
content = response.text

 
#获取超链接<a>和</a>之间内容
res = r'<a .*?>(.*?)</a>'  
texts = re.findall(res, content, re.S|re.M)  
for t in texts:
    print(t)
复制成功

观察结果:

3.抓取标签中的参数

HTML超链接的基本格式为“<a href=URL>链接内容</a>”,现在需要获取其中的URL链接地址,方法如下:

代码块
JavaScript
自动换行
复制代码
import re  
import requests  
url = "http://www.baidu.com/"  
response = requests.get(url)
response.encoding='utf-8'
content = response.text
# 定义正则表达式获取所有网页的超链接
res = r"<a.*?href=.*?<\/a>"
urls = re.findall(res, content)
# 将所有的超级链接拼接成字符串
all_urls = '\n'.join(urls)
# 定义正则表达式
res = r"(?<=href=)http:.+?(?=\>)|(?<=href=)http:.+?(?=\s)"
# 查找符合规则的超级链接
urls = re.findall(res, content, re.I|re.S|re.M)
for url in urls:
    print(url) 
复制成功

4、抓取图片超链接标签的URL

HTML插入图片使用标签的基本格式为“”,则需要获取图片URL链接地址,下面👇案例不仅获取的图片链接而且将图片保存到了本地。

代码块
JavaScript
自动换行
复制代码
import re
import requests

# 从网络获取一张图片的html标签
image = ''
# 使用正则表达式获取src后面的内容
m = re.match(r'
复制成功

cut-off

需要资料也可以关注微信公众号:Python专栏,事不宜迟,一起进步吧!