正则表达式
常用的匹配规则
匹配规则 |
描述 |
示例 |
. |
匹配除换行符以外的任何单个字符 |
a.b 匹配 aab, a0b, a*b |
^ |
匹配字符串的开始 |
^abc 匹配 abcdef 中的 abc |
$ |
匹配字符串的结尾 |
abc$ 匹配 123abc 中的 abc |
* |
匹配前面的字符 0 次或多次 |
ab*c 匹配 ac, abc, abbc |
+ |
匹配前面的字符 1 次或多次 |
ab+c 匹配 abc, abbc |
? |
匹配前面的字符 0 次或 1 次 |
ab?c 匹配 ac, abc |
{n} |
匹配前面的字符恰好 n 次 |
a{3} 匹配 aaa |
{n,} |
匹配前面的字符至少 n 次 |
a{3,} 匹配 aaa, aaaa, aaaaa |
{n,m} |
匹配前面的字符至少 n 次,至多 m 次 |
a{3,5} 匹配 aaa, aaaa, aaaaa |
[abc] |
匹配方括号内的任意一个字符 |
[abc] 匹配 a, b, c |
[a-z] |
匹配小写字母范围内的任意一个字符 |
[a-z] 匹配 a 到 z |
[A-Z] |
匹配大写字母范围内的任意一个字符 |
[A-Z] 匹配 A 到 Z |
[0-9] |
匹配数字范围内的任意一个字符 |
[0-9] 匹配 0 到 9 |
[^abc] |
匹配不在方括号内的任意一个字符 |
[^abc] 匹配 d, e, f 等 |
\d |
匹配任何数字,相当于 [0-9] |
\d 匹配 0 到 9 |
\D |
匹配任何非数字字符,相当于 [^0-9] |
\D 匹配 a, b, c 等 |
\w |
匹配任何字母数字字符,相当于 [a-zA-Z0-9_] |
\w 匹配 a 到 z, A 到 Z, 0 到 9 和 _ |
\W |
匹配任何非字母数字字符,相当于 [^a-zA-Z0-9_] |
\W 匹配 @, #, 空格 等 |
\s |
匹配任何空白字符,相当于 [ \t\n\r\f\v] |
\s 匹配 空格, \t, \n 等 |
\S |
匹配任何非空白字符,相当于 [^ \t\n\r\f\v] |
\S 匹配 a, 1, @ 等 |
\b |
匹配一个单词边界 |
\bword\b 匹配 word |
\B |
匹配非单词边界 |
\Bword\B 匹配 password 中的 word |
(...) |
匹配括号内的表达式,并捕获该匹配的子字符串 |
(abc) 匹配并捕获 abc |
(?:...) |
匹配括号内的表达式,但不捕获该匹配的子字符串 |
(?:abc) 匹配 abc 但不捕获 |
(?P<name>...) |
匹配括号内的表达式,并捕获该匹配的子字符串,赋予组名 name |
(?P<word>\w+) 捕获一个单词,并命名为 word |
\num |
引用编号为 num 的捕获组 |
\1 引用第一个捕获组 |
(?P=name) |
引用名称为 name 的捕获组 |
(?P=word) 引用名为 word 的捕获组 |
*? |
匹配前面的字符 0 次或多次,非贪婪 |
.*? 匹配最少字符 |
+? |
匹配前面的字符 1 次或多次,非贪婪 |
.+? 匹配最少字符 |
?? |
匹配前面的字符 0 次或 1 次,非贪婪 |
a?? 在 a 中匹配 a |
{n,m}? |
匹配前面的字符至少 n 次,至多 m 次,非贪婪 |
a{2,5}? 在 aaaaaa 中匹配 aa |
(?=...) |
正向肯定断言,匹配当前位置后面的内容 |
\d(?=px) 匹配 3px 中的 3 |
(?!...) |
正向否定断言,匹配当前位置后面不出现的内容 |
\d(?!px) 匹配 3em 中的 3 |
(?<=...) |
反向肯定断言,匹配当前位置前面的内容 |
(?<=\$)\d+ 匹配 $100 中的 100 |
(?<!...) |
反向否定断言,匹配当前位置前面不出现的内容 |
(?<!\$)\d+ 匹配 100 中的 100 |
match
常用的匹配方法,传入要匹配的字符串以及正则表达式,可以检测这个正则表达式和字符串是否相匹配
会尝试从字符串的起始位置开始匹配正则表达式,如果匹配返回匹配成功的结果,若不匹配返回None
import re content = 'Hello 123 4567 World_This is a Regex Demo' print(len(content)) # ^匹配一行字符串的开头 # \s匹配任意空白字符 # \d匹配任意数字 # \w匹配字母数字加下划线 result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}', content) print(result) print(result.group()) print(result.span())
匹配目标
若想字符串中提取一部分内容,可以使用()将想提取的子字符串括起来。()实际上标记了一个子表达式的开头和结束位置,被标记的每个子表达式一次对应每个分组,调用group方法传入分组的索引即可获取提取的结果。
import re content = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^Hello\s(\d+)\sWorld', content) print(result) print(result.group()) print(result.group(1)) print(result.span())
若出现AttributeError: 'NoneType' object has no attribute 'group' 表示没有匹配到符合正则表达式的内容,但又调用了group方法。
通用匹配
.可以匹配任意字符(除换行符),*代表匹配前面的字符无限次,组合在一起就可以匹配任意字符。
import re content = 'Hello 123 4567 World_This is a Regex Demo' result = re.match('^Hello.*Demo$', content) print(result) print(result.group()) print(result.span())
贪婪与非贪婪
贪婪匹配
import re content = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^He.*(\d+).*Demo$', content) print(result) print(result.group(1))
在贪婪模式下,.*会匹配尽可能多的字符。正则表达式中.*后面是(\d+),也就是至少一个数字,而且没有指定具体几个数字,所以第一个.*会匹配尽可能多的字符,把123456全都匹配了,只给\d+留下一个7
非贪婪匹配
import re content = 'Hello 1234567 World_This is a Regex Demo' result = re.match('^He.*?(\d+).*Demo$', content) print(result) print(result.group(1))
.*?非贪婪匹配匹配尽量少的字符,当 .*?匹配打后面的空白字符再往后及时数字,而\d+恰好可以匹配,所以结果为1234567
import re content = 'http://weibo.com/comment/sdJHJfh' result1 = re.match('http.*?comment/(.*?)', content) result2 = re.match('http.*?comment/(.*)', content) print('result1', result1.group(1)) print('result2', result2.group(1))
在做匹配的时候,字符串中间尽量使用非贪婪匹配以避免出现匹配结果却是的情况。
如果匹配的解雇哦在字符串结尾,.*?有可能匹配不到任何内容
修饰符
在正则表达式中可以用一些标志修饰符来控制匹配的模式。
正则表达式没有匹配到这个字符返回AttributeError: 'NoneType' object has no attribute 'group'
因为匹配的内容是除换行符之外的任意字符,当遇到换行符时,.*?就不能匹配了,所以导致匹配失败。
这里加一个修饰符re.S即可修饰这个错误
result = re.match('^He.*?(\d+).*?Demo$', content, re.S)
常用修饰符列表
修饰符 | 完整名称 | 解释 |
---|---|---|
re.IGNORECASE |
re.I |
使匹配对大小写不敏感 |
re.MULTILINE |
re.M |
使开始和结束字符(^ 和 $ )分别匹配每一行的开始和结束,而不是整个字符串的开始和结束 |
re.DOTALL |
re.S |
使点号(. )匹配任何字符,包括换行符 |
re.VERBOSE |
re.X |
允许正则表达式包含空白和注释,以提高可读性 |
re.ASCII |
re.A |
使 \w 、\W 、\b 、\B 、\s 、\S 、\d 、\D 等只匹配ASCII字符,而不是Unicode字符(Python 3.7+ 中移除) |
re.UNICODE |
re.U |
使 \w 、\W 、\b 、\B 、\s 、\S 、\d 、\D 等匹配Unicode字符(在Python 3.3+ 中是默认行为,Python 3.7+ 中移除) |
re.LOCALE |
re.L |
使预定义字符类(如 \w 、\W 、\b 、\B 等)的行为取决于当前区域设置(不推荐使用,因为这会使得正则表达式的行为依赖于环境) |
转义匹配
import re content = '(百度)www.baidu.com' result = re.match('\(百度\)www\.baidu\.com', content) print(result)
search
match方法从字符串的开头开始匹配,一旦不从开头匹配,匹配就会失效。
import re content = 'Extra strings Hello 1234567 World_This is a Regex Demo Extra stings' result = re.match('Hello.*?(\d+).*?Deom', content) print(result)
正则表达式以Hello开头,字符串以Extra开头,所以匹配失败。
import re content = 'Extra strings Hello 1234567 World_This is a Regex Demo Extra stings' result = re.search('Hello.*?(\d+).*?Demo', content) print(result)
使用search扫整个字符串,返回第一个匹配成功的结果。
这是一段待匹配HTML开头
html = '''<div id="songs-list"> <h2 class="title"> 经典老歌 </h2> <p class="introduction"> 经典老歌列表 </p> <ul id="list" class="list-group"> <li data-view="2"> 一路上有你 </li> <li data-view="7"> <a href="/2.mp3" singer="任贤齐"> 沧海一声笑 </a> </li> <li data-view="4" class="active"> <a href="/3.mp3" singer="齐秦"> 往事随风 </a> </li> <li data-view="6"><a href="/4.mp3" singer="beyond"> 光辉岁月 </a></li> <li data-view="5"><a href="/5.mp3" singer="陈慧琳"> 记事本 </a></li> <li data-view="5"> <a href="/6.mp3" singer="邓丽君"> 但愿人长久 </a> </li> </ul>
尝试提取第三个li里面的歌手名和歌名,正则表达式写法:
<li.*?active.*?singer="(.*?)">(.*?)</a>
若正则表达式不带active则会匹配第一个符合条件的目标,从字符串开头开始搜索,此时符合条件的节点就变成了第二个li节点,后面的就不在匹配
result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html, re.S) if result: print(result.group(1), result.group(2))
在上面的两次匹配中search方法的第三个参数都添加了re.S,这使得.*?可以匹配换行,如果去掉则会匹配
不包含换行符的第四个li
findall
findall可以获取与正则表达式相匹配的所有字符串
results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S) print(results)
sub
除了使用正则表达式提取信息外,有时候还需要借助它来修改文本。比如,想要把一串文本中的所有数字都去掉,如果只用字符串的 replace 方法太烦琐,可以借助 sub 方法
import re content = 'a5s4d531fc3a5sd4sa35d4' content = re.sub('\d+', '', content) print(content)
第一个参数传入 \d+ 来匹配所有的数字,第二个参数为替换成的字符串(如果去掉该参数的话,可以赋值为空),第三个参数是原字符串。
上面的HTML文本中,获取所有li节点的歌名,可以如以下写法:
# 用 sub 方法将 a 节点去掉,只留下文本,然后再利用 findall 提取 html = re.sub('<a.*?>|</a>', '', html) print(html) results = re.findall('<li.*?>(.*?)</li>', html, re.S) for result in results: print(result.strip())
compile
可以将正则字符串编译成表达式对象,以便在后面的匹配中使用。 3 个日期,分别将 3 个日期中的时间去掉,可以借助 sub 方法。第一个参数是正则表达式,这里没有必要重复写 3 个同样的正则表达式,可以借助 compile 方法将正则表达式编译成一个正则表达式对象,以便以后使用
import re content1 = '2024-1-28 23:23' content2 = '2024-1-22 23:21' content3 = '2024-1-23 23:25' pattern = re.compile('\d{2}:\d{2}') result1 = re.sub(pattern, '', content1) result2 = re.sub(pattern, '', content2) result3 = re.sub(pattern, '', content3) print(result1, result2, result3)
compile 还可以传入修饰符,例如 re.S 等修饰符,这样在 search、findall 等方法中就不需要额外传了。所以,compile 方法可以说是给正则表达式做了一层封装,以便更好地复用。