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))