使用正则表达式处理字符串,能更加高效和简介。 首先,需要导入模块re
import re
常用的元字符:
- . 匹配除 “\n” 和 “\r” 之外的任何单个字符。
- ^ 匹配字符串开始位置。
- $ 匹配字符串中结束的位置。
- * 前面的原子重复 0 次、1 次、多次。
- ? 前面的原子重复 0 次或者 1 次。
- + 前面的原子重复 1 次或多次。
- {n} 前面的原子出现了 n 次。
- {n,} 前面的原子至少出现 n 次。
- {n,m} 前面的原子出现次数介于 n-m 之间。
- ( ) 分组,输出需要的部分。
常用的通用字符:
- \s 匹配空白字符。
- \w 匹配任意字母/数字/下划线。
- \W 和小写 w 相反,匹配任意字母/数字/下划线以外的字符。
- \d 匹配十进制数字。
- \D 匹配除了十进制数以外的值。
- [0-9] 匹配一个 0~9 之间的数字。
- [a-z] 匹配小写英文字母。
- [A-Z] 匹配大写英文字母。
search 第一个匹配串
找出子串第一个匹配位置。
se_str = 'i love you but just love you'
se_pat = 'love'
se_re = re.search(se_pat, se_str)
print(se_re)
print(se_re.span())
输出结果:
<re.Match object; span=(2, 6), match='love'>
(2, 6)
match
match与search匹配字符串不同:
- match 在原字符串的开始位置匹配。
- search 在字符串的任意位置匹配。
m_str = 'i love you'
re_com = re.compile('you')
print(re_com.match(m_str))
print(re_com.search(m_str).span())
输出结果:
None
(7, 10)
如果字符串为’you are my sunshine’,'you are my destiny’等,就能使用match匹配到’you’了。
finditer 匹配迭代器
返回所有子串匹配位置的迭代器。
fdr_str = 'i love you but just love you'
fdr_pat = 'love'
fdr_r = re.finditer(fdr_pat, fdr_str)
for i in fdr_r:
print(i)
输出结果:
<re.Match object; span=(2, 6), match='love'>
<re.Match object; span=(20, 24), match='love'>
findall 所有匹配
findall 方法能查找出子串的所有匹配。
原字符串:
fda_str = '3号楼一单元1001,水费100.56'
目标是查找所有数字,所以需要使用正则表达式。
# r 去除转义字符
fda_pat = r'\d+'
fda_r = re.findall(fda_pat, fda_str)
for i in fda_r:
print(i)
输出结果:
3
1001
100
56
水费 100.56 ,这里却分开显示,去掉了小数点,所以没有完成我们想要的效果。
匹配浮点数和整数
.? 表示匹配小数点(.)0 次或 1 次
因此,正则表达式可以修改成: \d+.?\d*
注意,后面使用 *
而不是 +
,是因为 +
表示至少有一位数字。加上前面的 \d+
,表达式是需要匹配至少两位数。而 *
表示匹配前面字符 0 次、1 次或多次。
fda_str = '3号楼一单元1001,水费100.56'
fda_pat = r'\d+\.?\d*'
fda_r = re.findall(fda_pat, fda_str)
for i in fda_r:
print(i)
输出结果:
3
1001
100.56
匹配正整数
写出匹配所有正整数的正则表达式。
^[1-9]\d*$
fda_lst = [11, 3, 1.3, 100.54, -5, 67]
fda_pat = r'^[1-9]\d*$'
re_lst = [i for i in fda_lst if re.match(fda_pat, str(i))]
print(re_lst)
输出结果:
[11, 3, 67]
re.I 忽略大小写
re.I 是方法的可选参数,表示忽略大小写。
re_str = 'This is the Way'
pat = r't'
r = re.finditer(pat, re_str, re.I)
for i in r:
print(i.span())
输出结果:
(0, 1)
(8, 9)
split 分割单词
正则模块中 split 函数强大,能够处理复杂的字符串分割任务。
sp_str = 'This,;, is ; \t the || , Way ;'
words_lst = re.split(r'[,\s;|]+', sp_str)
print(words_lst)
输出结果:
['This', 'is', 'the', 'Way', '']
sub 替换匹配串
替换匹配到的子串。
sub_str = 'This 2233 is station B 2233'
pat = re.compile(r'\d+')
mat = pat.sub("23333",sub_str)
print(mat)
输出结果:
This 23333 is station B 23333
compile 预编译
如果要用同一匹配模式,做很多次匹配,可以使用 compile 预先编译串。
案例:从一系列字符串中,挑选出所有正浮点数。
首先,生成预编译对象 rec:
rec = re.compile(r'^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$')
下面直接使用 rec,匹配列表中的每个元素,不用每次都预编译正则表达式,效率更高。
test_lst = [11, 'love', 3.33, -2.1, '666', 345, '2.33', 0.5]
res_lst = [i for i in test_lst if rec.match(str(i))]
print(res_lst)
输出结果:
[3.33, '2.33', 0.5]
贪心捕获
根据某个模式串,匹配到结果。
待爬取网页的部分内容如下所示,现在想要提取 <div> 标签中的内容。
content = '''<span>233333</span><div>bilibili</div><div>隐秘的角落</div>'''
通过正则提取 div 中的内容
result = re.findall(r'<div>.*</div>', content)
print(result)
输出结果:
['<div>bilibili</div><div>隐秘的角落</div>']
如果我们不想保留字符串最开始的 <div> 和结尾的 </div>,那么,就需要使用一对 () 去捕获。
result = re.findall(r'<div>(.*)</div>', content)
print(result)
输出结果:
['bilibili</div><div>隐秘的角落']
- 结果中已经没有开始的
<div>
,结尾的</div>
仅使用一对括号,便成功捕获到我们想要的部分。 (.*)
表示捕获任意多个字符,尽可能多地匹配字符,也被称为贪心捕获
。.
表示匹配除换行符外的任意字符。
非贪心捕获
上面的返回结果是: [‘bilibili</div><div>隐秘的角落’]
,对于剩下的 div
标签,我们也是不需要的,所以正则表达式还需要改进。
result = re.findall(r'<div>(.*?)</div>', content)
仅把上面的正则增加了一个 ?
,匹配串为: (.*?)
输出结果:
['bilibili', '隐秘的角落']
像这种匹配模式串,被称为非贪心捕获。