Python 网络爬虫从0到1 (5):Re(正则表达式)库入门详解

  在上一节Python 网络爬虫从0到1 (4):Beautiful Soup 4库入门详解中,我们已经能够从由Requests库发起请求得到的HTML响应报文主体中解析页面标签树并能遍历、搜索以得到需要的精确信息。但同时我们也发现了一个问题:标签搜索find()类函数中,只有搜索条件精确匹配,搜索函数才能搜索到需要的内容。但是,在实际应用中,我们常常会遇到一类情况,我们需要获取某一类具有共同特征的一组标签数据,但其各属性并不完全相同(但类似)。这时候,传统的完全匹配条件无法满足需要,这就需要使用正则表达式进行表达式匹配。

正则表达式库入门详解

准备知识

 正则表达式概述

  正则表达式的匹配模式主要是为了拓展常规搜索的局限。由于常规搜索要求提供与搜索结果完全匹配的条件,即使足够用于静态文本匹配置换,但其还不能轻松胜任匹配某一类型的动态文本的工作。这时候就需要使用模式匹配技术。继通配符后,功能更加强大的正则表达式的出现,为我们大大简化了字符串匹配的复杂性。正则表达式的中心是一个匹配模式(pattern),模式中有普通字符与限定符,所有的非完全匹配都有限定符支撑。

具体的匹配模式将在下文介绍

 Python中的正则表达式

  Python中的正则表达式功能通过一个标准库re实现,核心的匹配模式与标准正则表达式相同,但其他语法由于语言间差异而有所变动。re库主要提供如下方法/函数,

方法\函数 说明
re.match() 从字符串头开始匹配模式,如果没有匹配,返回空
re.search() 从全文搜索并返回第一个匹配子串(没有re.find()方法)
re.sub() 检索并替换匹配子串
re.complie() 生成一个正则表达式对象实例,实例方法与类相同(不需要重新指定模式)
re.findall() 全文搜索并找到所有匹配的子串,返回一个列表
re.finditer() 全文搜索并找到所有匹配的子串,返回一个可迭代对象
re.split() 按全文中符合模式的子串作为分隔符分割字符串,返回一个列表

re库同时提供了修饰符,用来设定匹配的特定条件,这些修饰符分别对应一个整形常量,参数名flag,通常在patternstring后面。当需要两个及以上修饰符时,可将这些修饰符进行与运算(或将其代表的数字直接相加(由于修饰符实际上是一个二进制位开关,其数值都为2的整数次幂)),使用re.<key name>.value查看对应的数字(key name为修饰符名称)

修饰符 说明 代表数字
re.I 匹配对大小写不敏感 2
re.L 本地化识别匹配(编码字符集相关) 4
re.M 多行匹配,影响^与$ 8
re.S 使.匹配包括换行符在内的所有字符 16
re.U 与L对应,使用Unicode字符集识别 32
re.X 使能够以更灵活的格式编写以提高阅读性 64

有关详细的re库以及正则表达式使用方法,我们将在下面介绍。


正则表达式使用

  由于正则表达式(匹配模式pattern)是整个re库的核心部分,缺少正则表达式,re库就缺少了灵魂,甚至不能进行一个普通的样例实验。所以我们就先来学习一下它的语法格式。

 正则表达式的限定符

  正则表达式主要由普通字符与限定符(也叫匹配模式)构成。普通字符用于限定具体的文本内容,限定符则用于限定这些具体内容的组织结构。限定符及其具体指代内容如下,

限定符 说明 样例
^ 匹配行首,在正则表达式最前面,表示以后面的模式开头 ^123 匹配123开头的字符串
$ 匹配行尾,在正则表达式最后面,表示以前面的模式结尾 123$ 匹配123结尾的字符串
. 一般情况下匹配除了换行符外的任意一个字符(不能为空) a.c 匹配abc,adc等
[] 匹配方括号中的任意一个字符 a[123]c 匹配a1c, a2c, a3c
[^] 字符写在^后面,不匹配方括号中的任意一个字符 [^123] 匹配不是1,2,3的一个字符
- (方括号中) 表示一个区间 [0-9] 匹配任意一个数字
* 匹配其前面一个字符的0次及以上出现 ur*a 表示ua, ura, urra等
+ 匹配其前面一个字符的1次及以上出现 ur+a 表示ura, urra, urrrra 等
匹配其前面一个字符0或1次出现 abc? 表示ab, abc
{n} 匹配前面一个字符n次出现(精确) ‘a{3}b’ 表示aaab
{n,m} 匹配前面一个字符出现n到m次(两边包括) ‘a{1,3}b’ 表示ab, aab, aaab
| 匹配其两侧表达式任意一个,整体用小括号包含 rub(e|y) 表示rube, ruby
() 可以将其中内容作为一个整体参与后面处理中 (u+r+a+){3} 表示乌拉(?)三次
  • {n,m}的特殊用法:如果n,m中有一个为空,则表示该方向上不受限制(最小仍是0个,最大无穷)。比如,a{3,}表示a至少出现三次,a{,10}表示a至多出现10次

 正则表达式的特殊符号

  另外,正则表达式还提供一些通过\转义的特殊符号,提供某些特殊用法

特殊符号 说明
\w 匹配一个数字字母或下划线
\W 匹配一个非数字字母下划线
\s 匹配任意一个空白符(Tab \t,space,return \r,newline \n, form feed \f换页符)。\r\n即为windows回车; \n unix\linux回车; \r MacOS 回车
\S 匹配任意非空字符(与\s相反)
\d 匹配任意数字,相当于[0-9]
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束
\b 匹配单词边界,即单词与空格相接的地方(如 ‘er\b’,能匹配’programmer’,但不能匹配’experience of’)
\B 匹配非单词边界
\b \t \s说明
\u.... \u后加入一个字符的UTF-8编码,代表这个UTF-8编码的对应字符,例如中文匹配[\u4e00-\u9fa5]
\n n为数字,表示第n组内容(在sub字符串匹配替换的时候需要)

 正则表达式的greedy \ lazy模式

  正则表达式中的限定符*+?{}都是greedy(贪婪的),它们总是尽可能地多匹配字符,但某些时候我们并不需要这样,这时候在这些限定符后加上?修饰符变为*?+???{}?,可以将其变为lazy(懒惰的),它们尽可能少地匹配字符。

以下示例将帮助更快地理解两个模式的区别

  • 字符串'abb',模式'ab?b'将匹配abb(优先匹配出现一次),而模式ab??b将匹配ab(优先匹配出现零次)
  • 字符串<p>Hello</p>,模式<.+>将匹配<p>Hello</p>(整个字符串可完全匹配,尽量多),模式<.+?>将匹配<p>(尽量少)
  • 字符串aaaaaaa,模式a{3,5}将匹配aaaaa(五个,尽量多),模式a{3,5}?将匹配aaa(三个,尽量少)

 正则表达式进阶样例

普通样例在介绍限定符的时候已经给出,这里不再赘述,下面介绍一些由多个限定符组合而成的较复杂样例

  • 国内邮编(第一位1-9,共6位):[1-9]\d{5}
  • 匹配带有er(任何位置,此处er仅举例)的单词(整体): \b.*?er.*?\b
  • 匹配末尾是er的单词: \b.*?er\b
  • 匹配一个IPv4地址(每一段都为0-255,对于255以下的限制需要进行分类讨论):(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5]).){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])

Python 正则表达式(re)标准库的使用

  Python中的正则表达式库使用原生字符串raw string类型表示正则表达式,表示为r'pattern',例如r'\b.*?er.*?\b'
  有关正则表达式标准库的功能在上面准备知识中已经给出,下面逐个介绍重要方法。

 compile

  compile函数将一个原生字符串编译称为一个正则表达式pattern对象实例,这个实例将可以使用find()match()等re库提供的正则表达式方法。通过编译的pattern对象在使用对应方法时,将不需要再次输入re.类方法中的第一个参数pattern。这对于同一个模式需要匹配多次的程序来说是一大优化。

a = re.compile(pattern, flag=0)

其中,

  • pattern: 要编译的原生字符串
  • flag: 正则表达式编译的修饰符,具体见上准备知识,多个修饰符连用可用|或逻辑运算或将这几个修饰符代表的数字相加

例如:

test = re.compile(r'(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])')

 match

  match函数将尝试从字符串的起始位置匹配一个模式,如果没有成功,则将返回None

b = re.match(pattern, string, flags=0)
b = a.match(string)

以上两行代码功能相同,第二行需要先进行complie函数的编译以生成pattern对象a

如果match函数匹配成功,则会返回一个match对象(re.Match),Match对象提供如下方法(属性)

方法/属性 说明
.group() 返回匹配到的字符串内容
.start() 返回匹配到字符串的开头位置
.end() 返回匹配到字符串的末尾位置
.span() 返回匹配到字符串的开头结尾,以元组形式
  • 如果在group后加上非0参数n(加0与不加相同),将返回第n组匹配到的结果(组是指小括号,一对括号看左括号,越靠左组号越小)如果遇到一组重复匹配则仅给出最后一轮匹配结果
  • 使用.groups()函数输出一个所有组的元组

例如:

test = re.compile(r'(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])')
a = test.match('192.168.0.1')
print(a.groups())
#以下是返回结果---
('0.', '0', '1')

 search

  search函数将从任何位置开始尝试匹配模式,如果匹配到,同样返回一个match对象,除了开始匹配的位置不同外,其余与re.match函数完全相同,不再赘述。

 fullmatch

  尝试用整个字符串匹配模式,如果匹配,返回一个match对象,如果不匹配则返回None,使用方法同上。

 split

re.split(pattern, string, maxsplit=0, flags=0)
a.split(string, maxsplit=0)

  split函数将字符串以匹配模式为界进行分割,将返回一个包含各部分的列表

  • 如果将pattern用小括号包含,那么得到的结果中也将包含这些匹配成功的内容,否则仅作为分界符而不包含
  • maxsplit指定最大分割次数(1次分割为两段),re会从前向后搜索字符串,只分割前面n次,后面内容会整段输出(不再进行匹配尝试),0次表示不限制分段次数
  • 如果一个字符串的末尾与模式匹配,则在结果的最后将会带有一个空字符串,开头匹配则在前面有。

 sub

re.sub(pattern, repl, string, count=0, flags=0)
a.sub(repl, string, count=0)

  sub函数将返回一个字符串,这个字符串将所有匹配的内容进行了替换(将替换为repl中的内容)。所有识别都将在不重叠的情况下依次进行(每匹配成功依次执行并替换一次)

  如果repl是一个字符串,则其中的所有转义特殊字符都能被识别,否则将被按原样放置。

  repl可以是一个函数,这个函数接收一个match对象(即match()函数返回值),对其进行处理并返回一个字符串(作为替换字符串)

  • 参数count表示最大替换次数,如果为0,则不进行限制。

例如,(以下两个实例)

# example 1
test = re.compile(r'\d+')
print(test.sub('test', '123_Hello_456'))
#-----------
test_Hello_test

# example 2
def dashrepl(matchobj):
	if matchobj.group(0) == '-': return ' '
	else: return '-'
    
re.sub('-{1,2}', dashrepl, 'pro----gram-files')
#--------------
'pro--gram files'

猜你喜欢

转载自blog.csdn.net/Zheng__Huang/article/details/108722145