Python 常用模块系列:
- [Python] 常用模块(1):内建模块 – random & datetime
- [Python] 常用模块(2):内建模块 – re 以及正则表达式(本文)
- [Python] 常用模块(3):内建模块 – math & itertools
- [Python] 常用模块(4):NumPy 上
- [Python] 常用模块(4):NumPy 中
- [Python] 常用模块(4):NumPy 下
- [Python] 常用模块(5):pandas 1
- [Python] 常用模块(5):pandas 2
- [Python] 常用模块(5):pandas 3
- [Python] 常用模块(5):pandas 4
- [Python] 常用模块(5):pandas 5
- [Python] 常用模块(5):pandas 6
- [Python] 常用模块(5):pandas 7
- [Python] 常用模块(5):pandas 8
- [Python] 常用模块(5):pandas 9
正则表达式的介绍
正则表达式在抓取信息上有着非常重要的作用,因为正则表达式可以识别特定格式的字符串,比如电话号码、电子邮箱地址等等。而且网页语言是高度格式化的,因此正则表达式在网页爬虫上的作用也非常大。和字符串格式化类似,正则表达式可以在字符串前加 r
来让 python 识别:
r"avengers"
这样可以在所有文本中识别 avengers
这个词。
正则表达式的模糊规则
虽然正则表达式可以用来识别特定字符串,更多时候我们希望用正则表达式识别一个字符串格式,比如所有手机号码。正则表达式提供了相应的语法进行指定格式的字符串抓取。
字符的“或”匹配
有时候我们希望匹配多个字符、字符串中的任意一个,这时可以将希望匹配的字符放进 []
里:
r"m[ae]n"
这个正则表达式的意思是匹配 man
或 men
。[]
内还可以填充一个范围,比如 [0-9a-zA-Z]
匹配了所有数字、小写字母和大写字母中的任一个,这个范围可以任意指定,比如 [0-5]
匹配 012345
中的任一个数字。
“或”匹配也可以使用系统保留字符 |
,比如 "man|men"
和 "m[ae]n"
的效果是一样的。|
认为分割线两边的字符串都是二选一的,所以可以选择字符串,比如 "Tony|Steve"
是选择 Tony 或 Steve 中的一个。如果引号中有其他字符串,为避免歧义,需要将或匹配的字符串用 ()
括起来,例如
r"(Tony|Steve) Natasha"
就是选择了 Tony
或 Steve
中的一个和 Natasha;如果没有 ()
则选择了 Tony
或 Steve Natasha
中的一个。
保留字符和脱离
很多字符有特殊的含义,它们被系统使用。这样的字符有:
字符 | 含义 |
---|---|
. | 匹配任何字符(除了 /n ) |
^ | 匹配“开头” |
$ | 匹配“结尾” |
? | 匹配一个字符串就结束 |
.
可以匹配除了换行符(/n
)以外的任意字符;^
和 $
表示正则表达式的匹配必须在字符串的开头或结尾。?
的作用下面再说。
这样问题就来了,如果想匹配这些字符本身怎么办?办法是在这些保留字符前加 \
,这样系统就明白了你想匹配这些字符本身。这个 \
本身也是系统保留字符,意味着想匹配它本身,需要写成 \\
。
还有一点要注意,在 []
里的 ^
假如在字符串的开头,它的含义变成了“除了 xxx 以外的字符串”,比如[^1]
的含义是“除了 1 以外的所有字符。
重复匹配
因为中国大陆的手机号码加上国家区号一共 11 位,假如我们想匹配,难道我们要写 10 遍[0-9]
(号码第一位肯定是 1,所以是 10 个模糊字符)?当然不需要,因为正则表达式提供了字符的重复匹配方法:
字符 | 含义 |
---|---|
* | 重复零次或多次 |
+ | 重复一次或多次 |
{n, m} | 重复 n 至 m 次 |
{n} | 重复 n 次 |
也可以省略 {n, m}
中的任何一边,比如 {n,}
代表至少重复 n 次,而 {, m}
代表之多重复 m 次。
贪婪搜索和 ‘?’
正则表达式的搜索都是“贪婪”的,意味着假如你使用了不限匹配次数的 *
或 +
,系统会搜索整个文本试图找到所有的匹配。终结这种贪婪搜索的方法是在正则表达式后面加上 ?
,这样系统在搜索到第一个匹配以后就会停止搜索。
类型匹配
系统已经定义好了若干系统保留字符串,用来匹配指定字符类型:
字符 | 含义 |
---|---|
\d | 任意数字 |
\D | 不是数字的任意字符 |
\s | 任意 whitespace([空格\t\n] 等) |
\S | 不是 whitespace 的任意字符 |
\w | 任意大小写字母,数字和空格 |
\W | 不属于 \w 的任意字符 |
这里要说明一点,whitespace 指的是空白占位符,比如空格、tab(\t
)、换行符(\n
)等等。
re 模块
正则表达式的语法大概说完了,下面咱们看 re 模块。
载入模块
因为我们要用到 re 模块中的多数功能,所以我们载入整个模块:
import re
以后除非是载入模块中的某个函数,默认已经载入整个模块了。
re.compile(pattern, flags = 0)
将一个正则表达式的样式编译为一个正则对象用于匹配。如果这个正则对象会经常用到,预编译会节省很多打字的时间。正则对象下面会用到。
- 多行匹配
默认情况下,正则表达式把整个文本看成一个整体,比如:
"""
Tony said,
'I'm Iron Man!'
"""
会被系统看成
"Tony said,\n'I'm Iron Man!'"
这样假如我们希望用 r"^I\'m"
匹配行首的 I'm
,系统会返回 None
,因为系统把整个文本看成一行,认为行首是 Tony
。这时我们使用 flags = re.M
参数,让系统将换行符识别出来。
- 匹配时忽略大小写
有时候我们希望寻找匹配的时候忽略大小写,可以使用flag = re.I
。
re.findall(pattern, string, flags = 0)
搜索 string 内所有符合 pattern 的字符串,返回一个列表。
>>> re.findall(r"I\S+", "I'm Iron Man!")
["I'm", 'Iron']
re.finditer(pattern, string, flags = 0)
返回一个迭代器,这个迭代器 yield 匹配字符串。
>>> match = re.finditer(r"I\S+", "I'm Iron Man!")
>>> next(match)
<re.Match object; span=(0, 3), match="I'm">
>>> next(match)
<re.Match object; span=(4, 8), match='Iron'>
>>> next(match)
re.search(pattern, string, flags = 0)
返回第一个匹配的字符串。
>>> re.search(r"I\S+", "I'm Iron Man!")
<re.Match object; span=(0, 3), match="I'm">
re.sub(pattern, repl, string, count = 0)
搜索 string 里符合 pattern 的字符串,以 repl 代替最多替换几次(count 参数)。
>>> re.sub(r"I\S+", "He is", "I'm Iron Man!", count = 1)
He is Iron Man!
re.split(pattern, string, maxsplit = 0, flags = 0)
用 pattern 分开 string。 如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。如果 maxsplit 非零, 最多进行 maxsplit 次分隔, 剩下的字符全部返回到列表的最后一个元素。
>>> re.split(r"\s", "I'm Iron Man!")
["I'm", 'Iron', 'Man!']
正则对象的方法
正则对象的方法与 re 函数非常相似,无非就是把参数里的 pattern
变成了方法的对象。比如:
>>> ptn = re.compile("I\S+")
>>> ptn.findall("I'm Iron Man!")
["I'm", 'Iron']
匹配分组
通过对正则表达式用 ()
,我们可以为匹配分组,可以轻松定位所找到的内容。
>>> match = re.search(r"(I'm) (Iron) Man!", "I'm Iron Man!")
>>> match.groups()
("I'm", 'Iron')
>>> match.group(1)
"I'm"
>>> match.group(2)
'Iron'
如果分组太多,数字定位可以会不方便,这时我们可以在括号的开头写 ?P<名字>
给这个组定义一个名字,之后可以用这个名字找到组的内容。
>>> match = re.search(r"(?P<name>I'm) (?P<role>Iron) Man!", "I'm Iron Man!")
>>> match.group("name")
"I'm"
>>> match.group("role")
'Iron'