《零基础入门学习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…

Kafka日志告警关键字

在配置Kafka日志告警时&#xff0c;可以关注以下关键字&#xff1a; ERROR&#xff1a;表示发生了错误&#xff0c;需要进一步检查错误消息以确定具体的问题。 Exception&#xff1a;表示发生了异常情况&#xff0c;需要进一步检查异常信息以确定具体的问题。 Failed&#xf…

uniapp实现轮播图大图预览

为轮播图绑定点击事件将轮播图index传给自定义事件 <!-- 轮播图 --><swiper :indicator-dots"true" :autoplay"true" :interval"3000" :duration"1000" circular"true"><swiper-item v-for"(item, inde…

【单片机】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…

pytest钩子函数(四):用例执行钩子

前言 pytest这个框架提供了非常多的钩子。通过这些钩子我们可以对pytest 用例收集、用例执行、报告输出等各个阶段进行干预&#xff0c;根据需求去开发对应的插件&#xff0c;以满足自己的使用场景。 01 什么是钩子函数&#xff1f; 钩子函数在pytest称之为Hook函数,它pytes…

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

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

ElasticSearch初探

ElasticSearch 是什么? ElasticSearch是一个分布式、Restfui 风格的搜索和数据分析引擎,国内简称ES, ElasticSearch 是用Java开发的,底层基于Lucene,Lucene是一种全文的索引库,直接采用Lucene是比较麻烦的,Elasticsearch在Lucene的基础上开发了一个强大的搜索引擎。 ELK…

第四十六天

●运算符也叫操作符 通过运算符可以对一个或多个值进行运算&#xff0c;并获取运算结果 比如&#xff1a;typeof就是运算符&#xff0c;可以来获得一个值的类型 它会将该值的类型以字符串的形式返回 number string boolean undefined object ●算数运算符 当对非Number类型的…

vscode开发Go和Java

vscode开发Go和Java 最新最全 vscode 插件推荐可以参考&#xff1a; https://zhuanlan.zhihu.com/p/623580867 1、公共插件安装 下面是个人使用的插件&#xff1a; # 中文插件 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code https://marketplace…

基于人工智能的房地产行业的ChatGPT能提供什么样的改进解决方案?

解决方案描述自动化客户服务通过自然语言处理和聊天机器人技术&#xff0c;实现自动化的客户咨询和问题解答。这可以提高客户满意度&#xff0c;节省人力资源&#xff0c;并实现全天候的客户支持。数据分析与预测利用机器学习和数据分析技术&#xff0c;对房地产市场数据进行深…

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

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

Hive窗口函数大全

Hive窗口函数 一、偏移量函数laglead 二、窗口分析函数first_valuelast_value 三、排序函数rankdense_rankrow_number 一、偏移量函数 lag 语法&#xff1a;lag(col,n,default_val) 返回值&#xff1a;字段类型 说明&#xff1a;往前第n行数据。 lag(column字段&#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博客 借用…