前言时刻

又是一星期没写总结,家里有事,嗯……。没事,现在开始学,也是可以的,觉得最晚的是时候,恰好是最好的开始。

来来总结一波,今天学习的是最重要的re正则。 包括:字符组、元字符、量词。 贪婪匹配和惰性匹配 findall和search用法

1、正则表达式

正则(regex)是一种匹配字符串的语法,就像是 xpath 一样。注意:正则表达式不是哪种语言特有的,而是编程语言为了支持正则而开发相应的模块或库。比如正则表达式对应的 Python 库就是 re ,同样的Java、js等都是支持正则表达式的。

接下来,就先写正则表达式的语法,然后再写 Python 的 re 模块,re 支持正则表达式中的所有语法。所以学好正则表达式,以后换哪个编程语言都可。

推荐一个在线练习正则表达式的网站,很好用。https://tool.chinaz.com/regex

1. 字符组

字符组:指的是在括号[]中的字符,一个括号只匹配一个字符。字符串:xiyuangongzi666

  • [abcd]:匹配abcd中任一字符,结果:a
  • [0-9]:表示匹配任意 Unicode 十进制数。相当于是[0123456789]
  • [a-z]:表示匹配 a 到 z 中的任意一位字母。
  • [A-Z]:表示匹配 A 到 Z 中的任意一位字母。
  • [0-9a-zA-Z]:表示匹配任意一位数字、字母。
  • 注意:[]中的字符均不含转义功能
  1. import re
  2. st = "xiyuangongzi666"
  3. re.findall("[a-z][0-9]", st) # ['i6']
  4. re.findall("[azi][569]", st) # ['i6']
  5. re.findall("[a-z]\d", st) # ['i6']
  6. re.findall("[a-z][\d]", st) # ['i6']

2. 元字符

如果你觉得用上面的字符组匹配有些麻烦了,那么接下来介绍的将更佳简洁

  • \d 相当于是[0-9],表示匹配任意 Unicode 十进制数。
  • \D 相当于是[^0-9],对 \d 取非,匹配除了十进制数以外的字符。
  • \w相当于是[0-9a-zA-Z_],匹配数字、字母、下划线。
  • \W 相当于是[^0-9a-zA-Z_],匹配除了数字、字母、下划线以外的字符。
  • 空白字符:空格、tab键(\t)、回车(\n),可统一使用\s表示。
  • \S :是匹配除了空白字符以外的字符。
  • . 是匹配除了换行符之外的任意一个字符
  • [^]:表示 功能,例如:[^\d]匹配所有非数字字符
  • ^:匹配一个字符串的开头
  • $:匹配一个字符串的结尾
  • | 的意思,如ab|cd,表示ab或者cd
  • ()表示分组,可以限定区域以及获取分组结果。

例子:

  1. # 2、非^和或|
  2. re.findall("[^0-9]\d", st) # ['i6']
  3. re.findall("[a-z]\d|\d\d", st) # ['i6', '66']
  4. # 3、简洁版
  5. re.findall("\w\d", st) # ['i6', '66']
  6. # ^和$
  7. re.findall("^[a-z]\d$", st) # []
  8. re.findall("^[a-z][a-z]", st) # ['xi']
  9. re.findall("\d\d$", st) # ['66']
  10. # .
  11. st2 = """我喜欢1python
  12. 学习吧~
  13. """
  14. re.findall("python.学习", st2) # []
  15. re.findall("python.学习", st2, re.DOTALL) # ['python\n学习']
  16. # re.DOTALL 让 . 可以匹配任何的字符,包括换行符

3. 量词

  1. {n} 表示对前面的正则式匹配 n 个。
  2. {n,} 表示对前面的正则式至少匹配 n 个。
  3. {n, m} 表示对前面的正则式至少匹配 n 个,最多匹配 m 个。
  4. ? 表示对前面的正则式匹配 0 个或 1 个。
  5. + 表示对前面的正则式匹配 1 个或 无穷 个。
  6. * 表示对前面的正则式匹配 0 个或 无穷 个
  1. # 量词
  2. import re
  3. st = "xiyuangongzi666"
  4. re.findall("[a-z]{2,4}[0-9]", st) # ['ngzi6'] 贪婪模式
  5. re.findall("[a-z]{2,}[0-9]", st) # ['xiyuangongzi6']
  6. re.findall("\d?\d{3}", st) # ['666']
  7. re.findall("[a-z]{2,}[0-9]", st) # ['xiyuangongzi6']
  8. re.findall("[a-z]+[0-9]", st) # ['xiyuangongzi6']
  9. re.findall("6*[a-z]*[0-9]", st) # ['xiyuangongzi6', '66']

2、re正则

查找:

2.1 re.findall

re.findall(pattern, string, flags=0):返回所有匹配的结果组成的列表。

  • pattern:正则表达式
  • flags:标志位,用于控制正则表达式的匹配模式,比如:忽略大小写,等
  1. st = "xiyuangongzi666xiyuangongzi666"
  2. # 1.findall
  3. re.findall('[a-z]\d', st, flags=2) # ['i6', 'i6']
  4. re.findall('xi(.+?)gong(.+?)\d', st) # [('yuan', 'zi'), ('yuan', 'zi')]

1)如果你想用前一个分组匹配的 结果 匹配后面字符,可以用(?P=name)或者(\index)

  1. st2 = "gongzigongzi"
  2. re.findall(r'gong(.+?)gong(\1)', st2) # [('zi', 'zi')]
  3. re.findall(r'gong(?P<name>.+?)gong(?P=name)', st2) # ['zi']
  4. # 解释:请看文章最底部的,其他

2.2 re.search

re.search(pattern, string, flags=0):只匹配字符串中的第一个结果,返回匹配到的内容的位置和内容等,取值需要使用 group ,用法啥的和 findall 一样。

  1. import re
  2. st = "xiyuangongzi666xiyuangongzi666"
  3. # 2. re.search
  4. res = re.search('[a-z]\d', st) # <re.Match object; span=(11, 13), match='i6'>
  5. res.group() # i6
  6. res = re.search('xi(.+?)gong(.+?)\d', st) # <re.Match object; span=(0, 13), match='xiyuangongzi6'>
  7. res.group(0) # xiyuangongzi6
  8. res.group(1) # yuan
  9. res.group(2) # zi

注意:.group(index)中,若无 index 默认取0,表示取全部匹配到的结果,index 取的是第 index 个分组的匹配结果。

2.3 re.match

re.match(pattern, string, flags=0):match 函数和 search 的用法几乎一样,唯一不同在于 match 只匹配字符串开头部分,若匹配到会返回结果,若无则返回 None 。

  1. import re
  2. # 3、re.match 函数
  3. st = "xiyuangongzi666xiyuangongzi666"
  4. res = re.match('xi[a-z]', st) # <re.Match object; span=(0, 3), match='xiy'>
  5. res.group() # xiy
  6. re.match('zi\d', st) # None
  7. re.search('^zi\d', st) # None
  8. # 在search中正则表达式中加上 ^ 就相当于match函数。

问题1?

当我们使用 findall 时,返回的是匹配的所有结果组成的列表。如果老板让你匹配了几百万个数据,你的内存会瞬间爆满。那该怎么办?

还记得之前学习的迭代器或生成器,是典型的时间换空间,每次仅仅返回其中的一个数据,这样就可节省大量内存。

那么请出接下来的re.finditer,返回所有匹配到的结果的迭代器。

2.4 re.finditer

用法和 findall 一样,re.finditer 强大之处在于它返回一个迭代器,节省空间,性能好。

  1. # 4、re.finditer
  2. st = "xiyuangongzi666xiyuangongzi666"
  3. res = re.finditer('xi(.*?)gong', st)
  4. print(res) # <callable_iterator object at 0x7fa0e636a3d0> # 迭代器
  5. for each in res:
  6. print(each.group())
  7. # xiyuangong
  8. # xiyuangong

问题2?

老板又给你布置了个爬虫任务,对 20 万个豆瓣电影网页,进行数据分析,需要提取出里面的标题、内容、标签等?

分析:每个网页的结构都一样,仅需要编写出一个正则表达式,就可对每个网页提取出目标数据。如果你采用 re.seach 方法, 每提取一个网页都要编译一次正则表达式,然而每个网页的正则表达式都是一样的,要是只可以编译一次就好了?

那你需要re.compile,预先编译正则表达式,后续调用不在重复编译,妥妥的效率,详细用法看下面。

2.5 re.compile

re.compile(pattern, flags=0):将一个正则表达式编译成一个正则表达式对象,该对象可调用 search、findall、match、sub、split等方法。

  1. # 5、re.compile
  2. import re
  3. st1 = "likepython"
  4. st2 = "likepython666"
  5. pattern = re.compile(r'like(.*)\d*')
  6. pattern.search(st1).group(1) # python
  7. pattern.search(st2).group(1) # python666
  8. pattern.findall(st2) # ['python666']
  9. pattern.match(st2).group(1) # python666

替换和分割

问题3?

今天老板让你写个脚本,替换一个 text 里面的广告文字,这些广告文字有个特点,就是内容随机不固定,你的解决办法是?

分析:首先排除直接使用文本编辑软件 一键替换 功能,因为广告内容不固定。或者使用 python 中的 replace 替换,也行不通,原因如上。

解决:使用正则(re.sub)匹配广告文字并替换,so 酸爽。具体请看下方

2.6 re.sub()

re.sub(pattern, repl, string, count=0, flags=0),用正则表达式匹配字符串并替换,强大而实用。

  • pattern: 正则表达式,用于匹配要替换的字符串
  • repl:要替换成的内容
  • string:要处理的字符串
  • count:要替换多少个
  1. # 6、re.sub函数
  2. text = """
  3. 我喜欢Python
  4. 人生苦短,我广告1用Python
  5. 我用广2告Python,因为3广告人生苦广4告短
  6. """
  7. re.sub('\d*广\d*告\d*', 'hi', text, count=6)
  8. # '\n我喜欢Python\n人生苦短,我hi用Python\n我用hiPython,因为hi人生苦hi短\n'

—-2021-7-14更新—-

1)使用 re 正则在指定位置插入字符串:
这个功能超级强大,请看例子。

  1. import re
  2. st = "我喜欢Python666"
  3. res = re.sub('(喜欢)', r'真\1', st)
  4. print(res) # 我真喜欢Python666
  5. res2 = re.sub(r'(喜欢)\w{3,6}', r'\1*', st)
  6. print(res2) # 我喜欢*666

解释:上方的(喜欢),是添加了一个分组,后面的 \1 表示的是,前面第一个参数匹配到的内容中第一个分组内容。所以就是把喜欢换成了真喜欢

2.7 re.split()

re.split(pattern, string, maxsplit=0, flags=0),对正则表达式匹配到的结果进行分割,返回列表。

  • pattern: 正则表达式,用于匹配要分割的字符串
  • string:要处理的字符串
  • maxsplit:最大要分割的次数。
  • flags:
  1. # 7、re.split
  2. st = "我用广2告Python,因为3广告人生苦广4告短"
  3. re.split('\d*广\d*告\d*', st) # ['我用', 'Python,因为', '人生苦', '短']
  4. re.split('(\d*广\d*告\d*)', st) # ['我用', '广2告', 'Python,因为', '3广告', '人生苦', '广4告', '短']
  5. # 使用分组的话,被分割的字符串也会显示在列表中,可用于查看。

flag

  • re.I == re.IGNORECASE:忽略大小写匹配,比如 [a-z] 匹配的是小写字母,加上 re.I 之后,大写小写均匹配。
  • re.A == re.ASCII:让 \w, \W, \b, \B, \d, \D, \s\S 只匹配ASCII,而不是Unicode。
  • re.S == re.DOTALL:让 . 可以匹配到所有字符,包括换行符。
  • re.M == re.MUTILINE:多行模式,使用 ^ 和 $ 时候,会匹配每一行的开头或者结尾,而不是之前的只匹配字符串的开头和结尾。
  • re.X == re.VERBOSE:允许在正则表达式中添加注释(#+注释),可在每一行后面添加注释。

    1. a = re.compile(r"""\d + # 匹配整数
    2. \. # 匹配小数点
    3. \d * # 匹配至少 0 个数字""", re.X)
    4. b = re.compile(r"\d+\.\d*")

其他不常用用法

  • (?#…):注释功能,#后面的内容会被忽略
  • \number:匹配数字代表的组合。
  • (?P=name):反向引用一个命名组合的内容,可用于后续匹配

    1. st2 = "gongzigongzi"
    2. re.findall(r'gong(.+?)gong(\1)', st2) # [('zi', 'zi')]
    3. re.findall(r'gong(?P<name>.+?)gong(?P=name)', st2) # ['zi']
  • (?!…):匹配 不符合的情况。这个叫 negative lookahead assertion (前视取反)。比如说, Isaac (?!Asimov) 只有后面 'Asimov' 的时候才匹配 'Isaac ' 。相当是 if 判断

  • (?!…):匹配 不符合的情况。这个叫 lookahead assertion (前视取反)。比如说, Isaac (?=Asimov) 只有后面 'Asimov' 的时候才匹配 'Isaac ' 。相当是 if 判断
  • (?:…):取消该分组。

总结:

今天学习的 re 正则,非常的重要,建议重点掌握,多多练习。多看多练多总结。

参考文章: https://docs.python.org/zh-cn/3.8/library/re.html https://www.runoob.com/python/python-reg-expressions.html