正则表达式(python)

一、正则表达式基本介绍

1.起源

正则表达式的“祖先”可以一直上溯至对人类神经系统如何工作的早期研究。Warren McCulloch 和 Walter Pitts 这两位神经生理学家研究出一种数学方式来描述这些神经网络。

1956 年, 一位叫 Stephen Kleene 的美国数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了一篇标题为“神经网事件的表示法”的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为“正则集的代数”的表达式,因此采用“正则表达式”这个术语。

随后,发现可以将这一工作应用于使用Ken Thompson 的计算搜索算法的一些早期研究,Ken Thompson是Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的qed 编辑器。从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。具有完整语法的正则表达式使用在字符的格式匹配方面上,后来被应用到熔融信息技术领域。自从那时起,正则表达式经过几个时期的发展,现在的标准已经被ISO(国际标准组织)批准和被Open Group组织认定。

在最近的六十年中,正则表达式逐渐从模糊而深奥的数学概念,发展成为在计算机各类工具和软件包应用中的主要功能。不仅仅众多UNIX工具支持正则表达式,近二十年来,在WINDOWS的阵营下,正则表达式的思想和应用在大部分 Windows 开发者工具包中得到支持和嵌入应用!从正则表达式在Microsoft Visual Basic 6 或 Microsoft VBScript到.NET Framework中的探索和发展,WINDOWS系列产品对正则表达式的支持发展到无与伦比的高度,几乎所有 Microsoft 开发者和所有.NET语言都可以使用正则表达式。如果你是一位接触计算机语言的工作者,那么你会在主流操作系统(*nix[Linux, Unix等]、Windows、HP、BeOS等)、主流的开发语言(delphi、Scala、PHP、C#、Java、C++、Objective-c、Swift、VB、Javascript、Ruby以及Python等)、数以亿万计的各种应用软件中,都可以看到正则表达式优美的舞姿。

2.定义

正则表达式,又称正规表示法、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。

3.功能

正则表达式,是处理字符串的强大工具,具有自己特定的语法结构,能够实现字符串的检索,替换,匹配验证等功能。

4.原理

描述一组字符串特征,以此来匹配特定的字符串。

5.学习方法

学习正则表达式要从两个方面着手:正则语法正则处理函数

二、正则语法

模式字符串使用特殊的语法来表示一个正则表达式(pattern)

1、普通字符

字母、数字、汉字、下划线、以及没有特殊定义的标点符号,都是“普通字符”。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。

2、转义字符

多数字母和数字前加一个反斜杠时会拥有不同的含义。

模式描述
\w匹配数字字母下划线
\W匹配非数字字母下划线
\s匹配任意空白字符,等价于 [\t\n\r\f]。
\S匹配任意非空字符
\d匹配任意数字,等价于 [0-9]。
\D匹配任意非数字
\A匹配字符串开始\
\Z匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z匹配字符串结束
\G匹配最后匹配完成的位置。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\n, \t, 等。匹配一个换行符。匹配一个制表符, 等
\1…\9匹配第n个分组的内容。
\10匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

tips:

  1. 匹配’‘本身时需要反斜杠来转义,两个反斜杠:’\‘表示匹配’'自身
  2. 由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r’\t’,等价于 \t )匹配相应的特殊字符。
import re


str1 = r'\n123'  # 它其实相当于sr1='\\n123'
pattern = '\\\\n\d{3}'   # 由于一个反斜杠匹配自身需要用两个\
# pattern = r'\\n\d{3}'  # 或者使用原始字符串  
result = re.match(pattern, str1)
print(result.group())

3、特殊字符

模式描述
|表示或,pattern1|pattern2表示匹配正则1或者正则2
^匹配字符串的开头
$匹配字符串的末尾。
.匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[…]用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’
[^…]不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re*匹配0个或多个的表达式。
re+匹配1个或多个的表达式。
re?匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n}匹配n个前面表达式。例如,"o{2}“不能匹配"Bob"中的"o”,但是能匹配"food"中的两个o。
re{ n,}精确匹配n个前面表达式。例如,"o{2,}“不能匹配"Bob"中的"o”,但能匹配"foooood"中的所有o。"o{1,}“等价于"o+”。"o{0,}“则等价于"o*”。
re{ n, m}匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
ab
(re)匹配括号内的表达式,也表示一个组
(?imx)正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx)正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: re)类似 (…), 但是不表示一个组
(?imx: re)在括号中使用i, m, 或 x 可选标志
(?-imx: re)在括号中不使用i, m, 或 x 可选标志
(?#…)注释.
(?= re)前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re)前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功。
(?> re)匹配的独立模式,省去回溯。

实例:

实例描述
python匹配 “python”.
[Pp]ython匹配 “Python” 或 “python”
rub[ye]匹配 “ruby” 或 “rube”
[aeiou]匹配中括号内的任意一个字母
[0-9]匹配任何数字。类似于 [0123456789]
[a-z]匹配任何小写字母
[A-Z]匹配任何大写字母
[a-zA-Z0-9]匹配任何字母及数字
[^aeiou]除了aeiou字母以外的所有字符
[^0-9]匹配除了数字外的字符

注意:[A-z]和[A-Za-z]两个表达式表示的范围是不一样的,[A-z]的范围更大一点,除了包含大小写字母,还包括几个特殊字符[ \ ] ^ _
具体请看:正则表达式中[A-z]和[a-zA-Z]的区别

4、练习网站

正则表达式在线测试网站

比如,匹配字符串中的网址。
在这里插入图片描述

匹配邮箱地址:

在这里插入图片描述

页面往下翻:还有很多的示例,常见的用法等
在这里插入图片描述

5、贪婪匹配和非贪婪匹配

贪婪匹配:尽可能匹配多的字符
非贪婪匹配:尽可能匹配少的字符
举个例子:

  1. 贪婪匹配:
    在这里插入图片描述
  2. 非贪婪匹配
    在这里插入图片描述
    解析:
    ’.‘表示匹配除换行符以外的任意字符
    ‘表示匹配前面那个字符0-n次
    ’.
    ‘表示匹配任意字符0-n次
    在贪婪模式下,只要满足匹配规则,.* 尽可能的匹配多的字符
    非贪婪模式,在.*后面加上?表示非贪婪,在满足匹配规则的前提下,尽可能匹配少的字符。

6、分组

可以将要匹配的字符分组,通过group(index)来获取该分组匹配到的字符。也可以引用分组匹配到的字符(匹配跟被引用分组一样的内容)。

# 匹配标签中的内的标题
import re
str1 = '<html><title>我是title</title></html>'
pattern = r'<(.+)><(.+)>(.+)</\2></\1>'  # 分组默认按照顺序命名为1,2,3。\2表示引用分组2匹配到的字符,\1表示引用分组1匹配到的字符
result = re.match(pattern, str1)
print(result.group(3))

当然如果觉得数起来麻烦,我们也可以为分组起别名

  • 起别名:在分组中加上?P
  • 引用时:(?P=name)
    注意P时大写
import re
str1 = '<html><title>我是title</title></html>'
pattern = r'<(?P<num1>.+)><(?P<num2>.+)>(?P<num3>.+)</(?P=num2)></(?P=num1)>'
result = re.match(pattern, str1)
print(result.group(3))

三、正则函数

在python的内置模块re中,有以下几种函数供大家使用。

1、match()

对于正则表达式,任何一种符号对于匹配来说都是至关重要的,哪怕一个空格,比如说下面的例子,出现的原因:
·在(.*?).*之间有无空格,有空格的话,空格本身必须匹配上空格,所以(.*?)必须去匹配smarter才能满足要求
#!/usr/bin/python3
import re
 
line = "Cats are smarter than dogs"
# .* 表示任意匹配除换行符(\n、\r)之外的任何单个或多个字符
# (.*?) 表示"非贪婪"模式,只保存第一个匹配到的子串
matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)
result = re.match(r'(.*)are(.*?).*',line,re.M|re.I)

print ("matchObj.group() : ", matchObj.group())
print ("matchObj.group(1) : ", matchObj.group(1))
print ("matchObj.group(2) : ", matchObj.group(2))

print ("result.group() : ", result.group())
print ("result.group(1) : ", result.group(1))
print ("result.group(2) : ", result.group(2))
matchObj.group() :  Cats are smarter than dogs
matchObj.group(1) :  Cats
matchObj.group(2) :  smarter
result.group() :  Cats are smarter than dogs
result.group(1) :  Cats 
result.group(2) :  

2、search()

用途

search()方法不同于match()方法,它是从整个字符串中匹配第一个符合条件的字符,并不是从头开始。

语法

re.search(pattern, string, flags=0)

参数说明:

  1. pattern:匹配的正则表达式
  2. string:要匹配的字符串。
  3. flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

示例

import re 
result1 = re.search('www','www.baidu.com',re.I)
result2 = re.search('com','www.baidu.com',re.I)
result3 = re.match('www','www.baidu.com',re.I)
result4 = re.match('com','www.baidu.com',re.I)
print('result1:',result1.group())
print('result2:',result2.group())
print('result3:',result3.group())
print('result4:',result4)
result1: www
result2: com
result3: www
result4: None

从上面的例子就可以看出,match和search的差别:

  • match表示从原字符串开头开始匹配,如果开头不匹配,那么即使后面有满足条件的字符也不能匹配,返回none。
  • search是先扫描整个字符串,如果有符合的则匹配,没有就返回none。

3、sub()

用途

用于替换字符串中的匹配项

语法

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

参数说明:

  1. pattern : 正则中的模式字符串。
  2. repl : 替换的字符串,也可为一个函数
  3. string : 要被查找替换的原始字符串。
  4. count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
  5. flags : 编译时用的匹配模式,数字形式。
    前三个为必选参数,后两个为可选参数。
    说明:用pattern去匹配原字符串,再用repl去替换匹配到的字符或者字符串,然后返回被repl替换后的原字符串。

示例

import re 
string = '  电话号码:123-456-789,   网址:www#baidu#com'
#用空字符串去替换string中的'-'
result1 = re.sub('-','',string)
#返回的结果是字符串对象
print(type(result1))
#用'.'去替换result1中的'#'
result2 = re.sub('#','.',result1) 
#count参数设置替换的次数
result3 = re.sub('#','.',result1,1)
#用空字符串替换空白字符
result4 = re.sub(r'\s','',result2)
#用空字符串替换非数字
result5 = re.sub(r'\D','',string)
#用1xxxxxxxx替换电话号码
result6 = re.sub(r'\d+','1xxxxxxxx',result1)
print('result2:',result2)
print('result3:',result3)
print('result4:',result4)
print('result5:',result5)
print('result6:',result6)
<class 'str'>
result2:   电话号码:123456789,   网址:www.baidu.com
result3:   电话号码:123456789,   网址:www.baidu#com
result4: 电话号码:123456789,网址:www.baidu.com
result5: 123456789
result6:   电话号码:1xxxxxxxx,   网址:www#baidu#com

repl也可以为一个函数

#将字符串中的数字乘以2
def set_double_value(matched):
    back = int(matched.group('value'))
    return str(back*2)
result7 = re.sub('\d+','','sad24123g4hj2g3hg5hj23gh2g3413')
print('result7:',result7)
result8 = re.sub('(?P<value>\d+)',set_double_value,'sad24123g4hj2g3hg5hj23gh2g3413')
print('result8:',result8)
result7: sadghjghghjghg
result8: sad48246g8hj4g6hg10hj46gh4g6826
解析: 
1. 首先通过sub()方法查找到对应的子串,返回一个Match对象,match对象需要通过group()函数获取子串。 
2. 自定义组名,定义组名为value,**(?P<value>\d+)**
3. repl函数接收到match对象,并通过组名获取匹配到的子串
4. 对子串处理后return回来,传入原字符串。

举个例子,将字符串中的数字转换成字母

def num2char(matched):
    back = matched.group('diy')
    return chr(int(back))
#A-z的ASCII码范围是65-122
string = 'A65oH87vD93sG121cY'
result9 = re.sub('(?P<diy>\d+)',num2char,string)
print('result9:',result9)
result9: AAoHWvD]sGycY

4、subn()

用途

同sub,不过,subn()返回的结果为一个元组,元组的第一个元素是替换后的结果,第二个元素是替换的次数。与sub()比起来,subn()能统计计算的次数。

语法

同sub()

示例

result1 = re.subn('-','',string)
print('result1:',result1)
result4 = re.subn(r'\s','',result2)
print('result4:',result4)
result1: ('  电话号码:123456789,   网址:www#baidu#com', 2)
result4: ('电话号码:123456789,网址:www.baidu.com', 5)

5、findall()

用途

在字符串中找到正则表达式匹配的所有子串,并返回一个列表,如果没有找到匹配的,返回空列表

语法

re.findall(pattern, string, flags=0)

示例

string = 'runoob 123 google 456'
result = re.findall('\d+',string)
print('result:',result)
result: ['123', '456']

findall与match、search的区别。
match、search都只是匹配一次,而findall是匹配所有符合条件的子串
注意:findall()与*配合使用时,会出现匹配很多空字符串的情况

answer_str = re.findall('[a-z]*',answer[1],re.S)

这里匹配时,不仅有a-z的小写字母,还有很多空字符串。这里用+就好了

6、compile()

用途

compile函数用于编译正则表达式,生成一个正则表达式(Pattern)对象

语法

re.compile(pattern[, flags])#pattern为必选参数,flags可选

flags参数为:

  1. re.I 忽略大小写
  2. re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
  3. re.M 多行模式,影响^和 $
  4. re.S 即为’ . ‘并且包括换行符在内的任意字符(’ . '不包括换行符)
  5. re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
  6. re.X 为了增加可读性,忽略空格和’ # '后面的注释

示例

import re
string = 'one12twothree34Four'
pattern_obj = re.compile(r'[a-z]+',re.I)
print('pattern_obj的类型:',type(pattern_obj))
result1 = pattern_obj.findall(string)
print('result1:',result1)
result2 = pattern_obj.match(string)
print('result2:',result2)
result3 = pattern_obj.search(string)
print('result3:',result3)
result4 = pattern_obj.sub('hhh',string)
print('result4:',result4)
result5 = pattern_obj.subn('hhh',string)
print('result5:',result5)
print(type(result2))

#result2是一个match对象
print('result2.pos():',result2.start()) #获取子串的开始索引
print('result2.end():',result2.end()) #获取子串的结束索引+1
print('result2.span():',result2.span())#获取匹配子串的开始和结束索引
print('result2.group():',result2.group()) #获取子串的匹配内容

#group()函数的索引是从1开始的。group(0)和group()返回的是整个匹配到的子串
string1 = 'Cats are smarter than Dogs'
pattern1 = re.compile('\w+ are (.*?) than(.*)',re.I)
result6 = pattern1.match(string1)
print('result6.group():',result6.group())
print('result6.group(1):',result6.group(1))
print('result6.group(2):',result6.group(2))
pattern_obj的类型: <class 're.Pattern'>
result1: ['one', 'twothree', 'Four']
result2: <re.Match object; span=(0, 3), match='one'>
result3: <re.Match object; span=(0, 3), match='one'>
result4: hhh12hhh34hhh
result5: ('hhh12hhh34hhh', 3)
<class 're.Match'>
result2.pos(): 0
result2.end(): 3
result2.span(): (0, 3)
result2.group(): one
result6.group(): Cats are smarter than Dogs
result6.group(1): smarter
result6.group(2):  Dogs

关于正则表达式中的对象。

  1. match对象:match()方法和search()方法返回的都是match对象,match对象的函数有:
  • group()
  • span()
  • start()
  • end()
  1. pattern对象,compile()函数返回的是正则表达式(pattern)对象,pattern对象的方法有如下:
  • search()
  • match()
  • find()
  • sub()
  • subn()

7、finditer()

用途

在字符串中找到正则表达式匹配的所有子串,并把他们作为一个迭代器返回。

语法

re.finditer(pattern, string, flags=0)

示例

import re 
it = re.finditer(r"\d+","12a32bc43jf3") #返回一个迭代器对象
print('it:',it)
print('type(it):',type(it))
#遍历迭代器对象
for i in it:
    print('i:',i)
    print('type(i):',type(i)) #i是一个Match对象
    print('i.group():',i.group()) #用group()方法提取子串
    
#findall
result = re.findall(r"\d+","12a32bc43jf3")
print('result:',result)
print('type(result):',type(result)) #返回一个列表
it: <callable_iterator object at 0x0696B448>
type(it): <class 'callable_iterator'>
i: <re.Match object; span=(0, 2), match='12'>
type(i): <class 're.Match'>
i.group(): 12
i: <re.Match object; span=(3, 5), match='32'>
type(i): <class 're.Match'>
i.group(): 32
i: <re.Match object; span=(7, 9), match='43'>
type(i): <class 're.Match'>
i.group(): 43
i: <re.Match object; span=(11, 12), match='3'>
type(i): <class 're.Match'>
i.group(): 3
result: ['12', '32', '43', '3']
type(result): <class 'list'>

finditer和findall的区别

  1. finditer 返回一个迭代器对象
  2. findall 返回一个列表
    他们的本质区别就在于迭代器和列表的区别,总的来说迭代器比列表效率更高,更安全。

8、split()

用途

按照匹配的子串将字符串分割后返回列表

语法

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

参数说明:
maxsplit:分割次数,默认为0,即分割无限次

示例

import re 
string = '我#爱$学%Python'
result1 = re.split(r'\W+',string)
print('result1:',result1)
#指定分割次数
result2 = re.split(r'\W+',string,2)
print('result2:',result2)
result1: ['我', '爱', '学', 'Python']
result2: ['我', '爱', '学%Python']

四、在爬虫中的应用

获取网页数据后,在不使用第三方库的情况下,如何从大量的数据中提取我们想要的数据呢,正则表达式可以帮助我们快速达成目的。

实例:爬取猫眼电影网top100榜

确定信息在哪个请求中

在这里插入图片描述

通过写正则表达式匹配到:电影名,主演,上映时间,网址,评分

<a href="(\S+)" title="(\S+)".*?<p class="star">.*?(\S+).*?<p class="releasetime">(.*?)</p>.*?<p class="score">.*?<i class="integer">(.*?)</i>.*?<i class="fraction">(.*?)</i>

在这里插入图片描述

#电影名,主演,上映时间,网址,评分
import requests
import re 
import csv

#翻页由参数offset控制,第一页offset = 0,第二页offset = 10,这个值是排行榜的开始索引。
headers = {
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36',
    'referer':'https://maoyan.com/board'
}
file = open('maoyan_movie.csv','w',encoding='gbk')
csv_file = csv.writer(file)
for num in range(0,91,10):
    url = 'https://maoyan.com/board/4?offset={}'.format(num)
    res = requests.get(url,headers = headers)
    #一定要慎用贪婪匹配。
    info_list = re.findall('<a href="(\S+)" title="(\S+)".*?<p class="star">.*?(\S+).*?<p class="releasetime">(.*?)</p>.*?<p class="score">.*?<i class="integer">(.*?)</i>.*?<i class="fraction">(.*?)</i>',res.text,re.S)
    #print('info_list:',info_list)
    format_list = [[i[1],i[2],i[3],'https://maoyan.com'+i[0],i[4]+i[5]] for i in info_list]
    #print(format_list)
    csv_file.writerow(format_list)
    #print('='*30+'已完成'+str(num+10)+'个电影'+'='*30)

file.close()


  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值