正则表达式实战精讲

正则表达式是一个非常强大的字符串处理工具,几乎所有关于字符串的操作都可以使用正则表达式来完成,正则表达式的在不同的语言中使用方式可能不一样,不过只要学会了任意一门语言的正则表达式用法,其他语言中大部分也只是换了个函数的名称而已,本质都是一样的。下面介绍一下 Python 中的正则表达式怎么使用并且给出几个实战案例。

它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了。否则,该字符串就是不合法的。

例如:我们判断一个字符串是否是合法的 Email 的方法是:

创建一个匹配 Email 的正则表达式;

用该正则表达式去匹配用户的输入的字符是否符合。具体的相关重点知识和具体案例在我的上两篇博文——一文详解正则表达式和python中的正则表达式中给出。其实正则表达式用处很多,也非常具有可玩性,大家可以慢慢发现!

# 导入用于正则表达式的re模块
import re

# 取出字符串string8中所有的天气状态
string8 = "{ymd:'2018-01-01',tianqi:'晴',aqiInfo:'轻度污染'},{ymd:'2018-01-02',tianqi:'阴~小雨',aqiInfo:'优'},{ymd:'2018-01-03',tianqi:'小雨~中雨',aqiInfo:'优'},{ymd:'2018-01-04',tianqi:'中雨~小雨',aqiInfo:'优'}"
# 基于正则表达式使用findall函数
print(re.findall("tianqi:'(.*?)'", string8))

# 取出string9中所有含O字母的单词
string9  = 'Together, we discovered that a free market only thrives when there are rules to ensure competition and fair play, Our celebration of initiative and enterprise'
# 基于正则表达式使用findall函数
print(re.findall('w*ow*',string9, flags = re.I))

# 将string10中的标点符号、数字和字母删除
string10 = '据悉,这次发运的4台蒸汽冷凝罐属于国际热核聚变实验堆(ITER)项目的核二级压力设备,先后完成了压力试验、真空试验、氦气检漏试验、千斤顶试验、吊耳载荷试验、叠装试验等验收试验。'
# 基于正则表达式使用sub函数
print(re.sub('[,。、a-zA-Z0-9()]','',string10))

# 将string11中的每个子部分内容分割开
string11 = '2室2厅 | 101.62平 | 低区/7层 | 朝南 
 上海未来 - 浦东 - 金杨 - 2005年建'
# 基于正则表达式使用split函数
split = re.split('[-|]', string11)
print(split)
# 分割结果的清洗
split_strip = [i.strip() for i in split]
print(split_strip)

out:
['晴', '阴~小雨', '小雨~中雨', '中雨~小雨']
['Together', 'discovered', 'only', 'to', 'competition', 'Our', 'celebration', 'of']
据悉这次发运的台蒸汽冷凝罐属于国际热核聚变实验堆项目的核二级压力设备先后完成了压力试验真空试验氦气检漏试验千斤顶试验吊耳载荷试验叠装试验等验收试验
['2室2厅 ', ' 101.62平 ', ' 低区/7层 ', ' 朝南 ', ' 上海未来 ', ' 浦东 ', ' 金杨 ', ' 2005年建']
['2室2厅', '101.62平', '低区/7层', '朝南', '上海未来', '浦东', '金杨', '2005年建']

相关模块

我们使用 re 模块来实现正则表达式操作。 这个模块是 Python 内置模块,不需要我们另外安装。

入门实例

这里有一个字符串:

“shuwsjiwswmxpythonixkssiwmswkwxkxkx” 

我想要知道里面是否含有单词 “ python ”,我们这样处理:

导入相关模块:

import re 

准备好要处理的字符串:

s="shuwsjiwswmxpythonixkssiwmswkwxkxkx" 

编写正则表达式:

pat="python" 

进行匹配并打印结果:

result=re.search(pat,s) 
print(result) 

执行结果如下:

<re.Match object; span=(12, 18), match='python'> 

这样就表示成功匹配到,并且标示了在字符串中的具体位置(12, 18)。

正则表达式通用知识

这里只讲基本用法,避免部分人之前还不熟悉正则表达式,导致无法往后看。

已经掌握的圈友可以忽略这一部分。

正则表达式中,我们需要了解两个概念:原子和元字符。

原子

原子是表达式中基本组成单位,每个表达式中至少要包含一个原子。

简单的说,我们要匹配的规则,都是由一个个原子组成。

例如:要匹配一个数字,对应的就是以数字作为原子。

匹配一个确定的字符串

在这里直接以要匹配的字符串作为原子。

例如在一堆名字中匹配“王五”:

s="张三李四王五赵六" 
pat="王五" 
result=re.search(pat,s) 
print(result) 

执行结果:

<re.Match object; span=(4, 6), match='王五'> 

成功匹配。

匹配一个通用字符

有时我们想要匹配某一类字符,例如数字,字母等,可以把通用字符作为原子。

例如在个人介绍中匹配出张三的年龄:

`s="张三今年18岁了"` 
`pat=r"\d\d" #加上 r 避免斜杠转义,建议左右的正则表达式都加上r`
`result=re.search(pat,s)` 
`print(result)` 

执行结果:

<re.Match object; span=(4, 6), match='18'>

匹配到 18,在这里一个 “\d” 表示一个 0-9 的数字。

原子表

原子表可以定义一组平等的原子。例如:

`pat="pyth[jsz]n" # j、s、j 出现其中一个总都可以匹配` 
`string="iswhjopythanisjw"`
`rst=re.search(pat,string)` 
`print(rst)` 

常用的通用字符

 \w 匹配任意字母/数字/下划线
 \d 匹配十进制数字 
 \s 匹配空白字符 
 \W 和小写 w 相反,匹配任意字母/数字/下划线以外的字符
 \D 匹配除了十进制数以外的值 
  [0-9] 匹配一个0-9之间的数字 
  [a-z] 匹配小写英文字母 
  [A-Z] 匹配大写英文字母 
  [\u4e00-\u9fa5] 匹配中文字符 

元字符

元字符表示正则表达式中具有特殊含义的字符。

常用元字符

. 匹配任意字符 
^ 匹配字符串始位置
 $ 匹配字符串中结束的位置 
 * 前面的原子重复0次1次多次 
 * ? 前面的原子重复一次或者0次 
 * + 前面的原子重复一次或多次 
 * {n} 前面的原子出现了 n 次
 *  {n,} 前面的原子至少出现 n 次
 *  {n,m} 前面的原子出现次数介于 n-m 之间
 *  ( ) 分组,需要输出的部分 

内容太多,以分组为例讲一下:

pat=r"(python)and(java) #两个括号即是分成两组" string="dhiwpythonandjavaidetdfetvywsvywvsw"
rst1=re.search(pat,string).group(1) 
rst2=re.search(pat,string).group(1)
print(rst1,rst2) `

执行结果:

python java 

本例中,想要输出的是 “Python” 和 “Java” ,通过分组可以轻易获得。

模式修正符

不改变正则表达式的情况下,改变正则表达式的含义,实现匹配调整。

最常用的是 re.I ,表示忽略大小写。使用方法:

pat1="python" 
pat2="python" 
string="gsdwPythonmdlwwmlwmdldmluq"#其中P为大写 
rst1=re.search(pat1,string)#无法匹配,正则表达式区分大小写 rst2=re.search(pat2,string,re.I) #忽略大小写后,可以匹配了 
print(rst2) 

Python 操作正则表达式

在前面的内容中,我们已经知道了使用 Python 的 re 模块操作正则表达式的基本用法。在之前的实例中,我们使用了 re.search() 方法,下面就来介绍其他方法的使用。

compile 方法

将 pattern 转换为内部的格式,从而执行得更快。使用方法:

strr="one12twothree34four" 
pat=r'one(.*?)twothree(.*?)four' 
pattern = re.compile(pat,re.I) 
data=pattern.search(strr) 
print(data.group(2)) 

match 方法

用于查找字符串的头部(也可以指定起始位置)。

它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。

strr="1234567890" 
pat=r'4' 
pattern = re.compile(pat) 
m1=pattern.match(strr) # 从头部开始匹配 m2=pattern.match(strr,3,9)# 从指定位置开始匹配 
print(m2) 

针对匹配的结果,还可以做以下操作:

print(m1.group(1)) # 获取匹配的字符,括号里面是分组序号,没有序号则获取整体 
print(m1.start()) # 获取匹配字符起点位置,括号里面是分组序号 
m.end() # 获取匹配字符结束位置,括号里面是分组序号 
m.span() # 获取匹配字符起点和结束位置,括号里面是分组序号 

search 方法

search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回。

strr="one1 2two three3 4four" 
pattern=re.compile(r"\d+[a-z]+") 
m=pattern.search(strr) 

针对匹配的结果,还可以做以下操作:

m.group() # 获取匹配的字符 s1=m.start() # 获取匹配字符起点位置 s2=m.end() # 获取匹配字符结束位置 s3=m.span() # 获取匹配字符起点和结束位置

findall 方法

findall 以列表形式返回全部能匹配的子串,如果没有匹配,则返回一个空列表。

strr="one1 2two three3 4four" 
pattern=re.compile(r"\d+[a-z]+",re.I) 
m=pattern.findall(strr) 
print(m) 

finditer 方法

finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。

但它返回一个顺序访问每一个匹配结果的迭代器。

strr="hello 123456 789" 
pattern=re.compile(r'\d+') 
m=pattern.finditer(strr) #需要用循环才能遍历出里面的值
for x in m: 
	print(x.group()) 

split 方法

split 方法按照能够匹配的子串将字符串分割后返回列表。

strr="a,b;; c d" 
pattern=re.compile(r'[\,\s\;]+') #表示用逗号、空格和分号将字符串分割 m=pattern.split(strr) 
print(m) 

sub 方法

sub 方法用于替换字符串中的某些字段。

strr="hello 123, hello 456" 
pattern=re.compile(r'\d+') #要替换的部分 
m=pattern.sub("666",strr) 
print(m) 

贪婪模式与非贪婪模式

贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * ); 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? )。

Python 里数量词默认是贪婪的。

贪婪模式:

strr='aa<div>test1</div>bb<div>test2</div>cc' 
pattern1=re.compile(r"<div>(.*)</div>") #贪婪模式 
m1=pattern1.findall(strr) 
print(m1) 

匹配结果:

['test1</div>bb<div>test2'] 

非贪婪模式:

strr='aa<div>test1</div>bb<div>test2</div>cc' 
pattern2=re.compile(r"<div>(.*?)</div>") #非贪婪模式 
m2=pattern2.findall(strr) 
print(m2) 

匹配结果:

['test1', 'test2'] 

实例 1 : 统计《天龙八部》中谁是第一主角?

有分析表明,一部小说中,主角的名字出现的次数最多。

小说《天龙八部》中公认的三位主角:乔峰、段誉、虚竹,那么谁是第一主角呢?

我们可以统计整部小说中三个人的名字出现的次数,在这里我们用到了 findall( ) 方法。

具体代码如下:

import re 
with open(r"d:\myfile\tianlongbabu.txt","rb") as f: data=f.read().decode() #以名字作为正则表达式 
pat1="乔峰" 
pat2="乔峰" 
pat3="段誉" 
pat4="虚竹" 
pattern1=re.compile(pat1) 
pattern2=re.compile(pat2) 
pattern3=re.compile(pat3) 
pattern4=re.compile(pat4) #获取到几个姓名的列表 
list1=pattern1.findall(data) 
list2=pattern2.findall(data) 
list3=pattern3.findall(data) 
list4=pattern4.findall(data) 
print("乔峰出现次数:",len(list1)+len(list2)) 
print("段誉出现次数:",len(list3)) 
print("虚竹出现次数:",len(list4)) 

其中,乔峰出现的次数是需要把乔峰和萧峰两个字段加起来。执行结果如下:

乔峰出现次数: 2172 
段誉出现次数: 3245 
虚竹出现次数: 1584 

我们发现,段誉出现的次数最(不同版本具体数字不同),是不是有点出乎意料?

当然这个仅供参考,不代表小说作者真实意图。

实例 2 :提取网页中特定信息

任务如下:我们需要用爬虫获取百度首页的信息,然后提取网页 title 标题。

我们知道,百度的网页标题是“百度一下,你就知道”,在网页中的内容为:

<title>百度一下,你就知道</title> 

获取这两个标签中间的文字,我们可以用 (.*?) ,表示任意内容并分组。 具体代码如下:

import re 
from urllib import request #爬虫爬取百度首页内容 data=request.urlopen("http://www.baidu.com/").read().decode() #分析网页,确定正则表达式 
pat=r'<title>(.*?)</title>' 
pattern=re.compile(pat) 
result=pattern.search(data).group(1) 
print(result) 

执行结果:

百度一下,你就知道

成功获取到了 title 信息。

实例 3 :爬虫中获取电话号码信息

下面来看一个更复杂一点的例子。我用一个爬虫爬取了一个常用电话号码的网站,得到了网站的网页信息。现在需要在里面清洗出需要的电话号码,网站内容是这样的:
在这里插入图片描述

通过爬虫,我找到了单位名字和电话号码所在的html字段:
在这里插入图片描述

最后使用 findall( ) 方法获取所有信息。

完整代码如下:

import re 
import requests 
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Ap\ pleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Sa\ fari/537.36" } 
response=requests.get("http://changyongdianhuahaoma.51240.com/",headers=headers).text 
pat1=r'<bgcolor="#EFF7F0">[\s\S]*?<td>(.*?)</td>[\s\S]*?<td>[\s\S]*?</td>[\s\S]*?</tr>' 
pat2=r'<tr bgcolor="#EFF7F0">[\s\S]*?<td>[\s\S]*?</td>[\s\S]*?<td>(.*?)</td>[\s\S]*?</tr>' 
pattern1=re.compile(pat1) 
pattern2=re.compile(pat2) 
data1=pattern1.findall(response) 
data2=pattern2.findall(response) 
resultlist=[] 
for i in range(0,len(data1)): 
	resultlist.append(data1[i]+data2[i]) 
print(resultlist) 

执行结果:

['匪警110', '火警119', '急救中心120', '交通事故122', '公安短信报警12110', '水上求救专用12395', '天气预报12121', '报时服务12117', '森林火警95119', '红十字会急救台999', '招商银行95555', '中国银行95566', '建设银行95533', '工商银行95588', '中信银行95558', '农业银行95599', '民生银行95568', '光大银行95595', '交通银行95559', '广发银行95508', '浦发银行95528', '深发银行95501', '华夏银行95577', '兴业银行95561', '中国移动客服10086', '中国联通客服10010', '中国电信客服10000', '中国移动IP号码17951', '中国联通IP号码17911', '中国电信IP号码17900', '电话及长途区号查询114', '供电局95598', '文化市场综合执法12318', '消费者申诉举报12315', '价格监督举报12358', '质量监督电话12365', '机构编制违规举报热线12310', '环保局监督电话12369', ' 民工维权热线电话 12333', '税务局通用电话12366......' 

结果太长,只显示其中一部分。

实例4:正则之爬取豆瓣评论

首先预热下,爬取豆瓣首页

导入urllib库下的request

 import urllib.request

使用下urlopen打开网站返回HTML

 urllib.request.urlopen("https://www.douban.com/")

读一下看看拿到了什么东西,使用read(),并给douban保存方便再GUI里调用

 douban = urllib.request.urlopen("https://www.douban.com/").read()

导入正则模块(re)

 import re

#豆瓣评论爬取小例子

 import urllib.request
 import re
 douban = urllib.request.urlopen("https://movie.douban.com/subject/27199913/?from=showing").read().decode("UTF-8")
 pat = '<span>.*?</span>'
 rst = re.compile(pat).findall(douban)
 print(rst[0])

引用decode(“UTF-8”)对其解码为utf-8

正则为

 <span>(.*?)</span>

如何写正则与本例的细节

打开网站

https://movie.douban.com/subject/27199913/?from=showing
右键源代码

发现评论格式为

就是山西版本的《两杆大烟枪》或者说《疯狂的石头》,将山西风光和特色与影片融合的很好,虽然还是有所瑕疵,也难逃一些俗套烂大街的剧情和段子,但是还可以。
所有评论被标签包裹,可以进行抓取,使用懒惰模式进行操作

.*?
"."全匹配,其细节为标签里有双引号,我们再写正则时使用单引号括起来,不要再使用双引号了

写循环把东西都显示出来

 for i in range(0,len(rst)):
     print(rst[i])

运行为

[‘一直很喜欢这种结构的电影,一圈圈地放出线团,再一段段收回,圆融又宿命。以山西籍为主的演员都挺走心的,生活的鸡飞狗走很是活灵活现,而且骆达华的出场太惊喜。导演说是改编太贵,索性自己写。但要是看过《提着心吊着胆》,会发现许多元素重合度很高,比如生意惨淡的饭店、存在问题的夫妇、警察、笨贼、拜金女、装大款……就连意外之财也是丢在饭店里。电影结尾顾虑较多,但整体完成度与幽默感不差。@平遥,特意买票支持,却来…’, ‘算是一部跟期待打平的作品吧,中规中矩,没有惊艳,王大治倒是真有那么几分长相之外的亮点。故事一圈圈闪回,重复的部分有点多,生怕观众跟不上这个倒叙+插叙的节奏似的。四川话河南话陕西话山西话一锅烩,泱泱大中华式的热闹。想要揭示人心叵测,但终归是浅了一点。【平遥电影节2018.10.19’, ‘这种结构挺好’, ‘审美疲劳,这种太多了,都大同小异。从每组人物的角度,反复讲同一个事。国产电影里的歹徒劫匪都是来搞笑的,都是傻逼,呵呵。’, ‘巧就巧在结构上。’]
一直很喜欢这种结构的电影,一圈圈地放出线团,再一段段收回,圆融又宿命。以山西籍为主的演员都挺走心的,生活的鸡飞狗走很是活灵活现,而且骆达华的出场太惊喜。导演说是改编太贵,索性自己写。但要是看过《提着心吊着胆》,会发现许多元素重合度很高,比如生意惨淡的饭店、存在问题的夫妇、警察、笨贼、拜金女、装大款……就连意外之财也是丢在饭店里。电影结尾顾虑较多,但整体完成度与幽默感不差。@平遥,特意买票支持,却来…
算是一部跟期待打平的作品吧,中规中矩,没有惊艳,王大治倒是真有那么几分长相之外的亮点。故事一圈圈闪回,重复的部分有点多,生怕观众跟不上这个倒叙+插叙的节奏似的。四川话河南话陕西话山西话一锅烩,泱泱大中华式的热闹。想要揭示人心叵测,但终归是浅了一点。【平遥电影节2018.10.19
这种结构挺好
审美疲劳,这种太多了,都大同小异。从每组人物的角度,反复讲同一个事。国产电影里的歹徒劫匪都是来搞笑的,都是傻逼,呵呵。
巧就巧在结构上。

保存到本地

 fh = open("G:\\python\\doubanpinglun.txt","w")#打开文件并新建doubanpinglun.txt
open里的路径为本地路径

完整代码如下

#豆瓣评论爬取小例子

 import urllib.request
 import re
 douban = urllib.request.urlopen("https://movie.douban.com/subject/27199913/?from=showing").read().decode("UTF-8")
 pat = '<span>(.*?)</span>'
 rst = re.compile(pat).findall(douban)
 print(rst)
 fh = open("G:\\python\\doubanpinglun.txt","w")#打开文件并新建doubanpinglun.txt
 for i in range(0,len(rst)):
     print(rst[i])
     fh.write(rst[i]+"\n")#写入文件
 fh.close()#关闭文件

效果:
在这里插入图片描述

总结

我们一起来总结一下。正则表达式实际上在多种语言里面都可以用,而且用法大同小异,掌握其中一门语言的用法后,其他语言只需要看一下文档就可以轻松驾驭。

如果抛开语言的不同,正则表达式的语法构成是完全一样的,都是由原子和元字符构成,通过各种匹配方式组合,形成灵活多变的匹配规则。

原子是匹配各种字段的基础构成,主要包括匹配普通字符串、匹配通用字符、原子表等;

元字符定义了一些特殊的含义,主要包括匹配原子次数设定、匹配开始结束、模式修正符等;

在 Python 中,针对正则表达式定义了一些特殊的方法,主要有 compile( ) 、match( ) 、search( ) 、sub( ) 、findall( ) 等方法,这些方法共同构成了 Python 对正则表达式各式各样的操作,非常简洁方便。

  • 3
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

Code进阶狼人

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值