浅谈正则表达式(中)
本节我们继续接着浅谈正则表达式(上)来讲正则表达式的其他使用。
零宽断言
断言:俗话的断言就是“我断定什么什么”,而正则中的断言,就是说正则可以指明在指定的内容的前面或后面会出现满足指定规则的内容,意思正则也可以像人类那样断定什么什么。
比如"ss1aa2bb3", 正则可以用断言找出 aa2 前面有 bb3,也可以找出 aa2 后面有 ss1.
零宽:就是没有宽度,在正则中,断言只是匹配位置,不占字符,也就是说,匹配结果里是不会返回断言本身。
我们来举个例子:
假设我们要用爬虫抓取 csdn 里的文章阅读量,通过查看源代码可以看到文章阅读量这个内容是这样的结构。
"<span class="read-count">阅读数:641</span>"
如果我们想知道不同文章的阅读量,变的是 641 这样的数字,641 旁边的东西是固定的,那么我们如何使用正则表达式匹配它呢?
下面先讲一下几种类型的断言:
正向先行断言(正前瞻)
语法:(?=pattern)
作用:匹配 pattern 表达式的前面内容,不返回本身。
回归刚才那个例子,要取到阅读量,在正则表达式中就意味着要能匹配到‘’前面的数字内容按照上所说的正向先行断言可以匹配表达式前面的内容,那意思就是:(?=) 就可以匹配到前面的内容了。
匹配什么内容呢?如果要所有内容那就是:
# -*- coding: utf-8 -*-
import re
test_str = "<span class=\"read-count\">阅读数:641</span>"
# start with "r" means raw string
# pattern = r".+(?=</span>)"
# 字符串变成正则表达式
pattern_str = ".+(?=</span>)"
pattern = re.compile(pattern_str)
result = re.search(pattern, test_str)
print(result)
print(result.group(0))
我们一起来看看 pattern_str = ".+(?=)",这个正则表达式吧:
根据我们在上一节学到的相关知识
. 匹配除换行符以外的任意字符
+ 重复一次或更多次,即至少出现一次
(?=pattern) 是匹配 pattern 前面的内容
在阅读量这个字符串里面,641 后面接着 ,故 pattern 为 。
所以这个正则表达式就为:
.+(?=</span>)
结果:
<re.Match object; span=(0, 32), match='<span class="read-count">阅读数:641'>
<span class="read-count">阅读数:641
显然,我们得到的是 前的所有内容。
如果我们要的只是前面的数字呢,那也简单,匹配数字 \d, 同时考虑到数字至少出现一次,/d 前加个 + ,可以改成:
# -*- coding: utf-8 -*-
import re
test_str = "<span class=\"read-count\">阅读数:641</span>"
# start with "r" means raw string
# pattern = r".+(?=</span>)"
# 字符串变成正则表达式
pattern_str = "\d+(?=</span>)"
pattern = re.compile(pattern_str)
result = re.search(pattern, test_str)
print(result)
print(result.group(0))
结果:
<re.Match object; span=(29, 32), match='641'>
641
是不是很有用呢!
正向后行断言(正后顾)
语法:(?<=pattern)
作用:匹配 pattern 表达式的后面的内容,不返回本身。
有先行就有后行,先行是匹配前面的内容,那后行就是匹配后面的内容啦。
上面的例子,我们也可以用后行断言来处理:
# -*- coding: utf-8 -*-
import re
test_str = "<span class=\"read-count\">阅读数:641</span>"
# start with "r" means raw string
# pattern = r".+(?=</span>)"
# 字符串变成正则表达式
pattern_str = "(?<=<span class=\"read-count\">阅读数:)\d+"
pattern = re.compile(pattern_str)
result = re.search(pattern, test_str)
print(result)
print(result.group(0))
从我们的待匹配字符串 "<span class="read-count">阅读数:641" 中,我们需要匹配 "<span class="read-count">阅读数:" 后面的内容,或者说后面的数字。
根据语法 (?<=pattern) ,pattern 就为 <span class="read-count">阅读数:
因此正则表达式为 "(?<=<span class="read-count">阅读数:)\d+"。
结果为:
<re.Match object; span=(29, 32), match='641'>
641
负向先行断言(负前瞻)
语法:(?!pattern)
作用:匹配非 pattern 表达式的前面内容,不返回本身。
有正向也有负向,负向在这里其实就是非的意思。
举个例子:比如有一句 "我爱祖国,我是祖国的花朵" 。
现在要找到不是 "的花朵" 前面的祖国
用正则就可以这样写:
祖国(?!的花朵)
负向后行断言(负后顾)
语法:(?<!pattern)
作用:匹配非 pattern 表达式的后面内容,不返回本身。
捕获和非捕获
捕获通常和分组联系在一起,也就是“捕获组”。
捕获组:匹配子表达式的内容,把匹配结果保存到内存中中数字编号或显示命名的组里,以深度优先进行编号,之后可以通过序号或名称来使用这些匹配结果。
而根据命名方式的不同,又可以分为两种组:
数字编号捕获组
语法:(exp)
解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第 0 组为整个表达式,第一组开始为分组。
比如固定电话的:020-85653333
他的正则表达式为:(0\d{2})-(\d{8})
按照左括号的顺序,这个表达式有如下分组:
序号 | 编号 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | 1 | (0\d{2}) | 020 |
2 | 2 | (\d{8}) | 85653333 |
不同语言会有一定的差异,这里以 python 为例,示例如下: |
# -*- coding: utf-8 -*-
import re
test_str = "020-85653333"
# start with "r" means raw string
# pattern = r".+(?=</span>)"
# 字符串变成正则表达式
pattern_str = "(0\d{2})-(\d{8})"
pattern = re.compile(pattern_str)
result = re.search(pattern, test_str)
print(result)
print("result 有 " + str(len(result.groups())) + " 个分组")
print(result.groups())
for i in range(len(result.groups())):
print(result.groups()[i])
结果如下:
<re.Match object; span=(0, 12), match='020-85653333'>
result 有 2 个分组
('020', '85653333')
020
85653333
命名编号捕获组
语法:(?
解释:分组的命名由表达式中的 name 指定
比如区号也可以这样写: (?
序号 | 编号 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | quhao | (0\d{2}) | 020 |
2 | haoma | (\d{8}) | 85653333 |
但值得注意的是,我在 python 中使用 "(? |
# -*- coding: utf-8 -*-
import re
test_str = "020-85653333"
# start with "r" means raw string
# pattern = r".+(?=</span>)"
# 字符串变成正则表达式
pattern_str = "(?P<quhao>0\d{2})-(?P<haoma>\d{8})"
pattern = re.compile(pattern_str)
result = re.search(pattern, test_str)
print(result)
print("result 有 " + str(len(result.groups())) + " 个分组")
print(result.groups())
group_list = ['quhao', 'haoma']
for i in range(len(result.groups())):
print("分组名称为: " + group_list[i] + " ; 匹配内容为" + result.group(group_list[i]))
稍作改进的正则表达式如下:
(?P<quhao>0\d{2})-(?P<haoma>\d{8})
在 python 中是这样的,其他语言可能正常使用。
示例结果如下:
<re.Match object; span=(0, 12), match='020-85653333'>
result 有 2 个分组
('020', '85653333')
分组名称为: quhao ; 匹配内容为020
分组名称为: haoma ; 匹配内容为85653333
非捕获组
语法:(?:exp)
解释:和捕获组刚好相反,它用来标识那些不需要捕获的分组,说的通俗一点,就是你可以根据需要去保存你的分组。
比如上面的正则表达式,程序不需要用到第一个分组,那就可以这样写:
(?:\0\d{2})-(\d{8})
分组如下:
序号 | 编号 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | 1 | (\d{8}) | 85653333 |
示例如下: |
# -*- coding: utf-8 -*-
import re
test_str = "020-85653333"
# start with "r" means raw string
# pattern = r".+(?=</span>)"
# 字符串变成正则表达式
pattern_str = "(?:0\d{2})-(\d{8})"
pattern = re.compile(pattern_str)
result = re.search(pattern, test_str)
print(result)
print("result 有 " + str(len(result.groups())) + " 个分组")
print(result.groups())
for i in range(len(result.groups())):
print(result.groups()[i])
结果如下:
<re.Match object; span=(0, 12), match='020-85653333'>
result 有 1 个分组
('85653333',)
85653333
本节内容我们介绍了零宽断言以及捕获和非捕获,之后我们讲继续介绍正则表达式相关的其他内容。
本文参考了:
- 微信公众号(前端之巅): 正则表达式真的很6,可惜你不会写。