常用模块:正则表达式之前戏、字符组、量词、特殊符号、贪婪与非贪婪匹配等,python正则模块之re

一、正则表达式前戏

正则表达式是一门独立的技术,适用于所有的编程语言,它的主要作用就是利用一些特殊符号进行查找,可以对一些庞大的数据进行筛选。

举例:

当我们在登陆京东的网站时,需要输入手机号进行登陆,如果我们没有输入正确的手机号格式,就会出现错误提示,这些功能就是用正则表达式来实现的,虽然我们在用python语言的时候,也可以实现一样的功能,但是正则表达式更加方便高效。

以下是纯python代码实现手机号校验:

基本需求:手机号必须是11位、手机号必须以13 15 17 18 19开头、必须是纯数字

'''纯python代码实现'''
while True:
    # 1.获取用户输入的手机号
    phone_num = input('请输入您的手机号>>>:').strip()
    # 2.先判断是否是十一位
    if len(phone_num) == 11:
        # 3.再判断是否是纯数字
        if phone_num.isdigit():
            # 4.判断手机号的开头
            if phone_num.startswith('13') or phone_num.startswith('15') or phone_num.startswith(
                    '17') or phone_num.startswith('18') or phone_num.startswith('19'):
                print('手机号码输入正确')
            else:
                print('手机号开头不对')
        else:
            print('手机号必须是纯数字')
    else:
        print('手机号必须是11位')
'''python结合正则实现'''
import re

phone_number = input('please input your phone number: ')
if re.match('^(13|14|15|18)[0-9]{9}$', phone_number):
    print('是合法的手机号码')
else:
    print('不是合法的手机号码')

二、正则表达式内容介绍

1、字符组

字符组是用于查找指定的字符,从左往右依次查找。通常来说,字符组中所有查找对象都是或的关系。

符号 说明
[0123456789] 匹配0到9任意一个数(全写)
[0-9] 匹配0到9任意一个数(缩写)
[a-z] 匹配26个小写英文字母
[A-Z] 匹配26个大写英文字母
[0-9a-zA-Z] 匹配数字或者小写字母或者大写字母

2、特殊符号

特殊符号默认匹配方式是挨个挨个匹配

符号 说明
. 匹配除换行符以外的任意字符
\w 匹配数字、字母、下划线
\W 匹配非数字、非字母、非下划线
\d 匹配数字
^ 匹配字符串的开头
$ 匹配字符串的结尾

^和$符号,两者组合使用可以非常精确的限制匹配的内容。

符号 名称
a|b 匹配a或者b(管道符的意思是或)
() 给正则表达式分组 不影响表达式的匹配功能
[] 字符组 内部填写的内容默认都是或的关系
[^] 取反操作 匹配除了字符组里面的其他所有字符

ps:注意上尖号在中括号内和中括号意思完全不同

3、量词

符号 说明
* 匹配零次或多次 默认是多次(无穷次)
+ 匹配一次或多次 默认是多次(无穷次)
? 匹配零次或一次 作为量词意义不大主要用于非贪婪匹配
重复n次
重复n次或更多次 默认是多次(无穷次)
重复n到m次 默认是m次
ps:量词必须结合表达式一起使用 不能单独出现 并且只影响左边第一个表达式
    jason\d{3} 只影响\d
这里的正则表达式的意思是查找到jason字符并且在后面需要有三个数字

注:正则表达式默认情况下都是贪婪匹配>>>:尽可能多的匹配

4、贪婪匹配与非贪婪匹配

贪婪匹配

上面我们提到了所有的量词都是贪婪匹配,会尽可能多的获取可以匹配的内容。

非贪婪匹配

默认情况下量词使用的是贪婪匹配,但是我们在量词后面加上问号就可以把他由贪婪匹配变成非贪婪匹配。这个时候就会变成尽可能少的获取匹配的内容

待匹配的文本
	<script>alert(123)</script>
待使用的正则(贪婪匹配)
	<.*>
结果如下:
	<script>alert(123)</script> 
待使用的正则(非贪婪匹配)
	<.*?>
结果如下:
	<script>
	</script>

5、转义符

当我们在正则表达式中使用\n这类内容的时候需要注意,这个时候我们是在查找换行符,当我们想要查找内容中的\n就需要在查找的表达式中使用\n来查找,如果是有两个撬棍的就需要使用四个撬棍来查找,以此类推。同时,如果我们是在python中使用的话,可以在字符串前面加上r来取消符号的特殊意义。

\n     	   匹配的是换行符
\\n			匹配的是文本\n
\\\\n		匹配的是文本\\n

6、正则表达式实战建议与一些例子

建议

虽然这个正则表达式很使用,但是这并不意味着我们需要很全面的了解他,相反我们只是需要了解就可以,做到看到表达式能知道意思就足够了。

1.编写校验用户身份证号的正则  ^[1-9]\d{13,16}[0-9x]$    ^[1-9]\d{14}(\d{2}[0-9x])?$    ^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ 2.编写校验邮箱的正则 3.编写校验用户手机号的正则(座机、移动) 4.编写校验用户qq号的正则

这些功能的正则表达式都已经在网上出现很多年了,虽然我们自己写的也能用,但我们会出现别人写的更好用的情况,因此我们也要学会前人栽树后人乘凉。

例子

1、. ^ $

正则 待匹配字符 匹配结果 说明
海. 海燕海娇海东 海燕海娇海东 匹配所有"海."的字符
^海. 海燕海娇海东 海燕 只从开头匹配"海."
海.$ 海燕海娇海东 海东 只匹配结尾的"海.$"

2、* + ?

正则 待匹配字符 匹配结果 说明
李.? 李杰和李莲英和李二棍子 李杰 李莲 李二 ?表示重复零次或一次,即只匹配"李"后面一个任意字符
李.* 李杰和李莲英和李二棍子 李杰和李莲英和李二棍子 *表示重复零次或多次,即匹配"李"后面0或多个任意字符
李.+ 李杰和李莲英和李二棍子 李杰和李莲英和李二棍子 +表示重复一次或多次,即只匹配"李"后面1个或多个任意字符
李. 李杰和李莲英和李二棍子 李杰和 李莲英 李二棍 {1,2}匹配1到2次任意字符

注意:前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配

正则 待匹配字符 匹配结果 说明
李.*? 李杰和李莲英和李二棍子 李 李 李 惰性匹配

3、字符集[][^]

正则 待匹配字符 匹配结果 说明
李[杰莲英二棍子]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子 表示匹配"李"字后面[杰莲英二棍子]的字符任意次
李[^和]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子 表示匹配一个不是"和"的字符任意次
[\d] 456bdha3 4 5 6 3 表示匹配任意一个数字,匹配到4个结果
[\d]+ 456bdha3 456 3 表示匹配任意个数字,匹配到2个结果

4、分组 ()与 或 |[^]

正则 待匹配字符 匹配结果 说明
[1]\d{13,16}[0-9x]$ 110101198001017032 110101198001017032 表示可以匹配一个正确的身份证号
[2]\d{13,16}[0-9x]$ 1101011980010170 1101011980010170 表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字
[3]\d{14}(\d{2}[0-9x])?$ 1101011980010170 False 现在不会匹配错误的身份证号了()表示分组,将\d{2}[0-9x]分成一组,就可以整体约束他们出现的次数为0-1次
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ 110105199812067023 110105199812067023 表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14}

5、转义符

正则 待匹配字符 匹配结果 说明
\n \n False 因为在正则表达式中\是有特殊意义的字符,所以要匹配\n本身,用表达式\n无法匹配
\n \n True 转义\之后变成\\,即可匹配
"\n" '\n' True 如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次
r'\n' r'\n' True 在字符串之前加r,让整个字符串不转义

6、贪婪匹配

img

几个常用的非贪婪匹配Pattern:

*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

三、re模块

简介

如果想要在python中使用正则表达式就可以导入re模块:

import re

常见操作方法:

1、.findall()

查找所有符合正则表达式要求的数据 结果直接是一个列表,第一个参数是查找对象,第二个参数是被查找的内容。

res = re.findall('a', 'jason apple eva')
print(res)
# ['a', 'a', 'a']

2、.finditer()

查找所有符合正则表达式要求的数据 结果直接是一个迭代器对象,第一个参数是查找对象,第二个参数是被查找的内容。

res = re.finditer('a', 'jason apple eva')
print(res)
# <callable_iterator object at 0x000001C249C45BB0>

这里就是相当于对这个可迭代对象进行了双下iter操作,把对象变成了迭代器对象,这时候我们可以用for或是双下next来获取内部内容,相比findall,在对大文件进行查找到的时候finditer使用的更多,不会占用过多的内存空间。

3、.search()

根据查找对象进行分割,查找对象会被删除。

第一个参数是查找对象,第二个参数是被查找的内容。

res = re.search('a', 'jason apple eva')
print(res)  
# <re.Match object; span=(1, 2),match='a'>

函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

print(res.group())  # a  匹配到一个符合条件的数据就立刻结束
# a

4、match()

匹配字符串的开头 如果不符合后面不用看了。

第一个参数是查找对象,第二个参数是被查找的内容。

res = re.match('a', 'jason apple eva')
print(res)  # None  匹配字符串的开头 如果不符合后面不用看了

5、compile()

当某一个正则表达式需要频繁使用的时候 我们可以做成模板

obj = re.compile('\d{3}')
res1 = obj.findall('23423422342342344')
res2 = obj.findall('asjdkasjdk32423')
print(res1, res2)
# ['234', '234', '223', '423', '423'] ['324']

6、split()

第一个参数是查找对象,第二个参数是被查找的内容。

ret = re.split('[ab]', 'abcd')
print(ret)
# ['', '', 'cd']

根据参数中的第一个字符把被查找内容分成''和'bcd',然后再根据查找对象中的b,分成'',''和'cd',数据值存在一个列表中。

7、sub()

进行替换操作,第一个参数是被替换的字符,第二个参数是需要替换进去的字符,第三个位置是被替换的字符串。

ret = re.sub('\d', 'H', 'eva3jason4yuan4', 1)  # 将数字替换成'H',参数1表示只替换1个
print(ret)  # evaHjason4yuan4

返回的结果是个字符串

8、subn()

跟sub一样是进行替换

ret = re.subn('\d', 'H', 'eva3jason4yuan4')  # 将数字替换成'H',返回的结果是元组(参数的内容分别是替换的结果,替换了多少次)
print(ret)  # ('evaHjasonHyuanH', 3)

返回的结果是个元组

9、re.S
匹配的时候忽略换行符(也能匹配换行符)
res = re.findall('a', 'jason apple eva', re.S)

10、re.I
匹配的时候忽略大小写
res = re.findall('a', 'jason apple eva', re.I)

四、re模块补充说明

1、分组优先

当我们使用括号跟管道符的时候会出现分组优先的顺序。

findall()

res = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com')
print(res)  # ['oldboy']
# findall分组优先展示:优先展示括号内正则表达式匹配到的内容
res = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com')
print(res)  # ['www.oldboy.com']

如果是默认情况下,会返回括号内匹配到的内容,如果在匹配对象前面加上?:就会返回完整的内容

search()

res = re.search('www.(baidu|oldboy).com', 'www.oldboy.com')
print(res.group())  # www.oldboy.com

match()

res = re.match('www.(baidu|oldboy).com', 'www.oldboy.com')
print(res.group())  # www.oldboy.com

使用match和search的时候需要使用group查看内容,得到的内容也是完整的结果

2、分组别名

有时候我们需要匹配的分组描述可能十分复杂,这时我们可以通过给分组取名来让我们更方便地获
取分组。
分组命名的规则为:(?P分组正则表达式)

在使用search查找的时候,得到的结果需要用group打印出来,但是这个时候我们可以根据索引的顺序,单独获得search语句中给的内容。

res = re.search('www.(?P<content>baidu|oldboy)(?P<hei>.com)', 'www.oldboy.com')
print(res.group())  # www.oldboy.com
print(res.group('content'))  # oldboy
print(res.group(0))  # www.oldboy.com
print(res.group(1))  # oldboy
print(res.group(2))  # .com
print(res.group('hei'))  # .com

五、网络爬虫简介

网络爬虫:通过编写代码模拟浏览器发送请求获取数据并按照自己指定的要求筛选出想要的数据

六、作业

思考红牛分公司数据爬取

import re
import os
# 整了网页的源代码这里是获取地址
now_path = os.getcwd()
redmer_path = os.path.join(now_path,'redmer.html')
# 这里是用地址获取内容
with open(redmer_path,'r',encoding='utf8') as f1:
    res = f1.read()

# 通过观察发现公司名称两头是<h2>包起来的,中间内容非贪婪获取的方式获得
res1 = re.findall('<h2>.*?</h2>',res)
print(res1)
# 这里我们发现每个公司的地址都是写在mapIco后面的,同理获取
res2 = re.findall('mapIco.*?</p>',res)
print(res2)
# 这里是邮编,可以在源代码中发现mailIco后面跟的是邮编
res3 = re.findall('mailIco.*?</p>',res)
print(res3)
# 这里是电话号码,在telIco后面是电话号码
res4 = re.findall('telIco.*?</p>',res)
print(res4)

# 如果想看格式化输出的话用split拆分一下然后for循环格式化输出就可以了



结果如下:
['<h2>红牛杭州分公司</h2>', '<h2>红牛广西分公司</h2>', '<h2>红牛广州分公司</h2>', '<h2>红牛深圳分公司</h2>', '<h2>红牛湖南分公司</h2>', '<h2>红牛福建分公司</h2>', '<h2>红牛东莞分公司</h2>', '<h2>红牛四川分公司</h2>', '<h2>红牛湖北分公司</h2>', '<h2>红牛云南分公司</h2>', '<h2>红牛贵州分公司</h2>', '<h2>红牛湛江分公司</h2>', '<h2>红牛南疆分公司</h2>', '<h2>红牛江西分公司</h2>', '<h2>红牛南京分公司</h2>', '<h2>红牛北京分公司</h2>', '<h2>红牛辽宁分公司</h2>', '<h2>红牛陕西分公司</h2>', '<h2>红牛苏北分公司</h2>', '<h2>红牛汕头分公司</h2>', '<h2>红牛重庆分公司</h2>', '<h2>红牛上海分公司</h2>', '<h2>红牛新疆分公司</h2>', '<h2>红牛安徽分公司</h2>', '<h2>红牛黑龙江分公司</h2>', '<h2>红牛河南分公司</h2>', '<h2>红牛山东分公司</h2>', '<h2>红牛甘肃分公司</h2>', '<h2>红牛海南分公司</h2>', '<h2>红牛青岛分公司</h2>', '<h2>红牛天津分公司</h2>', '<h2>红牛豫南分公司</h2>', '<h2>红牛山西分公司</h2>', '<h2>红牛吉林分公司</h2>', '<h2>红牛内蒙分公司</h2>', '<h2>红牛河北分公司</h2>', '<h2>红牛青海分公司</h2>', '<h2>红牛宁夏分公司</h2>', '<h2>红牛浙南代表处</h2>', '<h2>红牛金丽衢代表处</h2>']
["mapIco'>杭州市上城区庆春路29号远洋大厦11楼A座</p>", "mapIco'>南宁市金湖路59号地王国际商会中心50层D1、E1室</p>", "mapIco'>广东省广州市天河珠江新城华夏路10号富力中心写字楼1904房</p>", "mapIco'>广东省深圳市福田区福华三路88号财富大厦39楼BCD</p>", "mapIco'>湖南省长沙市天心区劳动西路289号嘉盛国际广场1626室</p>", "mapIco'>福建省福州市台江区广达路68号金源广场东区15楼BC</p>", "mapIco'>东莞市南城区石竹路3号广发金融大厦10楼01室</p>", "mapIco'>四川省成都市武侯区人民南路4段27号商鼎国际1栋1单元1201室</p>", "mapIco'>武汉市东西湖区金银湖路18号财富大厦13楼</p>", "mapIco'>云南省昆明市青年路389号志远大厦13楼A-2号</p>", "mapIco'>贵阳市云岩区北京路27号鑫都财富大厦26楼A座</p>", "mapIco'>湛江市霞山区人民大道南45号民大商贸大厦1103</p>", "mapIco'>新疆库尔勒市滨河路天赐花园B座2302室</p>", "mapIco'>江西省南昌市广场南路333号恒茂国际中心A座805室</p>", "mapIco'>江苏省南京市鼓楼区山西路8号金山大厦A座19楼1901室</p>", "mapIco'>北京市建国门外大街永安东里华彬国际大厦10层1007</p>", "mapIco'>沈阳市和平区和平北大街69号总统大厦C座2206</p>", "mapIco'>陕西省西安市锦业路1号都市之门C座11508室</p>", "mapIco'>盐城市盐都区华邦国际西厦A区2006、2007</p>", "mapIco'>广东省汕头市华山路碧霞庄中区1幢(民生银行大厦)407-408室</p>", "mapIco'>重庆市渝中区华盛路1号企业天地8号楼第20层6+7单元</p>", "mapIco'>上海市虹口区武进路360号壹丰广场1506室</p>", "mapIco'>新疆乌鲁木齐市北京南路439号中核发展大厦16楼</p>", "mapIco'>安徽省合肥市肥西路66号汇金大厦12B楼1203-1205室</p>", "mapIco'>黑龙江省哈尔滨市南岗区东大直街146号上和置和广场1号楼27层2711室</p>", "mapIco'>河南省郑州市中州大道1188号建业置地广场B座27层155号</p>", "mapIco'>山东省济南市经十路13777号中润世纪城5号楼902室</p>", "mapIco'>兰州市庆阳路105号澳兰名门B1座6楼</p>", "mapIco'>海南省海口市国贸大道2号海南时代广场10楼10D单元</p>", "mapIco'>山东省青岛市市北区延吉路76号中海大厦6号楼903室</p>", "mapIco'>天津市河西区越秀路38号银河大厦1004室</p>", "mapIco'>驻马店市乐山路与通达路交叉口爱家会展国际公寓5B1105</p>", "mapIco'>太原市杏花岭区肖墙路9号御花园假日广场写字楼B座11层8-16号</p>", "mapIco'>吉林省长春市朝阳区人民大街4111号兆丰国际大厦402室</p>", "mapIco'>呼和浩特市赛罕区呼伦南路119号东达城市广场写字楼2307室</p>", "mapIco'>河北省石家庄市建设南大街6号西美大厦1102室</p>", "mapIco'>青海省西宁市城中区西大街10号浩运大厦7楼703室</p>", "mapIco'>银川市金凤区北京中路51号瑞银财富中心C座11楼</p>", "mapIco'>温州市车站大道2号华盟商务广场1706-1707室</p>", "mapIco'>金华市八一北街739号东方国际财富双塔西塔24楼F室</p>"]
["mailIco'>310009</p>", "mailIco'>530021</p>", "mailIco'>510623</p>", "mailIco'>518048</p>", "mailIco'>410015</p>", "mailIco'>350005</p>", "mailIco'>523071</p>", "mailIco'>610041</p>", "mailIco'>430048</p>", "mailIco'>650021</p>", "mailIco'>550004</p>", "mailIco'>524000</p>", "mailIco'>841000</p>", "mailIco'>330002</p>", "mailIco'>210009</p>", "mailIco'>100022</p>", "mailIco'>110001</p>", "mailIco'>710065</p>", "mailIco'>224000</p>", "mailIco'>515041</p>", "mailIco'>400015</p>", "mailIco'>200080</p>", "mailIco'>830011</p>", "mailIco'>230001</p>", "mailIco'>150001</p>", "mailIco'>450000</p>", "mailIco'>250014</p>", "mailIco'>730000</p>", "mailIco'>570125</p>", "mailIco'>266000</p>", "mailIco'>300201</p>", "mailIco'>463000</p>", "mailIco'>030002</p>", "mailIco'>130000</p>", "mailIco'>010020</p>", "mailIco'>500000</p>", "mailIco'>810000</p>", "mailIco'>750001</p>", "mailIco'>325000</p>", "mailIco'>321000</p>"]
["telIco'>0571-87045279/7792</p>", "telIco'>0771-5592660/61/62</p>", "telIco'>020-38927681</p>", "telIco'>0755-23962001</p>", "telIco'>0731-88708080/8081</p>", "telIco'>0591-83362015</p>", "telIco'>0769-23184981</p>", "telIco'>028-85226292</p>", "telIco'>027-63370775/63370772</p>", "telIco'>0871-3100721</p>", "telIco'>0851—6814108</p>", "telIco'>0759-2837830/2837860</p>", "telIco'>0996-2106316</p>", "telIco'>0791-86497863/86665007</p>", "telIco'>025-83223948</p>", "telIco'>010-85288029-8010</p>", "telIco'>024-23267261/62/63</p>", "telIco'>029-88816813</p>", "telIco'>0515-88582102</p>", "telIco'>0754-88695688</p>", "telIco'>023-89031120/21</p>", "telIco'>021-61178178</p>", "telIco'>0991-4835894</p>", "telIco'>0551-2657611</p>", "telIco'>0451-53608457</p>", "telIco'>0371-65786101</p>", "telIco'>0531-88558011</p>", "telIco'>0931-8279900/0931-8276600 </p>", "telIco'>0898-68554263</p>", "telIco'>0532-66990781</p>", "telIco'>022-58376301/02</p>", "telIco'>0396-3336611</p>", "telIco'>0351-3343385</p>", "telIco'>0431-88658765/89819146/88405122</p>", "telIco'>0471-5254961/62/63</p>", "telIco'>0311-86966921</p>", "telIco'>0971-8276755</p>", "telIco'>0951-6890560</p>", "telIco'>0577-88557337</p>", "telIco'>0579-82515088</p>"]

posted @ 2022-10-25 19:12  wwwxxx123  阅读(69)  评论(0编辑  收藏  举报