学习爬虫Part3-强大的正则表达式,re模块

强大的正则表达式,re模块

特性

  • 其实BeautifulSoup也是用正则实现的,而且它find_all的参数里还能接收正则
  • BeautifulSoup用的是节点定位,可能会出现多个符合条件的节点(却没有目标信息);正则是直接针对目标信息,以字符为单位匹配,一次筛选出正确结果
  • 有时候完整的信息不是你想要的,你只想取它的某一部分,正则能搞定,BeautifulSoup只能先获取完整信息再分离。

如果你要匹配一个ip地址,正则表达式会是这样
匹配ip地址:((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d).){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d))

re 模块

python 自带模块,直接导入即可。有匹配,替换等方法。

匹配规则(pattern)

规则其实是一个原字符串如r’表达式’/r”表达式”,较正式的叫法是模式字符串。匹配以单个字符为单位(除括号能把多个字符打包成分组(整体)来匹配)

表达式本质是字符串,不要单引(双引)号里套单引(双引)号,会出错。

字符匹配

  • 任一字符(空格也算)——就是匹配这个字符,某些字符因为在正则中有特殊用途需前加反斜杠转义如 [ { . | ( ) ^ * + ? $
  • .——英文句号,匹配除换行符\n外的任意单个字符
  • []——匹配中括号里的任一字符,与-结合还能表示范围内的任一字符,中括号内的字符除\外会自动转义,还有小心多个中括号嵌套错误
  • [^]——中括号最前面加^,与[ ]反义,匹配一个不在中括号里的字符,也可以用横杠-
1
2
3
4
5
6
r'[abcd]'#匹配一个a或b或c或d
r'[0-9]'#匹配一个0至9的数,-的作用域是左右各一个字符
r'[a-z]' r'[A-Z]'#分别匹配a到z或A到Z的一个字母
r'[12-89]'#注意因为是单字符匹配,匹配的是1,2到8的数,9(即1到9的一个数),不是12到89的数
r'[{.|()^*+?$\\]'#匹配 { . | ( ) ^ * + ? $ \中任一个,\要转义
r'[^a-zA-Z]'#匹配一个不是字母的字符

分组& 或 &转义

  • ()——括号,表达式分组(第n组,n=1,2,3….99,从左往右数),并形成子表达式
  • (?P< name >)——拥有括号的功能,但能为该分组再指定一个自定名字
  • (?P=name)——引用分配过名字的分组,但没有分组功能
  • |——或,左右规则任意匹配一个,从左往右尝试匹配,一旦成功就跳过后面的规则。|没被包在括号中间的话它的作用域是整个表达式,被包的话作用域在括号内
  • \——反斜杠,后接功能字有符转义功能,后接数字(1到99)有引用分组的功能,后接某些字母又有特殊功能
1
2
3
4
r'abc|def|ghi'#匹配abc或def或ghi
r'ma(?:k|d)e'#匹配make或made
r'(abc)def\1'#相当于r'(abc)defabc',匹配abcdefabc
r'(?P<ok>abc)f(?P=ok)'#为(abc)子组分配了“ok”的名字,然后再引用,匹配abcfabc

预定字符集

  • \d——匹配任一个数字(0~9)
  • \D——匹配一个非数字字符,与\d互补
  • \s——匹配一个空白字符,包括空格,\t,\n,\r,\n,\f,\v
  • \S——匹配一个非空白字符
  • \w——匹配一个单词字符。unicode下匹配各种语言的单个字符,单个数字,和下横线。ASCII下匹配单个英文字母,单个数字,和下横线
  • \W——匹配一个非单词字符
    1
    r'\w' #能匹配'物语&ものがたり'中的:物,语,も,の,が,た,り,汉语日语的单字,其他语言同理

数量词(接在字符或子组后)

  • {n}——作用于前一个字符或子表达式,匹配它重复n次
  • {min,max}——作用于前一个字符或子表达式,匹配它重复重复多少次min~max次,min和max可只写一个设置重复下限或上限,但逗号不能省,不写min时min默认为0
  • *——星号,作用于前一个字符或子表达式,匹配它零次或多次
  • +——作用于前一个字符或子表达式,匹配它至少一次
  • ?——作用于前一个字符或子表达式,匹配它零次或一次
1
2
3
4
r'z{3}'#匹配zzz
r'z{0,3}'#匹配z或zz或zzz
r'(?:abc){2}'#对子表达式匹配两次,匹配abcabc,(?:)是一个用法,不分组的意思,详看后面
#星号加号问号同理

非贪婪模式

  • 在数量词后接?,对前面的数量词开启非贪婪模式,意思就是在能匹配的前提下尽可能少的重复匹配。
  • 正则默认开启贪婪模式
1
2
r'<.+>'#默认贪婪,对于'<abc><def>'能匹配到'<abc><def>'整条,因为.贪婪地把尖括号也匹配掉了
r'<.+?>'#非贪婪,对于'<abc><def>'能匹配到'<abc>'和'<def>'

边界匹配

  • ^——放在表达式的最前面,作用域是表达式,在多行模式中,在每一行匹配字符串开头(多行模式要手动开启,否则和\A没什么区别)
  • $——放在表达式的最后面,作用域是表达式,在多行模式中,在每一行匹配字符串末尾(多行模式要手动开启,否则和\Z没什么区别)
  • \A——放在表达式的最前面,作用域是表达式,匹配字符串开头,不能多行匹配
  • \Z——放在表达式的最后面,作用域是表达式,匹配字符串末尾,不能多行匹配
  • \b——不匹配字符,只匹配一个边界,匹配\w和\W或\W和\w的边界(单词字符和非单词字符的边界)
  • \B——不匹配字符,只匹配一个边界,与\b相反,匹配\w和\w或\W和\W的边界
1
2
3
4
5
6
r'^abc|^def'#匹配abc开头或def开头,开启了多行模式时,对字符串'abcd\ndefh'能匹配出abc,def两个
r'abc$|def$'#匹配abc结尾或def结尾,开启了多行模式时,对字符串'0abc\n0def'能匹配出abc,def两个
r'\Aabc'#匹配abc开头,因为不能多行匹配,就算开启多行模式,对字符串'abcd\nabcd'只能匹配到前面的abc
#\Z同理
r'\w\b\W'#匹配“单词字符+非单词字符”的结构如'a!','1%'
#\B同理

Python 的re模块内置函数几乎都有一个flags参数,以位运算的方式将多个标志位相加。其中有两个模式:单行(re.DOTALL, 或者re.S)和多行(re.MULTILINE, 或者re.M)模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
单行模式 re.DOTALL
在单行模式里,文本被强制当作单行来匹配,什么样的文本不会被当作单行?就是里面包含有换行符的文本,比如:
This is the first line.\nThis is the second line.\nThis is the third line.

点号(.)能匹配所有字符,换行符例外。现在我们希望能匹配出整个字符串,当用点号(.)匹配上面这个字符串时,在换行符的地方,匹配停止。例如:
>>> a = 'This is the first line.\nThis is the second line.\nThis is the third line.'
>>> print a
This is the first line.
This is the second line.
This is the third line.
>>> import re
>>> p = re.match(r'This.*line.' ,a)
>>> p.group(0)
'This is the first line.'
>>>

在上面的例子里,即使是默认贪婪(greedy)的匹配,仍然在第一行的结尾初停止了匹配,而在单行模式下,换行符被当作普通字符,被点号(.)匹配:
>>> q = re.match(r'This.*line.', a, flags=re.DOTALL)
>>> q.group(0)
'This is the first line.\nThis is the second line.\nThis is the third line.'

点号(.)匹配了包括换行符在内的所有字符。所以,更本质的说法是
单行模式改变了点号(.)的匹配行为


多行模式 re.MULTILINE
在多行模式里,文本被强制当作多行来匹配。正如上面单行模式里说的,默认情况下,一个包含换行符的字符串总是被当作多行处理。但是行首符^和行尾符$仅仅匹配整个字符串的起始和结尾。这个时候,包含换行符的字符串又好像被当作一个单行处理。
在下面的例子里,我们希望能将三句话分别匹配出来。用re.findall( )显示所有的匹配项
>>> a = 'This is the first line.\nThis is the second line.\nThis is the third line.'
>>> print a
This is the first line.
This is the second line.
This is the third line.
>>> import re
>>> re.findall(r'^This.*line.$', a)
[]
>>>
默认点号不匹配换行符,我们需要设置re.DOTALL。
>>> re.findall(r'^This.*line.$', a, flags=re.DOTALL)
['This is the first line.\nThis is the second line.\nThis is the third line.']
>>>
匹配出了整句话,因为默认是贪婪模式,用问号切换成非贪婪模式:
>>> re.findall(r'^This.*?line.$', a, flags=re.DOTALL)
['This is the first line.\nThis is the second line.\nThis is the third line.']
>>>
仍然是整句话,这是因为^和$只匹配整个字符串的起始和结束。在多行模式下,^除了匹配整个字符串的起始位置,还匹配换行符后面的位置;$除了匹配整个字符串的结束位置,还匹配换行符前面的位置.
>>> re.findall(r'^This.*?line.$', a, flags=re.DOTALL+re.MULTILINE)
['This is the first line.', 'This is the second line.', 'This is the third line.']
>>>

更本质的说法是
多行模式改变了^和$的匹配行为

特殊构造(不作为分组,不被findall捕获)

  • (?:)——取消括号的分组功能,使其不会被findall方法捕获
  • (?#)——#后写注释内容,整个(?#)会被忽略
  • A(?=)——A之后的字符串需要匹配括号里的表达式A才会被匹配,一定用在表达式的最后(A是表达式,(?=)内的表达式不会被匹配捕捉,下同)
  • A(?!)——A之后的字符串需要不匹配括号里的表达式A才会被匹配,一定用在表达式的最后
  • (?<=)A——A之前的字符串需要匹配括号里的表达式A才会被匹配,一定用在表达式的最前,括号内的表达式需固定长度不能使用除{n}外的数量词
  • (?<!)A——A之前的字符串需要不匹配括号里的表达式A才会被匹配,一定用在表达式的最前,括号内的表达式需固定长度不能使用除{n}外的数量词

    1
    2
    3
    4
    r'(ab(?=cde))'#匹配后面是cde的ab
    r'a(?!\d+)'#匹配后面不跟一串数字的a,后括号可用所有数量词
    r'(?<=abc)de'#匹配前面是abc的de
    r'(?<!\d{3})a'#匹配前面不是三个数字的a,前括号可以用{n}但是不能用不定量的数量词
  • (?iLmsux)——放在表达式最前面,为所在的表达式设置模式,”i”, “L”, “m”, “s”, “u”, “x”,它们不匹配任何字串,对应python中re模块当中的(re.I, re.L, re.M, re.S, re.U, re.X)的6种模式,下面flag参数讲

    1
    r'(?i)abc'#“i”对应re.I,忽略大小写模式,能匹配Abc,ABC,abc等

方法&参数

相比于繁杂的规则,方法则要简单多了,常用的就这几个:

  • re.search(pattern,string,flags=0),返回第一个匹配的match对象(内含匹配字符串的信息)
  • re.findall(pattern,string,flags=0),返回所有匹配分组的字符串组成的列表,没设置分组相当于整个表达式就是一个分组
  • 如果表达式有多个分组,会返回复杂的列表,因此findall中的表达式通常只有一个分组
  • re.finditer(pattern,string,flags=0),同findall功能,但是返回的是迭代器

    1
    2
    re.findall(r'\d+(abc)\d+','1abc1,2abc2')#分组为(abc),findall只捕捉被数字包起来的abc返回列表['abc','abc']
    re.findall(r'((?:ab){2}\d)\d','abab11,abab22')#整个表达式匹配abab加一个两位数,(?:)取消了ab的分组,findall只捕捉abab加一个数,返回列表['abab1','abab2']
  • pattern = re.compile(pattern,flags=0),把规则打包返回(如多次使用该规则),相当与pattern和flag的合体,当成pattern使用可免去设置flags

  • re.sub(pattern,repl,string,count=0,flags)把匹配到的部分用指定字符串repl替换,count设置最大的可以被替换的匹配到的字符串的个数,默认为零替换所有

参数:

  • pattern:接收模式字符串,即表达式,也可以接收打包的规则
  • string:接收待匹配字符串,如html文档
  • flags:模式(标签),接受以下模式,多个模式用“|”分开如 flags=re.I|re.M
    1
    2
    3
    4
    5
    6
    7
    re.I = re.IGNORECASE   忽略大小写
    re.L = re.LOCALE   支持当前语言,为了支持多语言版本的字符集使用环境
    re.U = re.UNICODE   使用w,W,b,B这些元字符时将按照UNICODE定义的属性
    re.M = re.MULTILINE   开启多行模式
    re.S = re.DOTALL   使.能匹配换行符\n
    re.X = re.VERBOSE   可以忽略正则表达式中的空白和#号的注释,不匹配空格和#注释
    re.A  开启ASCII模式

match对象方法

列出常用方法,下面的match是对象

  • match.group(id/name)id是分组序号(1~99),name是分组的自定名字,返回指定分组的字符串;不传参数是返回整条匹配字符串
  • match.start(id/name),match.end(id/name),match.span(id/name),分别返回指定分组字符串在整个字符串中的开始位置,结束位置,范围。
Donate? comment?