《零基础入门学习Python》第060讲:论一只爬虫的自我修养8:正则表达式4

有了前面几节课的准备,我们这一次终于可以真刀真枪的干一场大的了,但是呢,在进行实战之前,我们还要讲讲正则表达式的实用方法和扩展语法,然后再来实战,大家多把持一会啊。

我们先来翻一下文档:

首先,我们要举的例子是讲得最多的 search() 方法,search() 方法 既有模块级别的,就是直接调用 re.search() 来实现,另外,编译后的正则表达式模式对象也同样拥有 search() 方法,我问问大家,它们之间有区别吗?

如果你的回答仅仅是 模块级别的search() 方法比模式级别的search() 方法要多一个正则表达式的参数,那你肯定没有去翻文档。

re.search(patternstringflags=0)

Scan through string looking for the first location where the regular expression pattern produces a match, and return a corresponding match object. Return None if no position in the string matches the pattern; note that this is different from finding a zero-length match at some point in the string.

这是模块级别的 search() 方法,大家注意它的参数,它有一个 flags 参数, flags 参数就我们上节课讲得 编译标志 位,作为一个模块级别的,它没办法复印,它直接在这里使用它的标志位就可以了。

pattern 是正则表达式的模式

string 是要搜索的字符串

我们再来看一下 如果是编译后的模式对象,它的 search() 方法又有哪些参数:

regex.search(string[, pos[, endpos]])

Scan through string looking for the first location where this regular expression produces a match, and return a corresponding match object. Return None if no position in the string matches the pattern; note that this is different from finding a zero-length match at some point in the string.

The optional second parameter pos gives an index in the string where the search is to start; it defaults to 0. This is not completely equivalent to slicing the string; the '^' pattern character matches at the real beginning of the string and at positions just after a newline, but not necessarily at the index where the search is to start.

The optional parameter endpos limits how far the string will be searched; it will be as if the string is endpos characters long, so only the characters from pos to endpos - 1 will be searched for a match. If endpos is less than pos, no match will be found; otherwise, if rx is a compiled regular expression object, rx.search(string, 0, 50) is equivalent to rx.search(string[:50], 0).

前面的 pattern, 模式对象的参数,就不需要了。

string 第一个参数就是待搜索的字符串

后面有两个可选参数是我们模块级别的 search() 方法没有的,它分别代表需要搜索的起始位置(pos)和结束位置(endpos)

你就可以像 rx.search(string, 0, 50) 或者 rx.search(string[:50], 0) 这样子去匹配它的搜索位置了。

还有一点可能被忽略的就是,search() 方法并不会立刻返回你所需要的字符串,取而代之,它是返回一个匹配对象。我们来举个例子:

 
  1. >>> import re

  2. >>> result = re.search(r" (\w+) (\w+)", "I love Python.com")

  3. >>> result

  4. <_sre.SRE_Match object; span=(1, 13), match=' love Python'>

我们看到,这个 result 是一个匹配对象( match object.),而不是一个字符串。它这个匹配对象有一些方法,你使用这些方法才能够获得你所需要的匹配的字符串:

例如:group()方法:

 
  1. >>> result.group()

  2. ' love Python'

我们就把匹配的内容打印出来了。首先是 一个空格,然后是 \w+ ,就是任何字符,这里就是love,然后又是一个空格,然后又是 \w+,这里就是Python。

说到这个 group()方法,值的一提的是,如果正则表达式中存在着 子组,子组会将匹配的内容进行 捕获,通过这个 group()方法 中设置序号,可以提取到对应的 子组(序号从1开始) 捕获的字符串。例如:

 
  1. >>> result.group(1)

  2. 'love'

  3. >>> result.group(2)

  4. 'Python'

除了 group()方法 之外,它还有 start()方法  、end()方法、 span() 方法,分别返回它匹配的开始位置、结束位置、范围。

match.start([group])

match.end([group])

Return the indices of the start and end of the substring matched by groupgroup defaults to zero (meaning the whole matched substring). Return -1 if group exists but did not contribute to the match. For a match object m, and a group g that did contribute to the match, the substring matched by group g (equivalent to m.group(g)) is

m.string[m.start(g):m.end(g)]

Note that m.start(group) will equal m.end(group) if group matched a null string. For example, after m = re.search('b(c?)', 'cba')m.start(0) is 1, m.end(0) is 2, m.start(1) and m.end(1) are both 2, and m.start(2) raises an IndexError exception.

An example that will remove remove_this from email addresses:

>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net'

match.span([group])

For a match m, return the 2-tuple (m.start(group), m.end(group)). Note that if group did not contribute to the match, this is (-1, -1)group defaults to zero, the entire match.

 
  1. >>> result.start()

  2. 1

  3. >>> result.end()

  4. 13

  5. >>> result.span()

  6. (1, 13)

 接下来讲讲 findall() 方法:

re.findall(patternstringflags=0)

Return all non-overlapping matches of pattern in string, as a list of strings. The string is scanned left-to-right, and matches are returned in the order found. If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group. Empty matches are included in the result unless they touch the beginning of another match.

有人可能会觉得,findall() 方法很容易,不就是找到所有匹配的内容,然后把它们组织成列表的形式返回吗。

没错,这是在正则表达式里没有子组的情况下所做的事,如果正则表达式里包含了子组,那么,findall() 会变得很聪明。

我们来举个例子吧,上贴吧爬图:

例如我们想下载这个页面的所有图片:贴吧404

我们先来踩点,看到图片格式的标签:

我们就来直接写代码啦:

首先,我们写下下面的代码,来爬取 图片地址:

 
  1. import re

  2. p = r'<img class="BDE_Image" src="[^"]+\.jpg"'

  3. imglist = re.findall(p, html)

  4. for each in imglist:

  5. print(each)

打印的结果为:

 
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\tieba.py ==============

  2. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=65ac7c3d9e0a304e5222a0f2e1c9a7c3/4056053b5bb5c9ea8d7d0bdadc39b6003bf3b34e.jpg"

  3. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=d887aa03394e251fe2f7e4f09787c9c2/77f65db5c9ea15ceaf60e830bf003af33b87b24e.jpg"

  4. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=0db90d472c1f95caa6f592bef9167fc5/2f78cfea15ce36d34f8a8b0933f33a87e850b14e.jpg"

  5. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=abfd18169ccad1c8d0bbfc2f4f3f67c4/bd2713ce36d3d5392db307fa3387e950342ab04e.jpg"

很显然,这不是我们需要的地址,我们需要的只是后面的部分。我们接下来要解决的问题就是如何将里面的地址提取出来,不少人听到这里,可能就已经开始动手了。但是,别急,我这里有更好的方法。

只需要把图片地址用小括号括起来,即将:

 p = r'<img class="BDE_Image" src="[^"]+\.jpg"' 改为 p = r'<img class="BDE_Image" src="([^"]+\.jpg)"',

大家再来看一下运行后的结果:

 
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\tieba.py ==============

  2. https://imgsa.baidu.com/forum/w%3D580/sign=65ac7c3d9e0a304e5222a0f2e1c9a7c3/4056053b5bb5c9ea8d7d0bdadc39b6003bf3b34e.jpg

  3. https://imgsa.baidu.com/forum/w%3D580/sign=d887aa03394e251fe2f7e4f09787c9c2/77f65db5c9ea15ceaf60e830bf003af33b87b24e.jpg

  4. https://imgsa.baidu.com/forum/w%3D580/sign=0db90d472c1f95caa6f592bef9167fc5/2f78cfea15ce36d34f8a8b0933f33a87e850b14e.jpg

  5. https://imgsa.baidu.com/forum/w%3D580/sign=abfd18169ccad1c8d0bbfc2f4f3f67c4/bd2713ce36d3d5392db307fa3387e950342ab04e.jpg

是不是很兴奋,是不是很惊讶,先别急,我先把代码敲完,再给大家讲解。

 
  1. import urllib.request

  2. import re

  3. def open_url(url):

  4. req = urllib.request.Request(url)

  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')

  6. response = urllib.request.urlopen(url)

  7. html = response.read()

  8. return html

  9. def get_img(url):

  10. html = open_url(url).decode('utf-8')

  11. p = r'<img class="BDE_Image" src="([^"]+\.jpg)"'

  12. imglist = re.findall(p, html)

  13. '''

  14. for each in imglist:

  15. print(each)

  16. '''

  17. for each in imglist:

  18. filename = each.split('/')[-1]

  19. urllib.request.urlretrieve(each, filename, None)

  20. if __name__ == '__main__':

  21. url = "https://tieba.baidu.com/p/4863860271"

  22. get_img(url)

运行结果,就是很多美眉图片出现在桌面了(前提是这个程序在桌面运行,图片自动下载到程序所在文件夹。)

接下来就来解决大家的困惑了:为什么加个小括号会如此方便呢?

这是因为在 findall() 方法中,如果给出的正则表达式是包含着子组的话,那么就会把子组的内容单独给返回回来。然而,如果存在多个子组,那么它还会将匹配的内容组合成元组的形式再返回。

我们还是举个例子:

因为有时候 findall() 如果使用的不好,很多同学就会感觉很疑惑,很迷茫……

拿前面匹配 ip 地址的正则表达式来讲解,我们使用 findall() 来尝试自动从https://www.xicidaili.com/wt/获取 ip 地址:

初代码如下:

 
  1. import urllib.request

  2. import re

  3. def open_url(url):

  4. req = urllib.request.Request(url)

  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')

  6. reponse = urllib.request.urlopen(req)

  7. html = reponse.read()

  8. return html

  9. def get_ip(url):

  10. html = open_url(url).decode('utf-8')

  11. p = r'(([0,1]?\d?\d|2[0-4]\d|25[0-5])\.){3}([0,1]?\d?\d|2[0-4]\d|25[0-5])'

  12. iplist = re.findall(p, html)

  13. for each in iplist:

  14. print(each)

  15. if __name__ == "__main__":

  16. url = "https://www.xicidaili.com/wt/"

  17. get_ip(url)

运行结果如下:

 
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\getIP.py ==============

  2. ('180.', '180', '122')

  3. ('248.', '248', '79')

  4. ('129.', '129', '198')

  5. ('217.', '217', '7')

  6. ('40.', '40', '35')

  7. ('128.', '128', '21')

  8. ('118.', '118', '106')

  9. ('101.', '101', '46')

  10. ('3.', '3', '4')

得到的结果让我们很迷茫,为什么会这样呢?这明显不是我们想要的结果,这是因为我们在正则表达式里面使用了 3 个子组,所以,findall() 会自作聪明的把我们的结果做了分类,然后用 元组的形式返回给我们。

那有没有解决的方法呢?

要解决这个问题,我们可以让子组不捕获内容。

我们查看 -> Python3 正则表达式特殊符号及用法(详细列表),寻求扩展语法。

让子组不捕获内容,扩展语法 就是非捕获组:

所以我们的初代码修改如下:

 
  1. import urllib.request

  2. import re

  3. def open_url(url):

  4. req = urllib.request.Request(url)

  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')

  6. reponse = urllib.request.urlopen(req)

  7. html = reponse.read()

  8. return html

  9. def get_ip(url):

  10. html = open_url(url).decode('utf-8')

  11. p = r'(?:(?:[0,1]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[0,1]?\d?\d|2[0-4]\d|25[0-5])'

  12. iplist = re.findall(p, html)

  13. for each in iplist:

  14. print(each)

  15. if __name__ == "__main__":

  16. url = "https://www.xicidaili.com/wt/"

  17. get_ip(url)

运行得到的结果也是我们想要的 ip 地址了,如下:

 
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\getIP.py ==============

  2. 183.47.40.35

  3. 61.135.217.7

  4. 221.214.180.122

  5. 101.76.248.79

  6. 182.88.129.198

  7. 175.165.128.21

  8. 42.48.118.106

  9. 60.216.101.46

  10. 219.245.3.4

  11. 117.85.221.45

接下来我们又回到文档:

另外还有一些使用的方法,例如:

finditer() ,是将结果返回一个迭代器,方便以迭代方式获取数据。

sub() ,是实现替换的操作。

在Python3 正则表达式特殊符号及用法(详细列表)中也还有一些特殊的语法,例如:

(?=...):前向肯定断言。

(?!...):前向否定断言。

(?<=...):后向肯定断言。

(?<!...):后向肯定断言。

这些都是非常有用的,但是呢,这些内容有点多了,如果说全部都讲正则表达式的话,那我们就是喧宾夺主了,我们主要讲的是 网络爬虫 哦。

所以,大家还是要自主学习一下,多看,多学,多操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/5774.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

openGauss学习笔记-17 openGauss 简单数据管理-表达式

文章目录 openGauss学习笔记-17 openGauss 简单数据管理-表达式17.1 简单表达式17.2 条件表达式17.3 子查询表达式17.4 数组表达式17.5 行表达式 openGauss学习笔记-17 openGauss 简单数据管理-表达式 表达式类似一个公式&#xff0c;我们可以将其应用在查询语句中&#xff0c…

25 MFC 数据库

文章目录 导入ADO库 导入ADO库 #import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF","rsEOF")void CADODlg::OnBnClickedBtnQuery() {//导入ADO库::CoInitialize(NULL);//初始化COM库_ConnectionPtr pCo…

《面试1v1》如何提高远程用户的吞吐量

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

Flutter动画库:animations(路由过渡动画或者页面切换动画)

animations animations 是一个 Flutter 库&#xff0c;它提供了一组用于创建动画效果的工具和组件。这个库的核心重点是路由过渡动画或者页面切换动画 地址 https://pub-web.flutter-io.cn/packages/animations 安装 flutter pub add animations看了下官方文档和官方例子&a…

计科web常见错误排错【HTTP状态404、导航栏无法点开、字符乱码及前后端数据传输呈现、jsp填写的数据传到数据库显示null、HTTP状态500】

web排错记录 在使用javaweb的过程中会出现的一些错误请在下方目录查找。 目录 错误1&#xff1a;HTTP状态404——未找到 错误2&#xff1a;导航栏下拉菜单无法点开的问题 错误3&#xff1a;字符乱码问题 错误4&#xff1a;jsp网页全部都是&#xff1f;&#xff1f;&#x…

【单片机】MSP430F149单片机,晨启,音乐播放器,蜂鸣器音乐

四、音乐播放器 任务要求&#xff1a; 设计制作一个简易音乐播放器&#xff08;通过手柄板上的蜂鸣器发声&#xff0c;播放2到4首音 乐&#xff09;&#xff0c;同时LED模块闪烁&#xff0c;给人视、听觉美的感受。 评分细则&#xff1a; 按下播放按键P15开始播放音乐&#x…

【C++】继承基础知识及简单应用,使用reportSingleClassLayout(在Visual Studio开发人员命令提示窗口)查看派生类详细信息

author&#xff1a;&Carlton tag&#xff1a;C topic&#xff1a;【C】继承基础知识及简单应用&#xff0c;使用reportSingleClassLayout&#xff08;在Visual Studio开发人员命令提示窗口&#xff09;查看派生类详细信息 website&#xff1a;黑马程序员C date&#xf…

微信小程序原生上传图片和预览+云函数上传

1.前台页面 1.1wxml问阿金 <!-- 说明一个上传页面的按钮 --> <button type"primary" bindtap"uploadPage">上传页面展示</button> <!-- 声明一个上传服务器的按钮 --> <button type"warn" bindtap"uploadSeve…

第四讲:MySQL中DDL一些基本数据类型及表的创建、查询

目录 1、创建表:2、DDL一些基本数据类型&#xff1a; 1、创建表: 部分单词及解析&#xff1a; 1、tables:表 2、comment:评论&#xff0c;解释 3、gender:性别 4、neighbor&#xff1a;邻居 1、创建表&#xff1a;&#xff08;注&#xff1a;在自定义数据库操作&#xff0c;…

spring中bean实例化的三种方式 -- Spring入门(二)

文章目录 前言1.Bean实例化简介2.bean的实例化 -- 构造方法3.bean的实例化 -- 静态工厂实例化4.bean实例化 -- 实例工厂和FactoryBean5.三种bean实例化方式的区别 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。…

Leetcode 112. 路径总和

题目链接&#xff1a;https://leetcode.cn/problems/path-sum/description/ 思路 递归&#xff0c;先序遍历二叉树&#xff0c;每遍历一个节点便减去当前存储值&#xff08;targetSum targetSum - root.val&#xff09;&#xff1b;当到达某个节点等于targetSum (targetSum…

labview 子画面插入面板

1.前言 在前面一篇文章中描述了弹框式显示子画面&#xff0c; labview 弹窗(子vi)_weixin_39926429的博客-CSDN博客 本文介绍插入式显示子画面。 本文的主题在以前的文章中介绍过&#xff0c; labview 插入子面板_labview插入子面板_weixin_39926429的博客-CSDN博客 借用…

机器学习算法分类

机器学习根据任务的不同&#xff0c;可以分为监督学习、无监督学习、半监督学习、强化学习。 1. 无监督学习 训练数据不包含任何类别信息。无监督学习里典型例子是聚类。要解决的问题是聚类问题和降维问题&#xff0c;聚类算法利用样本的特征&#xff0c;将具有相似特征的样本…

微服务 云原生:搭建 K8S 集群

为节约时间和成本&#xff0c;仅供学习使用&#xff0c;直接在两台虚拟机上模拟 K8S 集群搭建 踩坑之旅 系统环境&#xff1a;CentOS-7-x86_64-Minimal-2009 镜像&#xff0c;为方便起见&#xff0c;直接在 root 账户下操作&#xff0c;现实情况最好不要这样做。 基础准备 关…

React18和React16合成事件原理(附图)

&#x1f4a1; React18合成事件的处理原理 “绝对不是”给当前元素基于addEventListener做的事件绑定&#xff0c;React中的合成事件&#xff0c;都是基于“事件委托”处理的&#xff01; 在React17及以后版本&#xff0c;都是委托给#root这个容器&#xff08;捕获和冒泡都做了…

【java】java中注解的简介,如何自定义注解,有哪些类型,有什么作用

java注解 注解的定义 Java 注解用于为 Java 代码提供元数据。作为元数据&#xff0c;注解不直接影响你的代码执行&#xff0c;但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。 首先要明确一点的是&#xff0c;注解并没有实际的作用&…

八股文(消息队列)

文章目录 1. RabbitMQ特点2. 如何保证消息的可靠性3. RabbitMQ消息的顺序性4. 实现RabbitMQ的高可用性5. 如何解决消息队列的延时以及过期失效问题&#xff1f;6. RabbitMQ死信队列7. RabbitMQ延迟队列8.RabbitMQ的工作模式9. RabbitMQ消息如何传输10. 核心概念10.1 生产者和消…

SpringBoot整合Spring Security实现权限控制

文章目录 Spring Security介绍Spring Security案例1、快速搭建一个springboot工程2、导入SpringSecurity整合springboot工程3、认证3.1、登录流程校验3.2、入门案例的原理3.3、实现思路3.4、实现认证流程&#xff08;自定义&#xff09;3.5、正式实现3.5.1 实现数据库的校验3.5…

python node Ubuntu 安装软件、删除软件 、更新软件 中的 软件源概念

在Node 用npm 安装软件 在Python 用 pip 安装软件 在Ubuntu 用 apt 、apt-get 、snap 安装软件 因为这三款软件 都是国外的&#xff0c; 软件包&#xff08;模块&#xff09;都放在国外的&#xff0c; 安装 、更新 特别慢 Node中配置 下载源 在 node 中 要配置 下载的的地址…

【C语言初阶】指针的运算or数组与指针的关系你了解吗?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《快速入门C语言》《C语言初阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 指针运算&#x1f4ad; 指针-整数&#x1f4ad; 指针-指针&#x1f4ad; 指针…