1.正则表达式初探

用比较经典的例子,查找一段文本中的手机号码。比如对于文本“我现在用的电话是188-8888-8888,之前那个186-6666-6666已经不用了”,我们想获取其中的手机号码信息,用正则表达式可以这么做呢?

正则表达式,简称为 regex,是文本模式的描述方法。例如, \d 是一个正则表达式,表示一位数字字符,即任何一位 0 到 9 的数字 。Python 使用正则表达式\d\d\d-\d\d\d\d-\d\d\d\d,来匹配3 个数字、一个短横线、4 个数字、一个短横线、4 个数字。所有其他字符串都不能匹配\d\d\d-\d\d\d\d-\d\d\d\d 正则表达式。

在一个表达式后加上花括号包围的 3({3}),就是说,“匹配这个模式 3 次”。所以较短的正则表达式\d{3}-\d{4}-\d{4},也可以匹配正确的手机号码格式。

引入正则表达式库 re ,该库是python自带的哈。

In [1]: import re
...: # 创建一个regex模式对象
...: phoneNum = re.compile(r'\d\d\d-\d\d\d\d-\d\d\d\d')
...: # 匹配regex对象
...: mo = phoneNum.search('我现在用的电话是188-8888-8888,之前那个186-6666-6666已经不用了') In [2]: mo.group()
Out[2]: '188-8888-8888'

其实,以下是等价的

# 创建一个regex模式对象,pattern指待匹配的正则表达式
phoneNum = re.compile(pattern)
# 匹配regex对象,string指代匹配的文本内容
mo = phoneNum.search(string)

等价于

mo = phoneNum.search(pattern, string)

如果需要多次使用这个正则表达式的话,使用 re.compile() 和保存这个正则对象以便复用,可以让程序更加高效。

不过,我们发现其实在待匹配的文本内容中出现了2个手机号码,但是 re.search() 只返回了第一个匹配成功的文本。如何可以获取全部匹配成功的项呢,咱们 可以使用re.findall()来进行操作,其返回的结果是由所有匹配组成的列表 。

In [3]: re.findall(r'\d{3}-\d{4}-\d{4}', '我现在用的电话是188-8888-8888,之前那个186-6666-6666已经不用了')
Out[3]: ['188-8888-8888', '186-6666-6666']

2.用正则表达式匹配更多模式

在实际解析网页HTML文本的时候,我们可能需要取匹配中某个部分分组文本、或者需要选择性匹配多个文本、又或者对某些字符或者分组需要匹配0/1次或者多次等等。

以下是待解析的某待租房间信息

info= '''
<h5 class="title sign"><a href="//www.ziroom.com/x/712447913.html" target="_blank" style="line-height: 0.9em;">合租·DBC加州小镇C区4居室-南卧</a></h5>
<div class="desc">
<div>23.3㎡ | 5/15层</div>
<div class="location">
小区距高楼金站步行约178米 </div>
</div>
<div class="price ">
<span class="rmb">¥</span>
<span class="num">188</span>
<span class="unit">/天</span>
</div>
<div class="tag">
<span>可短租</span>
<span>离地铁近</span>
<span>米苏4.0</span>
</div> '''

对于这种文本,由于存在很多空白字符类如换行、空格等等,我需要先用re.sub()进行简单的清洗。

info = re.sub(r'\s','',info) # \s 匹配任意空白字符

2.1.利用括号()进行分组

比如,我需要匹配子字符中的房间租金信息,因租金为数字但是还有别的一些信息也是数字(如房间大小等),因此我们在匹配的时候需要代入前后一些字符做唯一匹配,但是实际只需要对应的数字文本内容,因此需要进行分组。

<spanclass="num">188</span>

比如以上,我们想要获得价格188,可以使用 (\d{3}) 进行匹配。

注意:这里是的匹配模式是4位数字的精确匹配,在实际的操作中价格可能存在不确定的位置甚至带有小数,我们需要用到更复杂的匹配模式,具体见后续讲解。

In [4]: re.findall(r'<spanclass="num">(\d{3})</span>', info)
Out[4]: ['188']

2.2.利用管道|匹配多个分组

以示例的info文本,在爬虫过程中其价格有时候类型是天或者月,我们匹配的可能就是诸多表达式中的一个,此时可以使用 | 进行操作。正则表达式r“天|月”即可匹配 天 或者 月。

<spanclass="unit">/天</span>
# 或者
<spanclass="unit">/月</span>

我们采用正则表达式 r“天|月” 可实现匹配。

In [5]: re.findall(r'<spanclass="unit">/(月|天)</span>', info)
Out[5]: ['天'] In [6]: s = '<spanclass="unit">/月</span>'
In [7]: re.findall(r'<spanclass="unit">/(月|天)</span>', s)
Out[7]: ['月']

2.3.用问号?实现可选匹配

对于房间的面积,有的可能是整数有的可能是小数,因此小数点及小数点后的数字其实是可选项,为了更好的匹配这个面积文本,我们需要用到问号?。 字符?表示它前面的分组在这个模式中是可选的

<div>23.3㎡|5/15层</div>
# 或者
<div>23㎡|5/15层</div>

我们可以用 r'(\d{2}.?\d?)'来进行匹配,如果为了在整个html里找且怕存在重复,可以用r'(\d{2}.?\d?)|5/15层'。这里需要注意我们在 | 前面加了 转义字符 \,区别于 | 本身,否则可能无法得出正确结果。

In [8]: re.findall(r'<div>(\d{2}\.?\d?)㎡\|5/15层</div>',info)
Out[8]: ['23.3'] In [9]: re.findall(r'<div>(\d{2}\.?\d?)㎡\|5/15层</div>','<div>23㎡|5/15层</div>')
Out[9]: ['23']

2.4.用星号*实现0次或多次

对于楼层信息来说,我们要获取其楼层和楼高,有的可能有楼层信息但是有的可能没有,楼层和楼高可能是个位数或者十位数。这种情况下,我们可以使用星号进行匹配。 字符*表示它前面的分组在这个模式中是出现0次或者多次。

<div>23.3㎡|5/15层</div>
# 或者
<div>23㎡|9层</div>

由于楼高是一定存在的,而楼层不一定存在,因为我们可以用r'(\d*)/*(\d+)'来进行匹配,注意字符+代表至少一次,详见后续说明。

In [10]: re.findall(r'<div>\d{2}\.?\d?㎡\|(\d*)/*(\d+)层</div>', info)
Out[10]: [('5', '15')] In [11]: re.findall(r'<div>\d{2}\.?\d?㎡\|(\d*)/*(\d+)层</div>', '<div>23㎡|9层</div>')
Out[11]: [('', '9')]

2.5.用加号+实现1次或多次

我们在2.4中其实看到了 字符 + 的使用场景,其代表的就是 它前面的分组在这个模式中是出现1次或者多次

<spanclass="num">188</span>
# 或者
<spanclass="num">1888</span>

我们回到 2.1.中 匹配租金的案例,其实对于租金来说除了3位数之外,租金金额其实是一个大于0的值,也就是至少出现1次数字,因此我们可以用 r'(\d+)' 来匹配。

In [12]: re.findall(r'<spanclass="num">(\d+)</span>', info)
Out[12]: ['188'] In [13]: re.findall(r'<spanclass="num">(\d+)</span>', '<spanclass="num">1888</span>')
Out[13]: ['1888']

2.6.用花括号{}匹配特定次数

再以2.3.中的房间面积为例,我们认为房间面积不可能超过3位数、最低1位数 为正常值。如果想要一个分组重复特定次数,就在正则表达式中该分组的后面,跟上花括号包围的数字。例如,正则表达式(Ha){3}将匹配字符串'HaHaHa',但不会匹配'HaHa',因为后者只重复了(Ha)分组两次。

除了一个数字,还可以指定一个范围,即在花括号中写下一个最小值、一个逗号和一个最大值。例如,正则表达式(Ha){3,5}将匹配'HaHaHa'、 'HaHaHaHa'和'HaHaHaHaHa'。

也可以不写花括号中的第一个或第二个数字, 不限定最小值或最大值。例如,(Ha){3,}将匹配 3 次或更多次实例, (Ha){,5}将匹配 0 到 5 次实例。

不过,在使用过程中一定要慎重,同样的分组在不同的匹配模式可能带来不同的结果。

In [14]: re.findall(r'(\d{2,3})㎡','<div>3456㎡|5/15层</div>')
Out[14]: ['456'] In [15]: re.findall(r'<div>(\d{2,3})㎡','<div>3456㎡|5/15层</div>')
Out[15]: []

3.贪心和非贪心匹配

Python 的正则表达式默认是“贪心” 的,这表示在有二义的情况下,它们会尽可能匹配最长的字符串。

在表达式后面加上符号?,即为非贪心匹配。

In [16]: greedyHaRegex = re.compile(r'(Ha){3,5}')

In [17]: mo1 = greedyHaRegex.search('HaHaHaHaHa')

In [18]: mo1.group()
Out[18]: 'HaHaHaHaHa' In [19]: greedyHaRegex = re.compile(r'(Ha){3,5}?') In [20]: mo2 = greedyHaRegex.search('HaHaHaHaHa') In [21]: mo2.group()
Out[21]: 'HaHaHa' In [22]: re.findall(r'(Ha){3,5}?','HaHaHaHaHa')
Out[22]: ['Ha'] In [23]: re.findall(r'(Ha){3,5}','HaHaHaHaHa')
Out[23]: ['Ha'] In [24]: re.findall(r'((Ha){3,5})','HaHaHaHaHa')
Out[24]: [('HaHaHaHaHa', 'Ha')] In [25]: re.findall(r'((Ha){3,5}?)','HaHaHaHaHa')
Out[25]: [('HaHaHa', 'Ha')]

4.字符类型

模式 描述
^ 匹配字符串的开头
$ 匹配字符串的末尾。
. 匹配任意字符,除了换行符,当.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[...] 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k'
[^...] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
* 匹配0个或多个的表达式。
+ 匹配1个或多个的表达式。
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{ n} 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。
{ n,} 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+"。"o{0,}" 则等价于 "o*"。
{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
a|b 匹配a或b
() 对正则表达式分组并记住匹配的文本
(?imx) 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx) 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: ) 类似 (...), 但是不表示一个组
(?imx: ) 在括号中使用i, m, 或 x 可选标志
(?-imx: ) 在括号中不使用i, m, 或 x 可选标志
(?#...) 注释.
(?= ) 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! ) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
(?> ) 匹配的独立模式,省去回溯。
\w 匹配字母数字及下划线
\W 匹配非字母数字及下划线
\s 匹配任意空白字符,等价于 [ \t\n\r\f]。
\S 匹配任意非空字符
\d 匹配任意数字,等价于 [0-9].
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z 匹配字符串结束
\G 匹配最后匹配完成的位置。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\n, \t, 匹配一个换行符,匹配一个制表符
\1...\9 匹配第n个分组的内容。
\10 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。
实例 描述
[Pp]ython 匹配 "Python" 或 "python"
rub[ye] 匹配 "ruby" 或 "rube"
[aeiou] 匹配中括号内的任意一个字母
[0-9] 匹配任何数字。类似于 [0123456789]
[a-z] 匹配任何小写字母
[A-Z] 匹配任何大写字母
[a-zA-Z0-9] 匹配任何字母及数字
[^aeiou] 除了aeiou字母以外的所有字符
[^0-9] 匹配除了数字外的字符

正则匹配模式表

标志 含义
re.S(DOTALL) 使.匹配包括换行在内的所有字符
re.I(IGNORECASE) 使匹配对大小写不敏感
re.L(LOCALE) 做本地化识别(locale-aware)匹配,法语等
re.M(MULTILINE) 多行匹配,影响^和$
re.X(VERBOSE) 该标志通过给予更灵活的格式以便将正则表达式写得更易于理解
re.U 根据Unicode字符集解析字符,这个标志影响\w,\W,\b,\B

5.split()函数

根据正则匹配分割字符串,返回分割后的一个列表 split(pattern, string, maxsplit=0, flags=0)

pattern:正则模型

string :要匹配的字符串

maxsplit:指定分割个数

flags  :匹配模式

当我们获取了全部房源信息后,需要对一些信息进行二次解析,比如房屋信息的解析。

In [26]: # 房屋信息解析
...: s1 = '合租·李村东里3居室-北卧'
...: s2 = '合租·强佑·府学上院4居室-北卧'
...: s3 = '整租·铁二区1室1厅-北'
...: s4 = '整租·厂甸11号院1室1厅-东'
...: s5 = '整租·牛街182室1厅-西' In [27]: re.split(r'(\S*?)·(.*)(\d居*室.*)-(.*)',s1)
Out[27]: ['', '合租', '李村东里', '3居室', '北卧', ''] In [28]: re.split(r'(\S*?)·(.*)(\d居*室.*)-(.*)',s2)
Out[28]: ['', '合租', '强佑·府学上院', '4居室', '北卧', ''] In [29]: re.split(r'(\S*?)·(.*)(\d居*室.*)-(.*)',s3)
Out[29]: ['', '整租', '铁二区', '1室1厅', '北', ''] In [30]: re.split(r'(\S*?)·(.*)(\d居*室.*)-(.*)',s4)
Out[30]: ['', '整租', '厂甸11号院', '1室1厅', '东', ''] In [31]: re.split(r'(\S*?)·(.*)(\d居*室.*)-(.*)',s5)
Out[31]: ['', '整租', '牛街18', '2室1厅', '西', '']

大家可以尝试更多种正则表达式匹配规则,比如能把前后的空字符串去掉的等等。

如果我们要解析出 房间面积、楼层和楼高信息,观测数据发现存在以下3种情况,大家觉得怎么写正则表达式能实现呢?

# 房间信息解析
# 我们在数据处理中发现存在异常数据(楼层如 7层 或 -1/5层)
s1 = '87.26㎡|11/29层'
s2 = '87㎡|7层'
s3 = '8.6㎡|-1/5层'

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理

想要获取更多Python学习资料可以加
QQ:2955637827私聊
或加Q群630390733
大家一起来学习讨论吧!

对着爬虫网页HTML学习Python正则表达式re的更多相关文章

  1. 【Python】【爬虫】如何学习Python爬虫?

    如何学习Python爬虫[入门篇]? 路人甲 1 年前 想写这么一篇文章,但是知乎社区爬虫大神很多,光是整理他们的答案就够我这篇文章的内容了.对于我个人来说我更喜欢那种非常实用的教程,这种教程对于想直 ...

  2. Python正则表达式使用小记

    最近做Python课实验发现正则表达式和它在py中的的标准库re有很多能多琢磨一下的点,遂决定写成一篇小记,以后想复习能再来看看. 名词 因为不同文献书籍对正则表达式的描述有差别,我在这里列出一下我已 ...

  3. Python正则表达式学习摘要及资料

    摘要 在正则表达式中,如果直接给出字符,就是精确匹配. {m,n}? 对于前一个字符重复 m 到 n 次,并且取尽可能少的情况 在字符串'aaaaaa'中,a{2,4} 会匹配 4 个 a,但 a{2 ...

  4. Python 正则表达式 (python网络爬虫)

    昨天 2018 年 01 月 31 日,农历腊月十五日.20:00 左右,152 年一遇的月全食.血月.蓝月将今晚呈现空中,虽然没有看到蓝月亮,血月.月全食也是勉强可以了,还是可以想像一下一瓶蓝月亮洗 ...

  5. Python 正则表达式学习

    摘要 在正则表达式中,如果直接给出字符,就是精确匹配. {m,n}? 对于前一个字符重复 m到 n 次,并且取尽可能少的情况 在字符串'aaaaaa'中,a{2,4} 会匹配 4 个 a,但 a{2, ...

  6. Python 爬虫介绍,什么是爬虫,如何学习爬虫?

    ​ 作为程序员,相信大家对“爬虫”这个词并不陌生,身边常常会有人提这个词,在不了解它的人眼中,会觉得这个技术很高端很神秘.不用着急,我们的爬虫系列就是带你去揭开它的神秘面纱,探寻它真实的面目. 爬虫是 ...

  7. python爬虫之Scrapy学习

    在爬虫的路上,学习scrapy是一个必不可少的环节.也许有好多朋友此时此刻也正在接触并学习scrapy,那么很好,我们一起学习.开始接触scrapy的朋友可能会有些疑惑,毕竟是一个框架,上来不知从何学 ...

  8. Python 爬虫如何入门学习?

    "入门"是良好的动机,但是可能作用缓慢.如果你手里或者脑子里有一个项目,那么实践起来你会被目标驱动,而不会像学习模块一样慢慢学习. 另外如果说知识体系里的每一个知识点是图里的点,依 ...

  9. 第14.1节 通过Python爬取网页的学习步骤

    如果要从一个互联网前端开发的小白,学习爬虫开发,结合自己的经验老猿认为爬虫学习之路应该是这样的: 一. 了解HTML语言及css知识 这方面的知识请大家通过w3school 去学习,老猿对于html总 ...

随机推荐

  1. recovery.sh

    #!/bin/bash source /etc/profile Time=`date +%F-%H-%M` Dir=/data/any.service.recovery if [ ! -d $Dir ...

  2. Java蓝桥杯——贪心算法

    贪心算法 贪心算法:只顾眼前的苟且. 即在对问题求解时,总是做出在当前看来是最好的选择 如买苹果,专挑最大的买. 最优装载问题--加勒比海盗 货物重量:Wi={4,10,7,11,3,5,14,2} ...

  3. 更改ubuntu的分辨率

    乘号使用xyz的x打出来

  4. 20200520_windows2012安装python和django环境

    http://httpd.apache.org/download.cgi#apache24 配置文件修改后, 记得去阿里云开放端口 ServerName 172.18.196.189:9080 →不能 ...

  5. 树莓派RTL8723BU_LINUX驱动安装

    1.安装前准备:sudo apt-get -y update;sudo apt-get -y upgrade;sudo apt-get -y dist-upgrade;sudo apt-get ins ...

  6. python核心高级学习总结5--------python实现线程

    在代码实现上,线程的实现与进程的实现很类似,创建对象的格式都差不多,然后执行的时候都是用到start()方法,与进程的区别是进程是资源分配和调度的基本单位,而线程是CPU调度和分派的基本单位.其中多线 ...

  7. 第一章、PyQt的简介、安装与配置

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 第一章.PyQt的简介.安装与配置 一.引言 当朋友向我推荐PyQt时,老猿才知道有这样一个在Pyt ...

  8. PyQt(Python+Qt)学习随笔:Qt Designer中部件mimimumSize和maximumSize的含义

    1.mimimumSize mimimumSize表示部件能被缩小到的最小尺寸,单位为像素,缩小到该尺寸后不能再进一步缩小了.如果部件在布局管理器中,且布局管理器也设置了最小尺寸,则部件本身的最小尺寸 ...

  9. 【系统设计】不同分类的商品动态添加扩展属性的方法(WMS、小型电商)

    在做公司WMS系统的时候,遇到了一个商品模块的设计问题,具体业务流程如下. 客户提供需要存放的商品清单,根据商品清单生成收货单给客户,然后生成入库单进行商品入库操作. 在生成这两个单之前首先要录入商品 ...

  10. Jmeter添加事务

    事务 通过第三方工具或jmeter代理录制的脚本,你会发现会录制很多的子请求.比如当打开首页后, 会继续打开图片,css,其他请求等资源文件. 通常我们会剔除掉这些子请求, 但如果我需要衡量打开一个页 ...