写在前面:木匠最近在忙于探索如何从文档中提取信息,所以学习了正则表达式。写下这篇笔记用于加深印象,也方便日后回顾。
阅读本文你可以了解:
- 什么是正则表达式?
- 正则表达式的基本语法有哪些?
- 如何在python中使用正则表达式
- 元字符的更多应用
什么是正则表达式
该章节内容对应:1.什么是正则表达式
简单来说,正则表达式是用于匹配字符串的模式。比如,有一段文本“电话:+86-123456789”,你想获得其中“-”之后的数字,此时你可以编写这样的正则表达式(或者叫模式)电话:\+86\-(\d+)
。该表达式的功能是匹配“电话:+86-”并将“-”之后的内容捕获。
具体来说,表达式中的“电话:+86-”被称作字面量(Literal),字面量直接匹配字符串中的相同文本;\
、+
和(
)
被称作元字符(Metacharacters),\
后可以跟各种字符来表示各种特殊序列(如\d),它还用于转义元字符,以便可以在表达式中匹配元字符本身(如\+),+
为量词(Quantifier)表示重复一次或多次,(
)
用来表示分组,分组也称为捕获组(Capture group),用于提取信息。通过该例子可知,将特殊序列(或字面量)与量词组合就可以表示重复某种字符,所以\d+
就表示一个或多个数字,将其放在括号中就成为一个分组(或捕获组),通过编写程序返回捕获组的内容即可获得不带区号的号码(对于本例,为123456789)。可将元字符理解为正则表达式的单词,通过合理使用它们可以形成任意的正则表达式(或模式)。本例的元字符是常见元字符的一部分,我会在下文更详细的介绍。网络上有很多正则表达式的概念介绍,你若有兴趣可以自行查询。
正则表达式的基本用法
该章节内容对应:2.正则表达式的语法有哪些?3.如何在python中使用正则表达式
表1 常见的元字符自查表(解释并非该语法的全部信息)
语法 |
解释 |
. |
匹配除换行符(\n)以外的任意字符 |
^ |
匹配字符串的开头,可与$搭配 |
$ |
匹配字符串的结尾,可与^搭配 |
* |
量词,重复零次或多次(多次当然包含一次) |
+ |
量词,重复一次或多次 |
? |
量词,表示字符的可选性,匹配0个或1个前一个字符,如’ab?c’可以匹配’abc’,也可匹配’ac’,因为前一个字符’b’是可选的 |
{m} |
指定前一个字符重复m次,如a{3}表示a重复3次 |
{m,n} |
指定前一个字符的重复范围在m到n之间,如a{1,3}表示a至少重复1次,至多重复3次 |
[] |
字符组。匹配特定字符,如[abc]表示只匹配a、b或c中的一个 |
\ |
表示转义,或与特定字母组成特殊序列 |
| |
表示逻辑或(OR),如'(abc|abd)’读作要么匹配abc要么匹配abd |
() |
表示分组或捕获组,在括号内填写模式以提取信息 |
表2 常见的特殊序列自查表
语法 |
解释 |
\d |
0-9的任意数字 |
\D |
除数字之外的任意字符 |
\w |
任意字母数字字符(字母数字是英文字母与数字的组合),常在英文字符串中使用 |
\W |
任意非字母数字字符,常在英文字符串中使用 |
\s |
空白字符(制表符、空格) |
\S |
非空白字符 |
表1和表2只列出了木匠项目里用到的语法,如果你需要其他语法可以查询完整的表格,肯定会有符合你需求的语法。
在python中使用正则表达式需要导入re
包。具体的库函数可以查看参考链接1。
重要提醒:对于英文字符串,正则表达式匹配的单位是一个字母,对于中文字符串则是一个汉字。
.
.
表示任意字符(除换行符),如果想匹配换行符,只需传入re.DOTALL
标志位就可以实现跨行匹配。它就像小丑牌(赖子)可以充当任何牌!一个.
可以代表任何一个字母或汉字。如果要匹配.
本身,加上\
转义符就行,\.
用来匹配.
。
练习1 编写正则表达式回答:我喜欢谁?(直接print答案)
text = '我喜欢你~'
解答1
import re
text = '我喜欢你~'
pattern = r'我喜欢(.)~'
match = re.search(pattern, text)
if match:
print(match.group(1))
*、+
*
的正式名称叫做Kleene星号( Kleene Star),意味着“零次或多次重复”,+
叫做Kleene加号( Kleene Plus),与其他元字符或特殊序列组合可以指定元字符或特殊序列的重复次数。如果要匹配*
、+
本身,加上\
转义符就行。像*
和+
这样的重复是贪婪的,匹配引擎将尝试重复尽可能多的次数。 如果表达式的后续部分不匹配,则匹配引擎将回退并以较少的重复次数再次尝试。
练习2 编写正则表达式回答:我想要当什么?要谁给我帮助?(直接print答案)
text = '我想要当宇航员!请你给予我帮助吧!'
解答2
import re
text = '我想要当宇航员!请你给予我帮助吧!'
pattern = r'我想要当(.*)!请(.)给予我帮助吧!'
match = re.search(pattern, text)
if match:
print(match.group(1), match.group(2))
元字符的组合
该章节内容对应:4.元字符的更多应用
(?:...)
非捕获组。有时你会想要使用分组(或捕获组)来表示正则表达式的一部分,但是对检索分组的内容不感兴趣。 你可以通过使用非捕获组来显式表达这个事实: (?:...)
,你可以用任何其他正则表达式替换 ...
。它的作用是当需要分组实现重复、逻辑操作(如 (a|b)+
),但不需要提取内容时,用非捕获组更简洁。
练习3 编写一种模式来匹配“摘要”或者“abstract”,print其后的内容
text_1 = '摘要:本文介绍了一种...'
text_2 = 'abstract:This essay...'
解答3
import re
text_1 = '摘要:本文介绍了一种...'
text_2 = 'abstract:This essay...'
pattern = r'(?:摘要|abstract)[::](.+)' # 请思考非捕获组和字符组的各自特点
match_1 = re.search(pattern, text_1)
match_2 = re.search(pattern, text_2)
print(match_1.group(1))
print(match_2.group(1))
(.*?)
还记的上面我们说过*
和+
等量词是以贪婪方式匹配重复字符的嘛,当量词后加上?
时就会变成非贪婪模式,即尽可能少匹配,一旦满足后续条件立即停止。后续条件就是该捕获组后续的模式。
[A-Z]
[
]
字符组用于匹配特定字符,而A-Z
表示匹配任意大写字母,类似的[0-9]
[a-z]
表示匹配任意数字和任意小写字母
[^\n]
当字符组内开头有一个帽子时,表示匹配除此之外的字符,对于本例则是匹配非换行符。更多的例子像,[^abc]
表示匹配a、b和c之外的内容。
(?=\n\s*|\Z)
当捕获组为(?=...)
的形式时,表示这是一个肯定型前视断言。如果内部的表达式(这里用 ...
来表示)在当前位置可以匹配,则匹配成功,否则匹配失败。 但是,内部表达式尝试匹配之后,正则引擎并不会向前推进;正则表达式的其余部分依然会在断言开始的地方尝试匹配。示例表达式含义是确保匹配项出现在换行符及后续空白或字符串的绝对结尾之前。
^\s*结\s*论\s*$
帽子表示字符串的开头,美元符表示字符串的结尾(与上边的\Z有相似的作用)。示例表达式的含义是匹配只含有“结论”(前中后可以有空格)的行,即完全匹配字符串。如果不添加帽子,就会造成部分完全匹配,即也会匹配“本章结论”中的“结论”。
总结
正则表达式主要用来提取字符串中的信息。通过组合元字符来构建你想要的任何模式。你无需深入了解它的内部算法原理,只需要能拿来解决问题即可。
参考链接