爬虫入门(三)——正则表达式(用法+实操)

1.正则表达式的简介

在这里插入图片描述

1.1 概念

正则表达式是对字符串操作的⼀种逻辑公式,就是⽤事先定义好的⼀些特定字符、及这些特定字符的组合,组成⼀个“规则字符串”,这个“规则字符串”⽤来表达对字符串的⼀种过滤逻辑

1.2 正则表达式的应⽤场景
  • 表单验证(例如 : ⼿机号、邮箱、身份证… )
  • 爬⾍

2. 正则表达式对Python的⽀持

2.1 普通字符

字⺟、数字、汉字、下划线、以及没有特殊定义的符号,都是"普通字符"。正则表达式中的普通字符,在匹配的时候,只匹配与⾃身相同的⼀个字符。例如:表达式c,在匹配字符串abcde时,匹配结果是:成功;匹配到的内容是c;匹配到的位置开始于2,结束于3。(注:下标从0开始还是从1开始,因当前编程语⾔的不同⽽可能不同)

match()函数

  • match(pattern, string, flags=0)
  • 第⼀个参数是正则表达式,如果匹配成功,则返回⼀个match对象,否则返回⼀个None
  • 第⼆个参数表示要匹配的字符串
  • 第三个参数是标致位⽤于控制正则表达式的匹配⽅式 如: 是否区分⼤⼩写,多行匹配等等

具体用法如代码所示:

import re

pattern = 'pyth'

s = 'python and java'

result = re.match(pattern,s)

if result:

    print(result.group())
    
else:

    print('没有匹配到')
    
    # 得到的结果是pyth

在用match方法的时候有一个需要注意的地方,很重要,非常容易导致出错,老规矩,用代
码解释:

import re
content = '评论数:12'
result = re.match('\d', content)
print(result)
# 得到的结果是None,如果直接print(result.group())是会报错的。
# 原因在于match方法是从content第一个字符开始去匹配\d,如果未匹配到,直接就返回None。这里因为content第一个字符不是数字,所以直接返回None
2.2 元字符

正则表达式中使⽤了很多元字符,⽤来表示⼀些特殊的含义或功能
在这里插入图片描述
⼀些⽆法书写或者具有特殊功能的字符,采⽤在前⾯加斜杠""进⾏转义的⽅法。

例如下表所示:
在这里插入图片描述
尚未列出的还有问号?、星号*和括号等其他的符号。所有正则表达式中具有特
殊含义的字符在匹配⾃身的时候,都要使⽤斜杠进⾏转义。这些转义字符的匹
配⽤法与普通字符类似,也是匹配与之相同的⼀个字符

2.3 预定义匹配字符集

正则表达式中的⼀些表示⽅法,可以同时匹配某个预定义字符集中的任意⼀个
字符。⽐如,表达式\d可以匹配任意⼀个数字。虽然可以匹配其中任意字符,
但是只能是⼀个,不是多个
在这里插入图片描述
在这里插入图片描述

2.4 重复匹配

前⾯的表达式,⽆论是只能匹配⼀种字符的表达式,还是可以匹配多种字符其 中任意⼀个的表达式,都只能匹配⼀次。但是有时候我们需要对某个字段进⾏重复匹配,例如⼿机号码13666666666,⼀般的新⼿可能会写成 \d\d\d\d\d\d\d\d\d\d\d(注意,这不是⼀个恰当的表达式),不但写着费 劲,看着也累,还不⼀定准确恰当。
这种情况可以使⽤表达式再加上修饰匹配次数的特殊符号{},不但重复书写表达式就可以重复匹配,例如[abcd][abcd]可以写成[abcd]{2}

在这里插入图片描述
在这里插入图片描述

2.5 位置匹配和⾮贪婪匹配

位置匹配
有时候,我们对匹配出现的位置有要求,⽐如开头、结尾、单词之间等等
在这里插入图片描述
贪婪与非贪婪模式

简单来说,贪婪模式就是尽可能多地去匹配字符,非贪婪模式就是尽可能少地去匹配字符,python默认采取的是贪婪模式。例如,针对⽂本dxxxdxxxd,表达式(d)(\w+)(d)中的\w+将匹配第⼀个d和最后⼀ 个d之间的所有字符xxxdxxx。可⻅,\w+在匹配的时候,总是尽可能多的匹配 符合它规则的字符。同理,带有?、*和{m,n}的重复匹配表达式都是尽可能地多匹配。

先来看下非贪婪模式:

# 实现功能:提取文章发布时间,比较贪婪与非贪婪
import re
content = '发布于2018/12/23'
result = re.findall('.*?(\d.*\d)', content)
#这里的?表示的就是非贪婪模式,第一个.*会尽可能少地去匹配内容,因为后面跟的是\d,所以碰见第一个数字就终止了。
print(result)
# 得到的结果是['2018/12/23']

再来看下贪婪模式:

import re
content = '发布于2018/12/23'
result = re.findall('.*(\d.*\d)', content)
# 这里的第一个.*后面没有添加问号,表示的就是贪婪模式,第一个.*会尽可能多地去匹配内容,后面跟的是\d,碰见第一个数字并不一定会终止,当它匹配到2018的2的时候,发现剩下的内容依然满足(\d.*\d),所以会一直匹配下去,直到匹配到12后面的/的时候,发现剩下的23依然满足(\d.*\d),但是如果再匹配下去,匹配到23的2的话,剩下的3就不满足(\d.*\d)了,所以第一个.*就会停止匹配,(\d.*\d)最终匹配到的结果就只剩下23了。
print(result)
# 得到的结果是['23']

我们再来看下这段代码:

import re
content = '发布于2018/12/23'
result = re.findall('.*(\d.*?\d)', content)
print(result)
 # 得到的结果是['23'],原因在于第一个.*是贪婪模式,会一直匹配到12后面的/,这样结果就是['23']

再来看下这段代码:

import re
content = '发布于2018/12/23'
result = re.findall('.*?(\d.*?\d)', content)
# 这里的第一个.*?表示非贪婪模式,匹配到2018前面的'于'之后就停止了
# 括号里的.*?也是表示非贪婪模式,括号里的内容从2018的2开始匹配,因为后面一个数字是0,那么也就满足了(\d.*?\d),所以就直接返回结果了,同样的,接下来的18也是这样,一直匹配到23才结束。
print(result)
# 得到的结果是['20', '18', '12', '23']

贪婪与非贪婪在使用的要慎重,因为一不小心就容易出错,匹配到的结果并不是我们想要的。

校验数字的相关表达式:
在这里插入图片描述
特殊场景的表达式:
在这里插入图片描述

3. re模块常用方法

在这里插入图片描述
compile(pattern, flags=0)
这个⽅法是re模块的⼯⼚法,⽤于将字符串形式的正则表达式编译为Pattern模 式对象,可以实现更加效率的匹配。第⼆个参数flag是匹配模式 使⽤compile() 完成⼀次转换后,再次使⽤该匹配模式的时候就不能进⾏转换了。经过 compile()转换的正则表达式对象也能使⽤普通的re⽅法*

flag匹配模式
在这里插入图片描述
在这里插入图片描述
search(pattern, string, flags=0)
在⽂本内查找,返回第⼀个匹配到的字符串。它的返回值类型和使⽤⽅法与
match()是⼀样的,唯⼀的区别就是查找的位置不⽤固定在⽂本的开头

findall(pattern, string, flags=0)
作为re模块的三⼤搜索函数之⼀,findall()和match()、search()的不同之处在
于,前两者都是单值匹配,找到⼀个就忽略后⾯,直接返回不再查找了。⽽
findall是全⽂查找,它的返回值是⼀个匹配到的字符串的列表。这个列表没有
group()⽅法,没有start、end、span,更不是⼀个匹配对象,仅仅是个列表!
如果⼀项都没有匹配到那么返回⼀个空列表

split(pattern, string, maxsplit=0, flags=0)
re模块的split()⽅法和字符串的split()⽅法很相似,都是利⽤特定的字符去分割
字符串。但是re模块的split()可以使⽤正则表达式,因此更灵活,更强⼤
split有个参数maxsplit,⽤于指定分割的次数

sub(pattern, repl, string, count=0, flags=0)
sub()⽅法类似字符串的replace()⽅法,⽤指定的内容替换匹配到的字符,可以
指定替换次数

4. 分组功能

Python的re模块有⼀个分组功能。所谓的分组就是去已经匹配到的内容再筛选出需要的内容,相当于⼆次过滤。实现分组靠圆括号(),⽽获取分组的内容靠的是group()、groups(),其实前⾯我们已经展示过。re模块⾥的积个重要⽅法在 分组上,有不同的表现形式,需要区别对待

5.项目实操

5.1 正则表达式+requests实现原生爬虫

来使用正则表达式和requests实现原生的爬虫,不使用BeautifulSoup或者Xpath了。
我们爬取的目标网站是豆瓣电影Top250,获取的内容有电影名称、上映时间、上映地点、电影分类、电影评分。
接下来,我们将分步实现这个功能。

第一步,获取第一页的网页源码并进行预处理:

import requests

headers = {
  'User‐Agent': 'Mozilla/5.0 (Windows NT 10.0;WOW64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
 }

url = 'https://movie.douban.com/top250'
response = requests.get(url)
content = response.text.replace(' ', '').replace('\n', '')
# 这一行表示将网页源码中的空格和换行替换掉,不然有可能会影响到我们的正则匹配,因为网页源码中太多换行和空格了。

第二步,从预处理好的网页中提取我们需要的标签,为之后的内容提取做准备:

import re

# 第一步:提取出网页源码中class="grid_view"的div下的所有<li>标签,因为里面包含了所有的电影信息
all_tags = re.findall('.*grid_view.*?(<li>.*</li>)', content)[0]
# 这一行中第一个.*表示grid_view前可以是除换行符以外的任意字符,这样就定位到了class="grid_view"这个div标签
# 第二个.*?表示grid_view与<li>之间可以是除换行符意外的任意字符,同时采用非贪婪匹配,遇见第一个<li>就终止了,否则会一直匹配下去,导致我们最终得到的<li>标签只有最后一个,而不是所有的<li>标签。
# <li>.*</li>表示一个组,以<li>开头,以</li>结尾,中间采用贪婪模式匹配,尽可能多地匹配, 注意这里要用贪婪模式,否则用非贪婪的话,遇见第一个</li>就终止了,这样就只匹配到了第一个<li>标签

# 第二步:从所有的<li>标签中提取出每个<li>标签:
movie_tags = re.findall('<li>.*?</li>', all_tags)
# 这一行中.*?就表示非贪婪模式,尽可能少地匹配,这样才能获取到所有的<li>标签列表,否则获取到的还是原来的字符串。

第三步,从所有的

  • 标签中提取出具体的内容:
  • import re
    
    #第一步:提取出电影名称
    for movie_tag in movie_tags:
     movie_name = re.findall('.*?class="title">(.*?)<', movie_tag)[0]
     # 这里的.*?和之前的意思是一样的,帮助我们快速定位到class="title"标签
    # 中间的.*?表示非贪婪匹配,匹配>和后面遇到的第一个<之间的内容,电影名称就藏在里面
    
    # 第二步:提取出上映时间、上映地点、电影分类
    other = re.findall('.*?class="bd".*?<br>(.*?)</p>.*', movie_tag)[0]
    # 前面的.*?和之前一样,帮助我们快速定位到class=bd标签
    # 之后的.*?<br>是非贪婪模式,遇见第一个<br>就终止
    # <br>(.*?)</p>表示以非贪婪模式提取出<br>与</p>之间的内容,包含了上映时间、上映地点、电影分类。
    # other的内容是'1994 / 美国 / 犯罪剧情'
    # 从other中提取上映时间
    release_date = int(re.findall('\d+', other)[0])
    
    # 从other中提取上映地点
    release_place = re.findall('.*?/(.*?)/', other)[0].replace(' ', '')
    # 这一行的第一个.*?表示以非贪婪的模式匹配到第一个/,第二个.*?也是表示以非贪婪的模式匹配/到下一个/之间的内容
    
    # 从other中提取电影分类
    release_category = re.findall('.*/(.*)', other)[0].replace(' ', '')
    # 这一行的.*表示以贪婪的模式匹配到最后一个/,最后一个/后面的内容就是电影分类
    
    # 第三步:提取出电影评分
    movie_rate = float(re.findall('.*?rating_num.*?>(.*?)<.*?', movie_tag)[0])
    

    第四步,将我们提取的内容组成一个元组,并添加到一个列表中:

    movie_informations = []
    movie_informations.append((movie_name, release_date, release_place, release_category, movie_rate))
    

    第五步:获取要爬取的所有URL,保存在列表中:

    url_list = []
    
    for i in range(10):
      url = 'https://movie.douban.com/top250?start=' + str(25*i) + '&filter='
      url_list.append(url)
    

    第六步:遍历整个url_list,对每个url进行数据提取:

    import re
    import requests
    
    url_list = []
    headers = {
    	 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
                }
     for i in range(10):
     url = 'https://movie.douban.com/top250?start=' + str(25*i) + '&filter='
     url_list.append(url)
     for url in url_list:
     response = requests.get(url, headers=headers)
     content = response.text.replace(' ', '').replace('\n', '')
     all_tags = re.findall('.*grid_view.*?(<li>.*</li>)', content, flags=re.S)[0]
     movie_tags = re.findall('<li>.*?</li>', all_tags)
     for movie_tag in movie_tags:
     movie_name = re.findall('.*?class="title">(.*?)<', movie_tag)[0]
     other = re.findall('.*?class="bd".*?<br>(.*?)</p>.*', movie_tag)[0]
     release_date = int(re.findall('\d+', other)[0])
     release_place = re.findall('.*?/(.*?)/', other)[0].replace('&nbsp;', '')
     release_category = re.findall('.*/(.*)', other)[0].replace('&nbsp;', '')
     movie_rate = float(re.findall('.*?rating_num.*?>(.*?)<.*?', movie_tag)[0])
     movie_informations.append((movie_name,release_date, release_place, release_category,movie_rate))
    

    第七步:打印出提取结果:

     for index, movie_information in enumerate(movie_informations):
     # 遍历enumerate(序列)与直接遍历一个序列得到的结果区别在于,enumerate多了一个值,就是index,序列的下标,从0开始。
      movie_name, release_date, release_place, release_category, movie_rate = movie_information
      # 这一行为序列的解包,将序列中的每个值拆出来分别赋予=前面的变量
      print(index+1, movie_name, release_date, release_place,release_category, movie_rate)
    

    以上,就完成了所有的代码了。

    完整代码如下:

    # 实现功能:利用正则表达式提取电影名称/上映时间/上映地点/电影分类/电影评分
    import re
    import requests
    
    url_list = []
    movie_informations = []
    
    headers = {
         'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
    }
    
    for i in range(10):
        url = 'https://movie.douban.com/top250?start=' + str(25*i) + '&filter='
        url_list.append(url)
    
        for url in url_list:
            response = requests.get(url, headers=headers)
            content = response.text.replace(' ', '').replace('\n', '')
            all_tags = re.findall('.*grid_view.*?(<li>.*</li>)', content,flags=re.S)[0]
            movie_tags = re.findall('<li>.*?</li>', all_tags)
        for movie_tag in movie_tags:
            movie_name = re.findall('.*?class="title">(.*?)<', movie_tag)[0]
            other = re.findall('.*?class="bd".*?<br>(.*?)</p>.*', movie_tag)[0]
            release_date = int(re.findall('\d+', other)[0])
            release_place = re.findall('.*?/(.*?)/', other)[0].replace(' ', '')
            release_category = re.findall('.*/(.*)', other)[0].replace(' ', '')
            movie_rate = float(re.findall('.*?rating_num.*?>(.*?)<.*?', movie_tag)[0])
            movie_informations.append((movie_name, release_date, release_place, release_category,movie_rate))
    
    for index, movie_information in enumerate(movie_informations):
        movie_name, release_date, release_place, release_category, movie_rate = movie_information
        # 这一行为序列的解包,将序列中的每个值拆出来分别赋予=前面的变量
        print(index+1, movie_name, release_date, release_place, release_category, movie_rate)    
    
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值