第57~59讲:一只爬虫的自我修养5——正则表达式
一 字符串查找
1 下载一个网页很容易,但是要在网页中查找到需要的内容很困难
2 字符串查找并不是使用find方法,查找到指定元素的位置就可以了
比如:写脚本获取最新的代理IP地址
——使用字符串查找定位IP地址所在位置就很困难(网页源代码有很多相同的标签,找到IP地址所在位置需要对标签按顺序依次定位)
——缺点:这样写不仅比较麻烦,而且不具备通用性(在一个网站可行,在另外一个网站就不可行了;网页源代码被修改之后也不可行了)
——思考:可不可以按照我需的内容自身特性来进行查找? 字符串附带的方法是无法做到的,但正则表达式可以做到
——比如查找IP地址:IP地址的特征有:由四段数字组成、每段数字的范围是0~255、由三个英文的句号隔开
二 正则表达式
在编写处理字符串程序或网页的时候,经常会有查找符合某些复杂规则的字符串的需要,此时正则表达式就会派上用场了
1 作用:描述复杂规则的工具
2 实现:python通过re模块实现正则表达式
3 正则表达式re模块的search方法——re.search()
- 功能:用于在字符串中搜索正则表达式模式第一次出现(匹配)的位置
- 注意:
- 该方法的第一个参数——即正则表达式模式(你要描述的搜索规则)需要使用原始字符串格式编写(即在字符串前面加一个r),这样可以避免很多不必要的麻烦
- 匹配位置从第一个字符串开始,对应索引为0;找不到没有返回值/返回null,找到的话会返回一段字符,并且span标签的值表示的是第一次匹配的字符串对应的索引范围
- 举例:
-
1 >>> import re 2 >>> re.search(r'FishC','Ilove FishC.com,小甲鱼') 3 <re.Match object; span=(6, 11), match='FishC'> 4 >>> "I love FishC.com".find('FishC') 5 7
此例子用find方法也可以实现
- 查找通配符:
- 一般概念中的通配符:*号或者?号,用它们来表示任何字符
- 正则表达式中的通配符:点号(英文句号.),它可以匹配除了换行符之外的任何字符
-
1 >>> re.search(r'.','I love FishC.com!') 2 <re.Match object; span=(0, 1), match='I'> 3 >>> re.search(r'Fish.','I love FishC.com!') 4 <re.Match object; span=(7, 12), match='FishC'>
-
- 如果只想匹配点号:只需要加反斜杠解义即可,此时点号不代表任何其它字符,它只代表它本身
-
1 >>> re.search(r'\.','I love FishC.com!') 2 <re.Match object; span=(12, 13), match='.'>
也就是说,在正则表达式中,反斜杠仍然可以剥夺元字符(该字符本身代表着其它含义,有特殊能力)的特殊能力;另外,反斜杠也可以使普通字符拥有特殊能力
-
1 >>> re.search(r'\d','I love 123 FishC.com!') 2 <re.Match object; span=(7, 8), match='1'> 3 >>> re.search(r'\d\d\d','I 345 love 123 FishC.com!') 4 <re.Match object; span=(2, 5), match='345'>
-
4 利用正则表达式匹配IP地址
- 方法一:
-
1 >>> re.search(r'\d\d\d.\d\d\d.\d\d\d.\d\d\d','192.168.111.123') 2 <re.Match object; span=(0, 15), match='192.168.111.123'>
- 存在的问题:
- ‘\d\d\d’匹配的最大数值是999,而IP地址的范围是0~255
- ‘\d\d\d.\d\d\d.\d\d\d.\d\d\d’要求IP地址每组都要有三位数字,但是有的IP地址每组可以由一位或者两位数字
- ‘\d\d\d.\d\d\d.\d\d\d.\d\d\d’这种写法并不美观
- 字符类
- 含义:只要匹配字符类里面的任意字符都算匹配(将里面的所有字符都当作普通字符看待,除了横杠- 转义符\ 脱字符^:在字符类里面是取反的意思且只能放在最前面;如果放在后面表示匹配脱字符本身 )
-
1 >>> re.findall(r'[\n]','FishC.com\n') 2 ['\n'] 3 >>> re.findall(r'[^a-z]','FishC.com\n') 4 ['F', 'C', '.', '\n'] 5 >>> re.findall(r'[a-z^]','FishC^.com\n') 6 ['i', 's', 'h', '^', 'c', 'o', 'm']
-
- 创建:我们用中括号创建一个字符类
- 举例:
-
1 >>> re.search(r'[aeiou]','I love FishC.com!') 2 <re.Match object; span=(3, 4), match='o'>
正则表达式默认是开启大小写敏感模式的,所以第一个匹配的字符是o
- 改进:
-
1 >>> re.search(r'[aeiouAEIOU]','I love FishC.com!') 2 <re.Match object; span=(0, 1), match='I'>
- 技巧1:在中括号中还可以使用小横杠‘-’来表示范围
- 举例:
-
1 >>> re.search(r'[a-z]','I love FishC.com!') 2 <re.Match object; span=(2, 3), match='l'> 3 >>> re.search(r'[0-9]','I love 12345 FishC.com!') 4 <re.Match object; span=(7, 8), match='1'> 5 >>> re.search(r'[4-9]','I love 12345 FishC.com!') 6 <re.Match object; span=(10, 11), match='4'>
- 技巧2:使用大括号来解决限定重复匹配的次数(即控制某个字符匹配多少次)
-
1 >>> re.search('a{5}bc','aaaaaaaabc') 2 <re.Match object; span=(3, 10), match='aaaaabc'> 3 >>> re.search('a{5}bc','aaabc')
只有字母a的重复次数够5次才可以匹配成功
- 也可以给字母重复匹配的次数限定一个范围
-
1 >>> re.search('a{3,10}bc','aaaaaabc') 2 <re.Match object; span=(0, 8), match='aaaaaabc'>
-
- 如何用正则表达式匹配IP地址:
- 错误示范:
-
1 >>> re.search(r'[0-255]','188') 2 <re.Match object; span=(0, 1), match='1'> 3 >>> re.search(r'[0-2][0-5][0-5]','188')
注意:(1)正则表达式匹配的是字符串,所以数字对于字符串来说只有0-9(例如说123就是由‘1’、‘2’、‘3’三个人字符组成的,它并没有说百十千这些单位);所以[0-255]这个字符类表示的是‘0-2’、‘5’、‘5’,它只匹配一位,所以它会匹配‘0、1、2、5’四个数字中的任何一个,所以后面会匹配出1; (2)后面的'[0-2][0-5][0-5]'可以匹配一个三位数,第一位可以匹配‘0、1、2’,第二位和第三位都可以匹配‘0、1、2、3、4’,‘188’这个三位数不满足要求,所以没有匹配成功
- 正确的方法:
-
1 >>> re.search(r'[01]\d\d|2[0-4]\d|25[0-5]','188') 2 <re.Match object; span=(0, 3), match='188'>
这样可以表示匹配IP地址中的一组数字
-
1 >>> re.search(r'(([01]\d\d|2[0-4]\d|25[0-5])\.){3}([01]\d\d|2[0-4]\d|25[0-5])','192.168.1.1') 2 >>> re.search(r'(([01]\d\d|2[0-4]\d|25[0-5])\.){3}([01]\d\d|2[0-4]\d|25[0-5])','192.168.112.123') 3 <re.Match object; span=(0, 15), match='192.168.112.123'>
这样可以匹配IP地址中的四组数字,但是由于没有充分考虑到数字的位数,导致第一次匹配失败,第二次匹配成功了
-
1 >>> re.search(r'(([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5])\.){3}([01]{0,1}\d{0,1}\d|2[0-4]\d|25[0-5])','192.168.1.1') 2 <re.Match object; span=(0, 11), match='192.168.1.1'>
通过限制在第一、二位匹配数字的重复次数:{0,1}(重复0次表示不用匹配该位的数字,即没有这一位),来达到匹配对应的组只有一位数或者两位数的效果。
- 含义:只要匹配字符类里面的任意字符都算匹配(将里面的所有字符都当作普通字符看待,除了横杠- 转义符\ 脱字符^:在字符类里面是取反的意思且只能放在最前面;如果放在后面表示匹配脱字符本身 )
-
三 正则表达式的具体语法
在python中,正则表达式是以字符串的形式描述的,正则表达式的强大之处在于特殊符号的应用,正是因为这些符号使得正则表达式可以匹配复杂的规则,而不仅仅只是一个字符串
1 正则表达式的特殊符号及其用法
- 特殊符号的分类:
- 元字符: . ^(脱字符:开始位置) $(美元符号:结束位置) * + ? { } [ ](字符类) \ |(管道符) ( )
- 反斜杠+序号 :
- 1 引用序号对应的子组所匹配的字符串,子组的序号从 1 开始计算
- 表示该数字所表示的序号对应的元组在所有组后面复制,然后再查找与“原字符串+复制内容” 匹配的下标索引位置
-
1 >>> import re 2 >>> re.search(r'(FishC)\1','FishC') 3 >>> re.search(r'(FishC)\1','FishCFishC') 4 <re.Match object; span=(0, 10), match='FishCFishC'> 5 >>> re.search(r'(FishC)(Fish)\2','FishCFishC') 6 >>> re.search(r'(FishC)(Fish)\2','FishCFishFishCFish') 7 <re.Match object; span=(0, 13), match='FishCFishFish'> 8 >>> re.search(r'(FishC)(Fish)\1','FishCFishFishCFish') 9 <re.Match object; span=(0, 14), match='FishCFishFishC'>
- 2 如果序号是以 0 开头,或者 3 个数字的长度。那么不会被用于引用对应的子组,而是用于匹配八进制数字所表示的 ASCII 码值对应的字符
- 060是字符'0'的ascii码的八进制表示;141是字母‘a’的ascii码的八进制表示
-
1 >>> re.search(r'(FishC)\060','FishCFishC0') 2 <re.Match object; span=(5, 11), match='FishC0'> 3 >>> re.search(r'(FishC)\141','FishCFishCa') 4 <re.Match object; span=(5, 11), match='FishCa'>
- 1 引用序号对应的子组所匹配的字符串,子组的序号从 1 开始计算
- 字符类[]
- 匹配次数{}
- * 号:匹配前面的子表达式零次或多次,等价于 {0,}
- + 号:匹配前面的子表达式一次或多次,等价于 {1,}
- ?号:匹配前面的子表达式零次或一次,等价于 {0,1}
-
- \A:匹配输入字符串的开始位置,类似于脱字符^;\Z匹配输入字符串的结束位置,类似于美元符号$
- \b:匹配一个单词边界,单词指的是Unicode格式的字母、数字和下划线字符
-
1 >>> re.findall(r'\bFishC\b','FishC.com_FishC_com!(FishC)') 2 ['FishC', 'FishC']
-
- \B:匹配非单词边界
-
1 >>> import re 2 >>> re.findall(r'\bFishC\b','FishC.com_FishC_com!(FishC)') 3 ['FishC', 'FishC'] 4 >>> re.findall(r'\BFishC\B','FishC.com_FishC_com!(FishC)') 5 ['FishC'] 6 >>> re.search(r'\BFishC\B','FishC.com_FishC_com!(FishC)') 7 <re.Match object; span=(10, 15), match='FishC'> 8 >>> re.search(r'\bFishC\b','FishC.com_FishC_com!(FishC)') 9 <re.Match object; span=(0, 5), match='FishC'>
-
- \d:表示匹配任何一个数字 对应\D \s: 对应\S 取反
- Unicode(str类型)模式:匹配Unicode中的空白字符(\t\n\r\f\v等)
- 8位(bytes类型)模式:匹配ascii中定义的空白字符(\t\n\r\f\v等)
- Unicode(str类型)模式:匹配任何Unicode的单词字符
- 8位(bytes类型)模式:匹配ascii中定义的字母和数字
- 转义字符
- 注意事项
- 正则表达式里面不能随意加空格
- 正则表达式默认启用了贪婪(即贪心,在可能的条件下尽可能多的去匹配)模式进行匹配
- 举例:
1 >>> s = '<html><title>I love FishC.com</title></html>' 2 >>> re.search(r'<.+>',s) 3 <re.Match object; span=(0, 44), match='<html><title>I love FishC.com</title></html>'>
- 举例:
- 如何启动非贪婪模式:在要匹配的内容后面加一个?号即可
-
1 >>> re.search(r'<.+?>',s) 2 <re.Match object; span=(0, 6), match='<html>'>
-
2 编译正则表达式
- 什么情况下需要编译正则表达式
- 如果你需要重复地使用某个正则表达式,那么你可以先将该正则表达式编译成模块对象
- 怎么编译正则表达式,使用re模块的compile方法,该方法是模块级别的方法
-
1 >>> p=re.compile(r'[A-Z]') 2 >>> type(p) 3 <class 're.Pattern'> 4 >>> p.search('I love FishC.com!') 5 <re.Match object; span=(0, 1), match='I'> 6 >>> p.findall('I love FishC.com!') 7 ['I', 'F', 'C']
-
- 使用模块方法和编译后的方法在字符串中查找与正则表达式匹配的内容,那个效率比较高?
- 没有定论,视情况而定
- 编译标志有哪些