Skip to content

正则表达式

正则表达式具有伟大技术发明的一切特点,它简单、优美、功能强大、妙用无穷。对于很多实际工作来讲,正则表达式简直是灵丹妙药,能够成百倍地提高开发效率和程序质量。

正则表达式很有用,但如果不是程序员,很少会有人了解它,尽管大多数现代文本编辑器和文字处理器,都有查找和查找替换功能,可以根据正则表达式查找。正则表达式可以节约大量时间,不仅适用于软件用户,也适用于程序员。

场景:找所有邮件地址

python
text = """
哥哥口袋有糖
初识物联1
346504108@qq.com

儒雅的刘飞3
初识物联1
397872410@qq.com,谢谢楼主

该来的总会来
物联博士5
1459543548@qq.com

谢谢谢谢
pyxxponly@qq.com

BLACKPINK_罗捷
深入物联2
1228074244@qq.com
"""

为了图简单,当然也可以手动进行复制,一旦内容很多就比较麻烦了。这时候我们就可以使用正则表达式进行快速处理。

346504108@qq.com
397872410@qq.com
1459543548@qq.com
pyxxponly@qq.com
1228074244@qq.com

元字符

使用元字符匹配单个字符

字符功能
\d匹配数字,即 0-9
\D匹配非数字,即不是数字
\s匹配空白,即空格、tab 键等空白字符
\S匹配非空白
\w匹配单词字符,即 a-z、A-Z、0-9、_
\W匹配非单词字符
.匹配任意 1 个字符(除了\n)
*匹配前一个字符出现 0 次或者无限次,即可有可无
+匹配前一个字符出现 1 次或者无限次,即至少有 1 次
[ ]匹配[ ]中列举的字符

匹配数字

python
import re

res = re.findall("\d", '346504108@qq.com')
print(res)

运行结果:

['3', '4', '6', '5', '0', '4', '1', '0', '8']

匹配多个字符

python
import re

res = re.findall("\d+", '346504108@qq.com')
print(res)

运行结果:

['346504108']

.* 匹配任意多个字符

思考:匹配 HelloDemo 之间的内容

python
import re

content = 'Hello World This is a Regex Demo'
result = re.findall('Hel.* Regex Demo', content)
print(result)

字符集

可能会出现的一些情况

[123456zxcv] 字符集只能匹配一个出现在集合里面的值

\d 代表 0-9 的所有数字

[0123456789]\d 等效

python
import re

res = re.findall("\d+", '346504108@qq.com')
print(res)

res1 = re.findall("[0123456789]+", '346504108@qq.com')
print(res1)

运行结果:

['346504108']
['346504108']

思考:邮箱可能出现为字符串,该如何处理?

例如:yanglong985@163.com

提示: [a-z]

案例:匹配手机号

匹配中国电信手机号码

  • 中国电信号段 133. 153. 180. 189
  • 号码总长度为11位

实现方式:

  1. 编写电信号码的正则
  2. 进行匹配
  3. 打印结果
python
import re

str_phone = """13357024777
电信 浙江省 衢州 尾数AAA 号码吉凶
18948121234 
电信 广东省 茂名 尾数ABCD 号码吉凶
13873179698 
移动 湖南省 长沙 个性靓号 号码吉凶
15802648889 
移动 湖南省 长沙 尾数AAAB号码吉凶
"""

# 第一位都是以1开头 第二位可以为34578 第三位没有6与9 后面都是数字
res = re.findall('1[358][039]\d+', str_phone)
print(res)

数量词

使用数量词匹配多个字符

字符功能
{m}匹配前一个字符出现m次
{m,n}匹配前一个字符出现从m到n次

需求:匹配出,8 到 20 位的密码,可以是大小写英文字母、数字、下划线

python
# coding=utf-8
import re

res = re.findall("[a-zA-Z0-9]{8}", "ash2e223 3424kjkljkljf 34523nmkdsjf")
print(res)

ret = re.findall("[a-zA-Z0-9_]{8,20}", "ash2e223 3424kjkljkljf 34523nmkdsadsjf")
print(ret)

案例:QQ 号码匹配

  • QQ 号规则
    1. 第一位数字不能为0
    2. 可能是 5-12 位
python
import re

qq_str = """
346504108@qq.com
Super劫Zed: 540775360@qq.com
397872410@qq.com,谢谢楼主
1459543548@qq.com
"""

ret = re.findall("[1-9][0-9]{4,11}", qq_str)
print(ret)

运行结果:

['346504108', '540775360', '397872410', '1459543548']

精确匹配与泛匹配

泛匹配

泛匹配是匹配所有的东西

python
import re

content = 'Hello World This is a Regex Demo'

result = re.findall('Hello.*Demo', content)
print(result)

精确匹配

精确匹配是匹配括号里面的东西

python
import re

content = 'Hello World This is a Regex Demo'
result = re.findall('Hello (\d+).*Demo', content)
print(result)

贪婪匹配与非贪婪匹配

Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;

非贪婪则相反,总是尝试匹配尽可能少的字符。

"*","?","+","{m,n}"后面加上 ,使贪婪变成非贪婪。

python
import re

content = 'Hello World This is a Regex Demo. This is a Regex Demo'
result = re.findall('Hel.*(\d+) Regex Demo', content)
print(result)

解决方式:非贪婪操作符“?”,这个操作符可以用在"*","+","?"的后面,要求正则匹配的越少越好。

python
result2 = re.findall('Hel.*(\d+)? Regex Demo', content)
print(result2)

案例:匹配文字数据

python
"""
1. 提取 a 标签里面的电影名字信息
2. 提取 title 后面的文字信息
"""

html = """
<a href="/films/488" title="机器人总动员" data-act="boarditem-click" data-val="{movieId:488}">机器人总动员</a>
<a href="/films/78312" title="时空恋旅人" data-act="boarditem-click" data-val="{movieId:78312}">时空恋旅人</a>
<a href="/films/491" title="神偷奶爸" data-act="boarditem-click" data-val="{movieId:491}">神偷奶爸</a>
"""
参考答案
python
import re

html = """
<a href="/films/488" title="机器人总动员" data-act="boarditem-click" data-val="{movieId:488}">机器人总动员</a>
<a href="/films/78312" title="时空恋旅人" data-act="boarditem-click" data-val="{movieId:78312}">时空恋旅人</a>
<a href="/films/491" title="神偷奶爸" data-act="boarditem-click" data-val="{movieId:491}">神偷奶爸</a>
"""

# re.S 最好默认就加上,因为有些匹配的内容会进行换行
# print(re.findall('<a .*>(.*)</a>', html, re.S))
# print(re.findall('<a .*?>(.*)</a>', html, re.S))
print(re.findall('<a .*?>(.*?)</a>', html, re.S))

print(re.findall("title='(.*?)'", html, re.S))
# 内容里面是双引号
print(re.findall('title="(.*?)"', html, re.S))

匹配开头结尾

字符功能
^匹配字符串开头
$匹配字符串结尾

末尾匹配

需求:匹配 163.com 的邮箱地址

python
import re

email_list = ["xiaoWang@163.com", "xiaoWang@163.comheihei", ".com.xiaowang@qq.com"]

for email in email_list:
    ret = re.match("[\w]{4,20}@163\.com", email)
    if ret:
        print("%s 是符合规定的邮件地址,匹配后的结果是:%s" % (email, ret.group()))
    else:
        print("%s 不符合要求" % email)

运行结果:

['xiaoWang@163.com']
[]
[]

完善后

python
email_list = ["xiaoWang@163.com", "xiaoWang@163.comheihei", ".com.xiaowang@qq.com"]

for email in email_list:
    ret = re.match("[\w]{4,20}@163\.com$", email)
    if ret:
        print("%s 是符合规定的邮件地址,匹配后的结果是:%s" % (email, ret.group()))
    else:
        print("%s 不符合要求" % email)

运行结果:

xiaoWang@163.com 是符合规定的邮件地址,匹配后的结果是:xiaoWang@163.com
xiaoWang@163.comheihei 不符合要求
.com.xiaowang@qq.com 不符合要求

案例-匹配品牌号

匹配每一条数据的品牌 中文、英文需要进行区分为要进行区分

提示

[\u4e00-\u9fa5] 可以匹配中文

python
import re

csv_str = """凯迪拉克ATS-L 2016款 28T 时尚型,2016年,2.5万公里,长沙,16.77万,34.60万
奥迪A6L 2014款 TFSI 标准型,2014年,13.8万公里,长沙,21.96万,44.50万
本田 思域 2016款 1.8L 自动舒适版,2016年,4.8万公里,长沙,8.87万,15.20万
大众 朗逸 2015款 1.6L 自动舒适版,2016年,10.5万公里,长沙,7.27万,14.90万
凯迪拉克ATS-L 2016款 28T 时尚型,2016年,2.5万公里,长沙,16.77万,34.60万
奥迪A6L 2014款 TFSI 标准型,2014年,13.8万公里,长沙,21.96万,44.50万
本田 思域 2016款 1.8L 自动舒适版,2016年,4.8万公里,长沙,8.87万,15.20万
大众 朗逸 2015款 1.6L 自动舒适版,2016年,10.5万公里,长沙,7.27万,14.90万
凯迪拉克ATS-L 2016款 28T 时尚型,2016年,2.5万公里,长沙,16.77万,34.60万
奥迪A6L 2014款 TFSI 标准型,2014年,13.8万公里,长沙,21.96万,44.50万
本田 思域 2016款 1.8L 自动舒适版,2016年,4.8万公里,长沙,8.87万,15.20万
大众 朗逸 2015款 1.6L 自动舒适版,2016年,10.5万公里,长沙,7.27万,14.90万
别克 君威 2014款 GS 2.0T 燃情运动版,2015年,3.0万公里,长沙,补贴后11.97万,
Smart smart fortwo 2012款 1.0 MHD 硬顶标准版,2014年,5.6万公里,长沙,4.89万,12.50万"""

参考答案

python
# 首尾匹配是针对一串字符串
for line in csv_str.split('\n'):
    r = re.findall('^[\u4e00-\u9fa5]+', line)
    # 如果中文品牌有内容,直接打印
    if r:
        print(r, line)
    else:
        r = re.findall('^[a-zA-Z]+', line)
        print(r, line)

r 的作用

python
>>> mm = "c:\\a\\b\\c"
>>> mm
'c:\\a\\b\\c'
>>> import re
>>> re.findall("c:\\\\",mm)
['c:\\']
>>> re.findall(r"c:\\",mm)
['c:\\']

Python 中字符串前面加上 r 表示原始字符串, 与大多数编程语言相同,正则表达式里使用 \ 作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符 \ ,那么使用编程语言表示的正则表达式里将需要 4 个反斜杠 \\ :前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。

Python 里的原生字符串很好地解决了这个问题,有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

匹配分组

字符功能
|匹配左右任意一个表达式
(a|b)将括号中字符作为一个分组

示例1:

需求:匹配出所有的 gmail 与 163 邮箱

python
import re

content = """
346504108@gmail.com
540775360@gmail.com
123123360@gmail.com
675454350@163.com
234234360@163.com
234237760@163.com
onlyasd40@163.com
23443f548@163.com
354573548@qq.com
345436548@qq.com
pyxxponly@qq.com
122874244@qq.com
"""
# 分组匹配
print(re.findall('[0-9a-zA-Z]+@163.com|[0-9a-zA-Z]+@gmail.com', content))
# 精确匹配
print(re.findall('[0-9a-zA-Z]+@(163.com|gmail.com)', content))
# 第二种分组匹配
print(re.findall('[0-9a-zA-Z]+@(?:163.com|gmail.com)', content))