正则表达式的“祖先”可以一直上溯至对人类神经系统如何工作的早期研究。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等)、数以亿万计的各种应用软件中,都可以看到正则表达式优美的舞姿。
正则表达式,又称正规表示法、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。
正则表达式,是处理字符串的强大工具,具有自己特定的语法结构,能够实现字符串的检索,替换,匹配验证等功能。
描述一组字符串特征,以此来匹配特定的字符串。
学习正则表达式要从两个方面着手:正则语法和正则处理函数
模式字符串使用特殊的语法来表示一个正则表达式(pattern)
字母、数字、汉字、下划线、以及没有特殊定义的标点符号,都是“普通字符”。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。
多数字母和数字前加一个反斜杠时会拥有不同的含义。
模式 | 描述 |
---|---|
\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:
import re
str1 = r'\n123' # 它其实相当于sr1='\\n123'
pattern = '\\\\n\d{3}' # 由于一个反斜杠匹配自身需要用两个\
# pattern = r'\\n\d{3}' # 或者使用原始字符串
result = re.match(pattern, str1)
print(result.group())
模式 | 描述 |
---|---|
| | 表示或,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 次由前面的正则表达式定义的片段,贪婪方式 |
a | b |
(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]的区别
正则表达式在线测试网站
匹配邮箱地址:
贪婪匹配:尽可能匹配多的字符
非贪婪匹配:尽可能匹配少的字符
举个例子:
可以将要匹配的字符分组,通过group(index)来获取该分组匹配到的字符。也可以引用分组匹配到的字符(匹配跟被引用分组一样的内容)。
# 匹配标签中的内的标题
import re
str1 = '我是title '
pattern = r'<(.+)><(.+)>(.+)\2>\1>' # 分组默认按照顺序命名为1,2,3。\2表示引用分组2匹配到的字符,\1表示引用分组1匹配到的字符
result = re.match(pattern, str1)
print(result.group(3))
当然如果觉得数起来麻烦,我们也可以为分组起别名
import re
str1 = '我是title '
pattern = r'<(?P.+)><(?P.+)>(?P.+)(?P=num2)>(?P=num1)>'
result = re.match(pattern, str1)
print(result.group(3))
在python的内置模块re中,有以下几种函数供大家使用。
对于正则表达式,任何一种符号对于匹配来说都是至关重要的,哪怕一个空格,比如说下面的例子,出现的原因:
·在(.*?)和.*之间有无空格,有空格的话,空格本身必须匹配上空格,所以(.*?)必须去匹配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) :
search()方法不同于match()方法,它是从整个字符串中匹配第一个符合条件的字符,并不是从头开始。
re.search(pattern, string, flags=0)
参数说明:
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的差别:
用于替换字符串中的匹配项
re.sub(pattern, repl, string, count=0, flags=0)
参数说明:
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)
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\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\d+)' ,num2char,string)
print('result9:',result9)
result9: AAoHWvD]sGycY
同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)
在字符串中找到正则表达式匹配的所有子串,并返回一个列表,如果没有找到匹配的,返回空列表
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的小写字母,还有很多空字符串。这里用+就好了
compile函数用于编译正则表达式,生成一个正则表达式(Pattern)对象
re.compile(pattern[, flags])#pattern为必选参数,flags可选
flags参数为:
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的类型:
result1: ['one', 'twothree', 'Four']
result2:
result3:
result4: hhh12hhh34hhh
result5: ('hhh12hhh34hhh', 3)
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
关于正则表达式中的对象。
在字符串中找到正则表达式匹配的所有子串,并把他们作为一个迭代器返回。
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:
type(it):
i:
type(i):
i.group(): 12
i:
type(i):
i.group(): 32
i:
type(i):
i.group(): 43
i:
type(i):
i.group(): 3
result: ['12', '32', '43', '3']
type(result):
finditer和findall的区别
按照匹配的子串将字符串分割后返回列表
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']
获取网页数据后,在不使用第三方库的情况下,如何从大量的数据中提取我们想要的数据呢,正则表达式可以帮助我们快速达成目的。
确定信息在哪个请求中
通过写正则表达式匹配到:电影名,主演,上映时间,网址,评分
<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('.*?(\S+).*?(.*?)
.*?.*?(.*?).*?(.*?)'
,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()