前面我们已经讲了,如何爬取整个网页的内容,那我就想要我需要的信息,改如何做呢,下面我们就来讲讲正则表达式,用正则表达式来获取我们需要的内容。
1 正则表达式
1.1 介绍
正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。
正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,这个过程会稍微有一些不同,但也是很好理解的,看下图中的示例以及自己多使用几次就能明白。
下图列出了Python支持的正则表达式元字符和语法:
图片来源:AstralWind
1.2 数量词的贪婪模式与非贪婪模式
正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式"ab*"如果用于查找"abbbc",将找到"abbb"。而如果使用非贪婪的数量词"ab*?",将找到"a"。
1.3 反斜杠的困扰
与大多数编程语言相同,正则表达式里使用""作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r"\"表示。同样,匹配一个数字的"\d"可以写成r"\d"。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
1.4 匹配模式
正则表达式提供了一些可用的匹配模式,比如忽略大小写、多行匹配等,这部分内容将在Pattern类的工厂方法re.compile(pattern[, flags])中一起介绍。
2 re模块
这个模块提供了与Perl中类似的正则表达式匹配操作,compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。
2.1 使用re模块
re模块主要用到的方法列举如下:
#返回pattern对象
re.compile(string[,flag])
#以下为匹配所用函数
re.match(pattern, string[, flags])
re.search(pattern, string[, flags])
re.split(pattern, string[, maxsplit])
re.findall(pattern, string[, flags])
re.finditer(pattern, string[, flags])
re.sub(pattern, repl, string[, count])
re.subn(pattern, repl, string[, count])
在介绍这几个方法之前,我们先来介绍一下pattern的概念,pattern可以理解为一个匹配模式,那么我们怎么获得这个匹配模式呢?很简单,我们需要利用re.compile方法就可以,将正则表达式模式编译成正则表达式对象pattern,可以使用它与match()、search()和其他方法进行匹配。例如
# 通过compile生成一个pattern对象,然后我们利用这个对象来进行进一步的匹配
pattern = re.compile(r'hello')
另外大家可能注意到了另一个参数 flags,在这里解释一下这个参数的含义: 参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I | re.M。
re.I(IGNORECASE): 忽略大小写(括号内是完整写法,下同)
re.M(MULTILINE): 多行模式,改变'^'和'$'的行为(参见上图)
re.S(DOTALL): 点任意匹配模式,改变'.'的行为
re.L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
re.U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
re.X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。以下两个正则表达式是等价的:
2.1.1 re.match(pattern, string, flags=0)
如果字符串开头的0或更多字符匹配正则表达式模式,则返回对应的匹配对象。如果字符串与模式不匹配,则返回None;请注意,这与零长度匹配是不同的。注意,即使在多行模式下,re.match()也只会在字符串的开头匹配,而不是在每一行的开头。如果您想在字符串的任何位置找到匹配,那么使用search()替代参见search()和match()。
# -*- coding: utf-8 -*-
# 导入re模块
import re
# 定义pattern变量
pattern = re.compile(r'abc')
# 使用match匹配结果,如果未匹配返回None
result1 = re.match(pattern=pattern, string='abc')
# 这里也有abc但是没有匹配
result2 = re.match(pattern=pattern, string='defabc')
# 注意这里换成了search
result3 = re.search(pattern=pattern, string='defabc')
if result1: ①
print(result1.group() ④ )
else:
print('1未匹配')
if result2: ②
print(result2.group())
else:
print("2未匹配")
if result3: ③
print(result3.group())
else:
print("3未匹配")
①pattern正则表达式为’abc’,我们匹配的目标字符串string也为'abc',匹配成功。 ②pattern正则表达式为’abc’,我们匹配的目标字符串string也包含'abc',但是没有在开头,匹配失败 ③pattern正则表达式为’abc’,用search重新匹配字符串,没有在开头也完成了匹配 ④group含义见下文
2.1.2 match对象的的属性和方法
Match对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用Match提供的可读属性或方法来获取这些信息。
属性:
1.string: 匹配时使用的文本。
2.re: 匹配时使用的Pattern对象。
3.pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
4.endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
5.lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
6.lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。
方法:
1.group([group1, …]):
获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
2.groups([default]):
以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
3.groupdict([default]):
返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
4.start([group]):
返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
5.end([group]):
返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
6.span([group]):
返回(start(group), end(group))。
7.expand(template):
将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符’0’,只能使用\g0。
下面我们用一个例子说明:
# -*- coding: utf-8 -*-
import re
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')
print('m.string:', m.string)
print('m.re:', m.re)
print('m.pos:', m.pos)
print('m.endpos:', m.endpos)
print('m.lastindex:', m.lastindex)
print('m.lastgroup:', m.lastgroup)
print('m.group:', m.group)
print('m.group(1, 2):', m.group(1, 2))
print('m.groups():', m.groups())
print('m.groupdict():', m.groupdict())
print('m.start(2):', m.start(2))
print('m.end(2):', m.end(2))
print('m.span(2):', m.span(2))
print("m.expand(r'\g \g\g'):", m.expand(r'\2 \1\3'))
# 返回结果
# m.string: hello world!
# m.re: re.compile('(\\w+) (\\w+)(?P<sign>.*)')
# m.pos: 0
# m.endpos: 12
# m.lastindex: 3
# m.lastgroup: sign
# m.group: <built-in method group of _sre.SRE_Match object at 0x000002658D836C10>
# m.group(1, 2): ('hello', 'world')
# m.groups(): ('hello', 'world', '!')
# m.groupdict(): {'sign': '!'}
# m.start(2): 6
# m.end(2): 11
# m.span(2): (6, 11)
# m.expand(r'\g \g\g'): world hello!
2.1.3 re.search(pattern, string, flags=0)
search方法与match方法极其类似,区别在于match()函数只检测re是不是在string的开始位置匹配,search()会扫描整个string查找匹配,match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回None。同样,search方法的返回对象同样match()返回对象的方法和属性。具体可以参见2.1.1中示例,同样的匹配字符串'defabc', match无法匹配到,search成功匹配。
2.1.4 re.split(pattern, string, maxsplit=0, flags=0)
按pattern分割字符串,如果在pattern中使用了括号,那么pattern中所有组的文本也会作为结果列表的一部分返回。如果maxsplit是非零的,那么最多maxsplit会发生,而字符串的其余部分将作为列表的最后一个元素返回。具体见示例:
>>>print(re.split(r'\W+', 'Words, words, words.'))
>>>print(re.split(r'(\W+)', 'Words, words, words.'))
>>>print(re.split(r'\W+', 'Words, words, words.', 1))
>>>print(re.split('[a-f]+', '0a3B9', flags=re.I))
# 返回结果
# ['Words', 'words', 'words', '']
# ['Words', ', ', 'words', ', ', 'words', '.', '']
# ['Words', 'words, words.']
# ['0', '3', '9']
2.1.5 re.findall(pattern, string, flags=0)
搜索string,以列表形式返回匹配到的子串,示例如下:
>>> print(re.findall(r'[a-f]', '0a8B9', flags=re.I))
# 返回结果
['a', 'B']
2.1.6 re.finditer(pattern, string, flags=0)
返回一个迭代器,它在字符串中为RE模式的所有非重叠匹配对象提供匹配对象。从左到右扫描字符串,并按找到的顺序返回匹配。
import re
# 定义pattern
m = re.compile(r'[a-f]')
# 获取返回结果result
result = re.finditer(m, 'a1b2c3d4e5')
# 打印result类型
print(type(result))
# 打印result内容
for r in result:
print(r.group())
# 返回结果
# <class 'callable_iterator'>
# a
# b
# c
# d
# e
2.1.7 re.sub(pattern, repl, string, count=0, flags=0)
通过repl来替换字符串中最左边的不重叠的模式pattern,返回字符串。如果没有找到该pattern,则字符串将保持不变。 pattern : 正则中的模式字符串。 repl : 替换的字符串,也可为一个函数。 string : 要被查找替换的原始字符串。 count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
# -*- coding: utf-8 -*-
import re
phone = "010-12345678 # 这是公司电话"
# 匹配‘#’到结尾的内容
pattern1 = re.compile(r'#.*$')
# 匹配非数字内容
pattern2 = re.compile(r'\D')
# 替换phone中的注释
result1 = re.sub(pattern=pattern1, repl="", string=phone)
# 替换phone中非数字内容
result2 = re.sub(pattern=pattern2, repl="", string=phone)
print(result1)
print(result2)
# 返回结果
# 010-12345678
# 01012345678
当repl是一个字符串时,可以使用\id或\g、\g引用分组,但不能使用编号0。 当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。 count用于指定最多替换次数,不指定时全部替换,具体见示例:
# -*- coding: utf-8 -*-
import re
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
# 匹配s中的内容,并将组1和组2颠倒顺序输出
print(re.sub(pattern, r'\2 \1', s))
def func(m):
'''
定义函数,分别返回匹配到的组1和组2内容,并且通过title方法,首字母大写
'''
return m.group(1).title() + ' ' + m.group(2).title()
# 打印匹配结果
print(re.sub(pattern, func, s))
# 返回结果
# say i, world hello!
# I Say, Hello World!
2.1.8 re.subn(pattern, repl, string, count=0, flags=0)
执行与re.sub同样的操作,不过返回是一个元组(new_string新的结果, number_of_subs_made替换次数)
# -*- coding: utf-8 -*-
import re
# 定义pattern
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
# 打印结果
print(re.subn(pattern, r'\2 \1', s))
def func(m):
'''
定义函数,返回匹配到的组并用title方法首字母大写
'''
return m.group(1).title() + ' ' + m.group(2).title()
# 打印结果
print(re.subn(pattern, func, s))
# 输出结果
# ('say i, world hello!', 2)
# ('I Say, Hello World!', 2)
至此,正则表达式部分结果,内容比较多,打家可以记个大概,真使用的时候可以翻回来再看看。 此文章部分出自Python爬虫学习系列教程