Python之路day19-20-re正则详解

223次阅读
没有评论

前言时刻

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

来来总结一波,今天学习的是最重要的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]:表示匹配任意一位数字、字母。
  • 注意:[]中的字符均不含转义功能
import re

st = "xiyuangongzi666"
re.findall("[a-z][0-9]", st)   # ['i6']
re.findall("[azi][569]", st)   # ['i6']
re.findall("[a-z]\d", st)   # ['i6']
re.findall("[a-z][\d]", st)   # ['i6']

2. 元字符

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

  • \d 相当于是[0-9],表示匹配任意 Unicode 十进制数。

  • \D 相当于是[^0-9],对 \d 取非,匹配除了十进制数以外的字符。

  • MARKDOWN_HASHb120082dcc73c364c6a937784dbb0c23MARKDOWNHASH相当于是[0-9a-zA-Z],匹配数字、字母、下划线。

  • \W 相当于是[^0-9a-zA-Z_],匹配除了数字、字母、下划线以外的字符。

  • 空白字符:空格、tab键(\t)、回车(\n),可统一使用\s表示。

  • \S :是匹配除了空白字符以外的字符。

  • . 是匹配除了换行符之外的任意一个字符

  • [^]:表示 功能,例如:[^\d]匹配所有非数字字符

  • ^:匹配一个字符串的开头

  • $:匹配一个字符串的结尾

  • | 的意思,如ab|cd,表示ab或者cd

  • ()表示分组,可以限定区域以及获取分组结果。

例子:

# 2、非^和或|
re.findall("[^0-9]\d", st)   # ['i6']
re.findall("[a-z]\d|\d\d", st)   # ['i6', '66']

# 3、简洁版
re.findall("\w\d", st)   # ['i6', '66']

# ^和$
re.findall("^[a-z]\d$", st)   # []
re.findall("^[a-z][a-z]", st)   # ['xi']
re.findall("\d\d$", st)   # ['66']

# .
st2 = """我喜欢1python
学习吧~
"""
re.findall("python.学习", st2)   # []
re.findall("python.学习", st2, re.DOTALL)   # ['python\n学习']
# re.DOTALL   让 . 可以匹配任何的字符,包括换行符

3. 量词

  1. {n} 表示对前面的正则式匹配 n 个。
  2. {n,} 表示对前面的正则式至少匹配 n 个。
  3. {n, m} 表示对前面的正则式至少匹配 n 个,最多匹配 m 个。
  4. ? 表示对前面的正则式匹配 0 个或 1 个。
  5. + 表示对前面的正则式匹配 1 个或 无穷 个。
  6. * 表示对前面的正则式匹配 0 个或 无穷 个
# 量词

import re

st = "xiyuangongzi666"
re.findall("[a-z]{2,4}[0-9]", st)   # ['ngzi6']   贪婪模式
re.findall("[a-z]{2,}[0-9]", st)   # ['xiyuangongzi6']

re.findall("\d?\d{3}", st)   # ['666']
re.findall("[a-z]{2,}[0-9]", st)   # ['xiyuangongzi6']
re.findall("[a-z]+[0-9]", st)   # ['xiyuangongzi6']
re.findall("6*[a-z]*[0-9]", st)   # ['xiyuangongzi6', '66']

2、re正则

查找:

2.1 re.findall

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

  • pattern:正则表达式
  • flags:标志位,用于控制正则表达式的匹配模式,比如:忽略大小写,等
st = "xiyuangongzi666xiyuangongzi666"

# 1.findall

re.findall('[a-z]\d', st, flags=2)   # ['i6', 'i6']
re.findall('xi(.+?)gong(.+?)\d', st)   # [('yuan', 'zi'), ('yuan', 'zi')]

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

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

2.2 re.search

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

import re
st = "xiyuangongzi666xiyuangongzi666"
# 2. re.search

res = re.search('[a-z]\d', st)   # <re.Match object; span=(11, 13), match='i6'>
res.group()   # i6

res = re.search('xi(.+?)gong(.+?)\d', st)   # <re.Match object; span=(0, 13), match='xiyuangongzi6'>

res.group(0)   # xiyuangongzi6
res.group(1)   # yuan
res.group(2)   # zi

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

2.3 re.match

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

import re

# 3、re.match 函数

st = "xiyuangongzi666xiyuangongzi666"

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

问题1?

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

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

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

2.4 re.finditer

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

# 4、re.finditer

st = "xiyuangongzi666xiyuangongzi666"
res = re.finditer('xi(.*?)gong', st)
print(res)   # <callable_iterator object at 0x7fa0e636a3d0>  # 迭代器
for each in res:
    print(each.group())

# xiyuangong
# xiyuangong

问题2?

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

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

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

2.5 re.compile

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

# 5、re.compile

import re
st1 = "likepython"
st2 = "likepython666"
pattern = re.compile(r'like(.*)\d*')
pattern.search(st1).group(1)   # python
pattern.search(st2).group(1)   # python666

pattern.findall(st2)   # ['python666']
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:要替换多少个
# 6、re.sub函数

text = """
我喜欢Python
人生苦短,我广告1用Python
我用广2告Python,因为3广告人生苦广4告短
"""

re.sub('\d*广\d*告\d*', 'hi', text, count=6)
# '\n我喜欢Python\n人生苦短,我hi用Python\n我用hiPython,因为hi人生苦hi短\n'

---2021-7-14更新---

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

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

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


2.7 re.split()

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

  • pattern: 正则表达式,用于匹配要分割的字符串
  • string:要处理的字符串
  • maxsplit:最大要分割的次数。
  • flags:
# 7、re.split

st = "我用广2告Python,因为3广告人生苦广4告短"
re.split('\d*广\d*告\d*', st)   # ['我用', 'Python,因为', '人生苦', '短']

re.split('(\d*广\d*告\d*)', st)   # ['我用', '广2告', 'Python,因为', '3广告', '人生苦', '广4告', '短']

# 使用分组的话,被分割的字符串也会显示在列表中,可用于查看。

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:允许在正则表达式中添加注释(#+注释),可在每一行后面添加注释。

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

其他不常用用法

  • (?#…):注释功能,#后面的内容会被忽略

  • \number:匹配数字代表的组合。

  • (?P=name):反向引用一个命名组合的内容,可用于后续匹配

    st2 = "gongzigongzi"
    re.findall(r'gong(.+?)gong(\1)', st2)   # [('zi', 'zi')]
    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

5
西园公子
版权声明:本站原创文章,由西园公子2021-06-10发表,共计6735字。
转载提示:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
载入中...