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

如果你在课后有勤加练习,那么你对于字符串的查找应该是已经深恶痛绝了,你发现下载一个网页是很容易的,但是要在网页中查找到你需要的内容,那就是困难的,你发现字符串查找并没有你想象的那么简单,并不是说直接使用 find 方法找到匹配字符串的位置就可以了。

我们来举个例子,学习了前面几节课你应该已经尝试过写一个脚本来自动获取最新的代理 ip 地址,但是呢,你肯定会遇到困难,我现在来重现一下大家会遇到的困难。

大家肯定会先踩点,在 https://www.xicidaili.com/wt 网点审查元素,找一下代理 ip 前后有什么标签,例如:

ip 为 61.135.217.7 前后的标签为 td ,但是呢,别的地方也会有 td,但是里面包括的不是 ip 地址,你可能会费了九牛二虎之力,先找 table,再找 tbody,再找td,终于找到了 ip 地址的唯一特性,找到了一个 ip 地址,但是这样写不仅麻烦,而且不具有通用性,你在这个网站可行,在另外一个网站就不可行了,而且,万一站长哪天心血来潮,改了一下网页,那你更是心塞啊。

这时候你就会琢磨,可不可以按照我们需要的内容特征来进行自动查找呢?也就是说,如果我要找一个 ip 地址,那 ip 地址的特征是什么呢?

就是由 4 段数字组成,每段数字的范围是 0-255,分别是由3个点号隔开,这就是 ip 地址的特征嘛。根据这个特征,它去网页里面查找。

很抱歉,字符串所附带的方法你无法做到。

但是呢,我们遇到的问题,计算机老前辈们也早就已经想到了,并且已经帮我们设计出了非常优秀的解决方案,就是我们今天要讲的 正则表达式。

今天,我们将来学习使用 正则表达式来匹配 ip 地址。

关于正则表达式有一个非常经典的美式笑话:

有些人面临一个问题的时候会想:“我知道,可以使用正则表达式来解决这个问题。”于是,现在他就有两个问题了。

没错,正则表达式的确是很难学,但却非常有用。在编写字符串网页或程序的时候,经常会有查找某些符合复杂规则字符串的需求,例如,我们需要查找的 ip 地址的规则。那么,使用  Pythob 自带的字符串方法,你一定会恼羞成怒,那么这时候,如果你懂得正则表达式,你会发现,这真是灵丹妙药啊。

因为 正则表达式就是为了描述这些复杂规则的工具。正则表达式本身就是用于描述这些规则的。不同的语言均有使用正则表达式的方法,但各不相同,Python 是使用 re 模块来实现的,因为这一部分比较难,所以我们边举例子边讲解。

 
  1. >>> import re

  2. >>> re.search(r'Python', 'I love Python')

  3. <_sre.SRE_Match object; span=(7, 13), match='Python'>

  4. >>> re.search(r'Python', 'I love Python').span()

  5. (7, 13)

re.search方法

re.search 扫描整个字符串并返回正则表达式模式第一次成功匹配的位置。

函数语法:

re.search(pattern, string, flags=0)

函数参数说明:

参数描述
pattern匹配的正则表达式
string要匹配的字符串。
flags标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配成功re.search方法返回一个匹配的对象,否则返回None。

有人就会认为,你说了这么多,和 find() 方法也没有什么区别啊,使用 find() 方法也可以实现上面的功能啊。如下:

 
  1. >>> ("I love Python".find('Python'), "I love Python".find('Python') + len('Python'))

  2. (7, 13)

那我们来一个 find() 方法没办法实现的:

大家都知道 通配符 (就是我们实际中经常使用的 * 和 ?这一类可以表示任何字符的符号),例如我们想找到 word 类型的文件的时候,我们就会搜索 *.docx。

正则表达式也有所谓的通配符,这里是使用点号(.),它可以匹配除了 换行符 以外的任何字符。

 
  1. >>> re.search(r'.', 'I love Python')

  2. <_sre.SRE_Match object; span=(0, 1), match='I'>

这里它就匹配到了字符串中的 ‘I’。

 
  1. >>> re.search(r'Pytho.', 'I love Python')

  2. <_sre.SRE_Match object; span=(7, 13), match='Python'>

这里 点号(.) 就匹配到了‘c’,然后正则表达式就匹配到了 ‘Python’。

看了上面的例子,会思考的小伙伴们就有了一个问题了,既然 点号(.)可以匹配任何字符,那我如果想要匹配 点号(.)字符本身,那你要怎么办?

正如 Python 的字符串规则一样,想要消除一个字符串的特殊功能,就在前面加上 反斜杠(\),如:

 
  1. >>> re.search(r'\.', 'I love Python.com')

  2. <_sre.SRE_Match object; span=(13, 14), match='.'>

这里 ‘\.’ 匹配的就是 点号(.)本身了,这时候,点号不代表任何其他字符,它只代表点号,前面的反斜杠已经将其解译了。

也就是说,在正则表达式中,反斜杠同样具有剥夺元字符的特殊功能的能力,什么是元字符,就是这个字符它本身代表着其他含义、有特殊功能的字符,例如 点号(.)。

同样呢,反斜杠还可以使得普通的字符具有特殊能力,例如 我们想要匹配数字,我们可以使用 ‘\d’ 来匹配任何数字,如:

 
  1. >>> re.search(r'\d', 'I love Python35.com')

  2. <_sre.SRE_Match object; span=(13, 14), match='3'>

 
  1. >>> re.search(r'\d\d', 'I love Python35.com')

  2. <_sre.SRE_Match object; span=(13, 15), match='35'>

OK,我们结合以上两点知识,就可以匹配一个 ip 地址大概会这么写:

 
  1. >>> re.search(r'\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d', '192.168.111.123')

  2. <_sre.SRE_Match object; span=(0, 15), match='192.168.111.123'>

大家会看到,匹配成功了,但是我们这么写是有问题的。首先,\d 表示匹配的数字是 0~9,但是 ip 地址的约定范围每组数字的范围是 0~255,那你这里 \d\d\d 最大匹配数字是 999 ,而 ip 地址的最大范围是 255;然后,你这里要求 ip 地址每组必须是三位数字,但实际上有些 ip 地址中的某组数字只有 1 位或者 2 位,像这种情况,我们就匹配不了了。

那我们怎么解决呢?

为了表示一个字符串的范围,我们可以创建一个叫做 字符类 的东西,使用中括号[] 来创建一个字符类,字符类的含义就是你只要匹配字符类中的一个字符,那么就算匹配,举例:

我们想要匹配 元音字母(aeiou),我们就可以这样写:

 
  1. >>> re.search(r'[aeiou]', 'I love Python')

  2. <_sre.SRE_Match object; span=(3, 4), match='o'>

我们匹配到了 ‘o’,那大家可能会有疑惑了,为什么没有匹配大写字母 ‘I’ 呢,这是因为 正则表达式 是默认开启 大小字母敏感 模式的,所以 大写 ‘I’ 和小写 ‘i’ 会区分开来。解决的方案有两种,一种是关闭大小写敏感模式(后边进行讲解),另一种是修改我们的字符类[aeiou]为[aeiouAEIOU]。

你还可以在字符类中使用 横杆 ‘-’ 表示一个范围,例如:

 
  1. >>> re.search(r'[a-z]', 'I love Python')

  2. <_sre.SRE_Match object; span=(2, 3), match='l'>

 
  1. >>> re.search(r'[0-9]', 'I love 123 Python')

  2. <_sre.SRE_Match object; span=(7, 8), match='1'>

 
  1. >>> re.search(r'[2-9]', 'I love 123 Python')

  2. <_sre.SRE_Match object; span=(8, 9), match='2'>

数字范围的问题我们解决了,那接下来我们处理的第二个问题就是匹配个数的问题:

限定重复匹配的次数,我们可以使用 大括号 来解决,举例:

 
  1. >>> re.search(r'ab{3}c', 'abbbc')

  2. <_sre.SRE_Match object; span=(0, 5), match='abbbc'>

因为这个大括号里面的数字表示前面要匹配的字符要重复多少次。上面的 {3} 就表示前面的 b 匹配时要重复3次,即匹配 abbbc。

 
  1. >>> re.search(r'ab{3}c', 'abbbbc') #这样子就匹配不了,返回 None

  2. >>>

大括号里还可以给出重复匹配次数的范围,例如{a, b} 表示匹配 a 到 b 次。

 
  1. >>> re.search(r'ab{3,10}c', 'abbbbbbbc')

  2. <_sre.SRE_Match object; span=(0, 9), match='abbbbbbbc'>

接下来我们来考虑一下,如何使用正则表达式来匹配 0~255 ?

有些人想都不用想,就写给你看:

 
  1. >>> re.search(r'[0-255]', '188')

  2. <_sre.SRE_Match object; span=(0, 1), match='1'>

本来我们想匹配 188 ,结果只是匹配到了 1。

还有的同学会这样写:

 
  1. >>> re.search(r'[0-2][0-5][0-5]', '188')

  2. >>>

结果匹配不到。

跟你想象的不一样啊,要记住,正则表达式 匹配的是字符串,所以呢,数字对于字符来说,只有 0~9,例如 188,就是由1、8、8 这三个字符来组成的,并没有说 百十千 这些单位。所以呢,[0-255] 这个字符类(其中0-2,就是 0 1 2,后面两个 5 重复了)表示的是[0125]这四个数字中的某一个,所以 re.search(r'[0-255]', '188') 就只匹配到了一个 1。

所以呢,要匹配 0~255 这个范围里的数字,我们使用正则表达式应该这么写:

 
  1. >>> re.search('[01]\d\d|2[0-4]\d|25[0-5]', '188')

  2. <_sre.SRE_Match object; span=(0, 3), match='188'>

这里写的匹配的正则表达式就是 [01]\d\d 或者 2[0-4]\d 或者 25[0-5],这其中任何一个成立都是 ok 的。这里的 “或 ” 和 C语言中的 “或” 是一样的。

但是上面的写法还是存在问题,要求匹配的数字必须是 3 位的,例如:

 
  1. >>> re.search('[01]\d\d|2[0-4]\d|25[0-5]', '8')

  2. >>>

  3. >>> re.search('[01]\d\d|2[0-4]\d|25[0-5]', '18')

  4. >>>

可以像下面这样改写,让前面的两位可以重复 0 次(因为默认是重复1次嘛):

 
  1. >>> re.search('[01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]', '8')

  2. <_sre.SRE_Match object; span=(0, 1), match='8'>

  3. >>> re.search('[01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]', '80')

  4. <_sre.SRE_Match object; span=(0, 2), match='80'>

  5. >>> re.search('[01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]', '118')

  6. <_sre.SRE_Match object; span=(0, 3), match='118'>

按照这样的话,我们就可以来匹配一个 ip 地址啦:

 
  1. >>> re.search('(([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5])\.){3}[01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]', '192.168.42.1')

  2. <_sre.SRE_Match object; span=(0, 12), match='192.168.42.1'>

上面的小括号的意思就是分组,首先 ([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5]) 作为一个组,然后加上 点号,(([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5])\.) 作为新的组,然后这个组重复3次,然后再加一组数字。这样就完成了对 ip 地址的匹配啦。

现在应该可以充分理解,“当你发现一个问题可以使用正则表达式来解决的时候,于是你就会有两个问题。”这句话的含义了。

但是大家也充分理解到掌握正则表达式的重要性,因为我们这里主要是讲 python爬虫,所以并没有花太多时间来讲正则表达式的隐藏技能,大家可以看一下Python正则表达式更深层次的知识:-> Python3 如何优雅地使用正则表达式

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

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

相关文章

(已解决)RuntimeError: Java gateway process exited before sending its port number

今天用Pycharm远程使用pysaprk解释器时&#xff0c;跑代码出现了这个错误&#xff1a; RuntimeError: Java gateway process exited before sending its port number 找了好多博客都没解决问题&#xff0c;有说重装spark的&#xff0c;有说本地配Java_home的&#xff0c;后面我…

leetcode 47. 全排列 II

2023.7.23 这道题是上一题全排列 的一个升级版。 唯一区别就是需要增加一个树层去重的操作&#xff0c;因为数组nums中允许有重复的元素了&#xff0c;而上一题没有重复元素。 下面看代码&#xff1a; class Solution { public:vector<vector<int>> ans;vector<…

如何评测一个大语言模型?

编者按&#xff1a;大型语言模型&#xff08;Large language models, LLMs&#xff09;因其在学术界和工业界展现出前所未有的性能而备受青睐。随着 LLMs 在研究和实际应用中被广泛使用&#xff0c;对其进行有效评测变得愈发重要。近期已有多篇论文围绕大模型的评测进行研究&am…

RocketMQ教程-(4)-领域模型-消费者分组ConsumerGroup

定义​ 消费者分组是 Apache RocketMQ 系统中承载多个消费行为一致的消费者的负载均衡分组。 和消费者不同&#xff0c;消费者分组并不是运行实体&#xff0c;而是一个逻辑资源。在 Apache RocketMQ 中&#xff0c;通过消费者分组内初始化多个消费者实现消费性能的水平扩展以…

【云原生】Docker网络及Cgroup资源控制

一、Docker网络 1.docker网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默认网关。…

Rust之所有权

1、所有权的概念&#xff1a; 程序需要管理自己在运行时使用的计算机内部空间。Rust语言采用包含特定规则的所有权系统来管理内存&#xff0c;这套规则允许编译器在编译的过程中执行检查工作&#xff0c;而不会产生任何的运行时开销。 (1)、所有权规则&#xff1a; Rust中的…

【前端知识】React 基础巩固(三十)——CSS编写方式

React 基础巩固(三十)——CSS编写方式 1.内联样式 Style 接受一个采用小驼峰命名属性的JS对象&#xff0c;而不是CSS字符串 可以引用state中的状态来设置相关的样式 优点&#xff1a;样式之间不会有冲突&#xff1b;可以动态获取当前state中的状态 缺点&#xff1a;需要使用…

微信小程序——页面跳转方法和场景用法总结

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Arduino 与 Unity3D TCP通信

一、通过传输流的方式 #include <WiFi.h>#define LED_PIN 5 const char * ssid "Daschow2021"; const char * password "daschow2021"; void setup() {Serial.begin(9600);//连接WiFiWiFi.begin(ssid,password);Serial.print("正在连接Wifi&…

linux麒麟系统二级等保【三权分立策略】

目录 三权分立策略 一、系统管理员 二、审计管理员 三、安全管理员 三权分立策略 "建议创建管理、操作、审计三类独立权限账号&#xff0c;支持三权分立机制。参考配置&#xff1a; 管理账号&#xff08;root&#xff09;&#xff1a;拥有所有操作权限&#xff1b;…

如何理解spring cloud 和 spring cloud Alibaba

Spring Cloud是一个基于Spring Framework构建的用于开发和构建分布式系统的开源框架。它提供了一系列的工具和组件&#xff0c;用于帮助开发者快速构建、部署和管理微服务架构。Spring Cloud提供了包括服务发现、配置管理、负载均衡、断路器等在内的众多功能。 Spring Cloud A…

第四十三天

●<template>&#xff1a;每个vue文件中最多可以包含一个&#xff0c;语块包裹的内容内容会被提取&#xff0c;编译成js渲染函数&#xff0c;并附在导出的组件上作为其render选项。 ●<script>​&#xff1a;每个 vue 文件最多可以包含一个 script>块(使用 <…

【JVM】JVM执行流程 JVM类加载 垃圾回收机制等

目录 &#x1f337;1、JVM是什么&#xff1f; &#x1f337;2、JVM的执行流程&#xff08;能够描述数据区5部分&#xff09; &#x1f337;3、JVM类加载过程 &#x1f337;4、双亲委派机制&#xff1a;描述类加载的过程 问题1&#xff1a;类加载器 问题2&#xff1a;什么…

Ubuntu Linux中hostname`命令

当然&#xff0c;让我们将关于在Ubuntu Linux中理解hostname的知识转化为列表形式&#xff1a; 参考&#xff1a; https://www.howtouseubuntu.com/network/change-hostname-in-ubuntu-linux/ 定义&#xff1a;hostname是Ubuntu&#xff08;以及其他Linux发行版&#xff09;中…

最优贸易(记忆化搜索)

题目链接&#xff1a;[NOIP2009 提高组] 最优贸易 - 洛谷 思路&#xff1a;这道题的标签是SPFA&#xff0c;但是我觉得这道题可以用记忆化搜索&#xff0c;用两组dfs&#xff0c;将从1到 i点道路上的最小值都存进min数组&#xff0c;将i 到n点的最大值存进max组&#xff0c;最后…

支付宝原生小程序组件与父级传递数据(微信小程序基本一样)

1. 声明组件 在对应的目录下,右击点击 新建小程序,之后会生成对应的文件 2. 子组件 Component({data: {colorList: [#165FF6, #3D16F6,

一元多项式的表示及相加

实现思路&#xff1a; 通过链表实现&#xff0c;会更为简单直观。用链表中的每个结点表示多项式中的每一项&#xff0c;多项式每一项都是由数据域&#xff08;包含系数和指数&#xff09;和指针域构成的&#xff0c;所以在定义表示结点的结构体时&#xff0c;可如下所示进行定义…

FFMPEG android mac 编译 支持DASH/OPENSSL问题汇总

一 下载源码 FFMPEG https://github.com/FFmpeg/FFmpeg/tree/release/4.3 二 编写脚本 支持https 就必须添加open SSL 的编译 具体可以查看我的另一个关于open SSL 的 然后我们配置的一些路径 涉及 ndk 的一定要查看你用的ndk 版本 是否存在这些路径这是第一步 然后如果支…

python爬虫入门

基础回顾 使用函数, 先导入, 直接点方法名使用 import math m math.log10(100) print(m)python 交互模式 input输入示例 age int(input("请输入年龄")) age 1 print(age)if else 的使用 和java一样, 只是不加括号, else if 阉割成了 elif 与或非 java : &am…

网络中的一些概念对比

HTTP与HTTPS 从安全性和效率性进行阐述&#xff1a; 对于HTTP HTTP默认端口是80 HTTP是明文传输&#xff0c;数据以纯文本、明文形式在网络上传输&#xff0c;容易受到黑客的攻击和数据窃取。 对于HTTPS HTTPS默认端口是443 HTTPS用的是SSL或者TLS协议对数据进行加密&am…