
2.5 网页分析方法4:正则表达式
对于复杂的数据结构,或者无法使用XPath、CSS进行提取的数据,使用正则表达式是一个很好的选择。正则表达式就是使用一些特殊规定的字符组成包含一定逻辑的特殊组合,使用这个特殊组合对字符串进行过滤,找到满足该逻辑的数据。
2.5.1 提取指定字符
获取数据结合,有用的数据只有一部分,我们可以指定字符串进行匹配,如表2-17所示。
表2-17 字符匹配

2.5.2 预定义字符集
数字信息应该是爬虫工作中最频繁提取的数据了,如日期、交易量、订单数等。用来匹配数字的字符是\d,而提取文字信息一般用\w,这种叫作预定义字符集,常用的预定义字符集如表2-18所示。
表2-18 预定义字符集

2.5.3 数量限定
当需要重复匹配时,比如电话号码11个数字,匹配11次\d,为了避免写11次\d或者更多次,就需要用到限定符,如表2-19所示。
表2-19 数量限定

2.5.4 分支匹配
如果有多种匹配规则,则满足任意一种规则即可完成匹配,这时就要用到分支条件的匹配。比如日期格式,2018-10-10、2018/10/10或者2018.10.10都满足条件,从左往右,匹配到任意一个即可,如表2-20所示。
表2-20 条件分支

2.5.5 分组
表2-20中的正则表达式不仅能匹配到2018-12-12这样的日期,也能匹配到2018-99-99这样不存在的日期,需要对月份日期做具体的限定,月份应该是0[1-9]|1[0-2],日应该是0[1-9]|[12][0-9]|3[01],完整表达式为\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])。这里没有判断每月是否有31日,二月是否为闰月,更详细的判断请读者练习完成。
每个“()”为一个分组,分组的起始编号为1,每遇到一个“(”编号加1,在提取数据时,可以方便地将一组数据单独提取出来。
2.5.6 零宽断言
先介绍什么是零宽断言,在程序设计中,断言指的是判断一个情况是否为真,而在正则表达式中,零宽断言指的是当断言表达式为真的时候,再进行匹配,匹配的时候并不匹配断言表达式的内容。断言有不同的形式,结合如下字符串更好理解:

(1)先行断言,也叫作零宽度正预测先行断言,表达式:(?=exp),当断言表达式为真时,匹配断言表达式前面的位置。先行断言的执行步骤为:先从要匹配的字符串中的最右端找到第一个断言表达式,再匹配其前面的表达式;若无法匹配,则继续查找第二个断言表达式,再匹配第二个断言表达式前面的字符串;若能匹配,则匹配。例如\w+(?=ted),会匹配到repea和reci(断言表达是“ted”不会匹配),而.*(?=t)则会匹配到My WeChat account。
(2)后发断言,也叫作零宽度正回顾后发断言,表达式:(?<=exp),当断言表达式为真时,匹配断言表达式后面的位置。与先行断言执行顺序相反,先从要匹配的字符串中的最左端找到第1个断言表达式,再匹配其后面的表达式;若无法匹配,则继续查找第2个断言表达式,再匹配第2个断言表达式后面的字符串;若能匹配,则匹配。例如(?<=re)\w+,会匹配到peated和cited,而(?<=re).*则会匹配到peated what Reardon recited when。
(3)零宽度负预测先行断言,表达式(?!exp),当断言表达式不成立时,匹配断言表达式前面的位置;若断言表达式成立,则不匹配。例如\b(?!R)\w+匹配不以大写字母R开头的字符串。
(4)零宽度负回顾后发断言,表达式(?<!exp),当断言表达式不成立时,匹配断言表达式后面的位置;若断言表达式成立,则不匹配。例如\w+(?<!ted)\b匹配不以ted结尾的字符串。
2.5.7 贪婪模式与非贪婪模式
正则表达式默认是贪婪模式,也就是尽可能多地匹配字符串,比如使用a\w+b匹配a123b123b时,得到的是a123b123b,而不是a123b。想匹配到a123b的时候,就需要启用非贪婪模式,将表达式改为a\w+?b就可以匹配到a123b。数量限定中启用非贪婪模式如表2-21所示。
表2-21 数量限定与非贪婪模式

2.5.8 Python中的正则表达式
在其他编程语言中,处理正则表达式时,反斜杠“\”时常带来想不到的造成困扰,反斜杠既可作为普通字符,又兼任着转义字符的功能,假如你需要匹配文本中的字符“\”,那么使用编程语言表示的正则表达式需要使用4个反斜杠“\\\\”:前两个和后两个分别用于在编程语言中转义成反斜杠,转换成两个反斜杠后,再在正则表达式中转义成一个反斜杠。而在Python中,原生字符串很好地解决了这个问题,简单地使用r"\\"即可。同样,匹配一个数字的"\\d"可以写成r"\d"。使用原生字符串,无须担心繁杂的转义问题,写出来的表达式也更加直观。
Python中使用re模块来提供对正则表达式的支持。使用re的一般步骤是先将正则表达式的字符串形式编译为Pattern实例,然后使用Pattern实例处理文本并获得匹配结果(一个Match实例),最后使用Match实例获得信息,进行其他的操作。下面对主要用到的方法进行示例讲解。
(1)re.compile(pattern,flags=0)
将字符串pattern转化为Pattern对象,用来给其他的re函数提供正则表达式参数,做进一步的搜索,其中的flags是匹配模式,可选的有:
- re.I:忽略大小写。
- re.M:多行模式,改变'^'和'$'的行为。
- re.S:点任意匹配模式,改变“.”的行为。
- re.L:使预定字符类 \w \W \b \B \s \S 取决于当前区域设定。
- re.U:使预定字符类 \w \W \b \B \s \S \d \D 取决于Unicode定义的字符属性。
- re.V:详细模式,这个模式下正则表达式可以是多行的,忽略空白字符,并可以加入注释。
【示例2-1】使用re.compile()转化Pattern对象:

(2)re.match(pattern, string, flags=0)
从字符串string起始位置开始匹配pattern,若匹配到,则返回一个Match对象;若未匹配到,则返回None。Match对象有很多方法,最常用的是group,示例如下。
【示例2-2】re.match()查找字符串

运行结果如下:

(3)re.search(pattern, string, flags=0)
search方法与match方法类似,不同的是,match必须在字符串起始位置开始匹配,而search则会查找整个string进行匹配。若在任意位置匹配到,则返回一个Match对象,示例如下。
【示例2-3】re.search()全文查找字符串

运行结果如下:

(4)re.findall(pattern, string, flags=0)
搜索整个字符串,以列表形式返回所有匹配结果,示例如下。
【示例2-4】re.findall()查找所有匹配对象

运行结果如下:

(5)re.split(pattern, string, maxsplit=0, flags=0)
以匹配到的字符串分割string,返回分割后的列表,示例如下。
【示例2-5】re.split()分割查找字符串

运行结果如下:

(6)re.sub(pattern, repl, string, count=0, flags=0)
使用repl替换匹配到的字符串,并返回替换后的字符串。repl可以是一个字符串或者一个方法,当是一个方法时,接收一个Match参数并返回一个字符串,使用此字符串进行替换操作,count指定次数,默认为0时全部替换,示例如下。
【示例2-6】re.sub()替换匹配对象

运行结果如下:
