正则表达式在线测试工具: http://tool.chinaz.com/regex/
本文大量参考:https://www.cnblogs.com/Eva-J/articles/7228075.html#_label10
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
一、字符组:[字符组]
在同一个位置可能出现的各种的字符组成了一个字符串,在正则表达式中用[ ]表示,字符分为很多类,比如数字、字母、标点等等。
例如你现在要求在一个位置上“只能出现一个数字”,那么这个位置上的字符只能是0、1、2…9这10个数之一。
正则 | 待匹配字符 | 匹配结果 | 说明 |
---|---|---|---|
[0123456789] | 8 | True | 8出现在字符组内,可以匹配 |
[0123456789] | a | False | a不在在字符组内,不能匹配 |
[0-9] | 7 | True | 简化写法,用-表示范围 |
[a-z] | s | True | [a-z]表示匹配任意一个小写字母 |
[0-9a-fA-F] | e | True | 可以匹配数字,大小写形式的a~f,用来验证十六进制字符 |
二、元字符
元字符 | 匹配内容 |
---|---|
. | 匹配除换行符一维的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白字符 |
\d | 匹配数字 |
\W | 匹配非字母或数字或下划线 |
\D | 匹配非数字 |
\S | 匹配非空白符 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\b | 匹配一个单词的结尾 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
a | b |
() | 匹配括号内的表达式,也表示一个组 |
[…] | 匹配字符组中的字符 |
^… | 匹配除了字符组中字符的所有字符 |
三、量词
量词 | 用法说明 |
---|---|
? | 重复零次或一次 |
+ | 重复一次或者多次 |
* | 重复零次或者多次 |
{n} | 重复n次 |
{n,} | 重复n次货更多次 |
{n,m} | 重复n到m次 |
一些例子
. ^ $
正则 | 待匹配字符 | 匹配结果 |
---|---|---|
小. | 小杰小海小梅 | 小杰 小海 小梅 |
^小. | 小杰小海小梅 | 小杰 |
小.$ | 小杰小海小梅 | 小梅 |
* + ? {}
正则 | 待匹配字符 | 匹配结果 |
---|---|---|
小.? | 小杰和小海龟和小梅大大 | 小杰 小海龟 小梅大大 |
小.* | 小杰和小海龟和小梅大大 | 小杰和小海龟和小梅大大 |
小.+ | 小杰和小海龟和小梅大大 | 小杰和小海龟和小梅大大 |
小.{1,2} | 小杰和小海龟和小梅大大 | 小杰和 小海龟 小梅大 |
注意,前面的 *,+,?等量词都是默认贪婪匹配,量词后面加?可以将其变成惰性匹配。
正则 | 待匹配字符 | 匹配结果 |
---|---|---|
小.&? | 小杰和小海龟和小梅大大 | 小 小 小 |
字符集[ ] [ ^ ]
正则 | 待匹配字符 | 匹配结果 |
---|---|---|
小[杰海龟梅大大]* | 小杰和小海龟和小梅大大 | 小杰 小海龟 小梅大大 |
小[^和]* | 小杰和小海龟和小梅大大 | 小杰 小海龟 小梅大大 |
[\d] | 520acdh5 | 5 2 0 5 |
[\d]+ | 520acdh5 | 520 5 |
分组()与 | [^]
身份证号码是一个长度为15或者18个字符的字符串,如果是15位那么全部都由数字组成,首位不能为0;如果是18位,那么前17位全部是数字,末位可能是数字或者x,下面用正则来表示:
正则 | 说明 |
---|---|
^[1-9]\d{13,16}[0-9x]$ | 可以匹配一个正确的身份证,但是16、17位的错误身份证也会被匹配 |
^[1-9]\d{14}{\d{2}[0-9x]}?$ | 完美匹配,()表示分组,整体约束\d{2}[0-9x]出现的次数为0-1次 |
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ | 表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14} |
四、转义符 \
在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s等,如果要在正则中匹配正常的"\n"而不是"换行符"就需要对""进行转义,变成’\’。
在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\n",字符串中要写成’\n’,那么正则里就要写成"\\n",这样就太麻烦了。这个时候我们就用到了r’\n’这个概念,此时的正则是r’\n’就可以了。
正则 | 待匹配字符 | 匹配结果 | 说明 |
---|---|---|---|
\n | \n | False | 因为在正则表达式中\是有特殊意义的字符,所以要匹配\n本身,用表达式\n无法匹配 |
\\n | \n | True | 转义\之后变成\,即可匹配 |
‘\\\\n’ | ‘\\n’ | True | 如果在python中,字符串中的’‘也需要转义,所以每一个字符串’'又需要转义一次 |
r’\\n’ | r’\n’ | True | 在字符串之前加r,让整个字符串不转义 |
五、贪婪匹配
贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配
正则 | 待匹配字符 | 匹配结果 | 说明 |
---|---|---|---|
<.*> | <script>…<script> | <script>…<script> | 默认为贪婪匹配模式,会匹配尽量长的字符串 |
<.*?> | <script>…<script> | <script> <script> | 加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串 |
几个常用的非贪婪模式搭配
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
.*?的用法
. 是任意字符
* 是取 0 至 无限长度
? 是非贪婪模式。
何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在:
.*?x
就是取前面任意长度的字符,直到一个x出现
六、re模块下的常用方法
import re
'''findall 没有匹配到返回一个空列表,search和match没有匹配到就会报错'''
ret = re.findall('a', 'jacky lu hallo') # 返回所有满足匹配条件的结果,放在列表里
print(ret) # 结果 : ['a', 'a']
ret = re.search('a', 'jacky lu hallo').group()
print(ret) # 结果 : 'a'
# 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
# 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。
ret = re.match('a', 'abc').group() # 用法和search一样,不过match只能从字符串的开头开始匹配
print(ret) # 结果 : 'a'
ret = re.split('[ab]', 'abcd') # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割
print(ret) # ['', '', 'cd']
ret = re.sub('\d', 'J', '1jacky89ha', 1) # 将数字替换成'H',参数1表示只替换一次
print(ret) # Jjacky89ha
ret = re.subn('\d', 'J', '1jacky89ha') # 将数字替换成'H',会返回一个元祖,(替换的结果,替换了多少次)
print(ret) # ('JjackyJJha', 3)
compiled_obj = re.compile('\d{2}') # 将正则表达式编译为一个正则表达式对象,规则要匹配的是2个数字
ret = compiled_obj.search('jacky56haha') # 正则表达式对象调用search,参数为待匹配的字符串
print(ret.group()) # 结果 : 56
import re
ret = re.finditer('\d', 'ja8xasca7856a') # finditer返回一个存放匹配结果的迭代器
print(ret) # <callable_iterator object at 0x000001AF202BC2B0>
print(next(ret).group()) # 8 查看第一个结果
print(next(ret).group()) # 7 查看第二个结果
print([i.group() for i in ret]) # ['8', '5', '6'] 查看剩余的结果
七、need注意的一些内容
-
| 从左到右匹配,只是匹配上了就不继续匹配了,应该把长的放在前面。
[^] 除了字符组内的其他都匹配 -
findall 的优先级查询
import re
# 使用findall用来匹配含分组的正则表达式时, 会优先把匹配结果组里内容返回
ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com')
print(ret) # ['oldboy']
# 如果想要匹配结果,分组内前加'?:'取消权限即可
ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com')
print(ret) # ['www.oldboy.com']
- split的优先级查询
import re
# 没有()的没有保留所匹配的项
ret = re.split('\d+', 'jacky7haha78wei')
print(ret) # ['jacky', 'haha', 'wei']
# 有()的却能够保留了匹配的项,这个在某些需要保留匹配部分的使用过程是非常重要的。
ret = re.split('(\d+)', 'jacky7haha78wei')
print(ret) # ['jacky', '7', 'haha', '78', 'wei']
八、综合练习与拓展
- 匹配标签
import re
ret = re.search(r"<(?P<tag_name>\w+)>\w+</(?P=tag_name)>", "<h1>hello</h1>")
# 还可以在分组中利用?<name>的形式给分组起名字
# 获取的匹配结果可以直接用group('名字')拿到对应的值
print(ret) # <_sre.SRE_Match object; span=(0, 14), match='<h1>hello</h1>'>
print(ret.group('tag_name')) # 结果 :h1
print(ret.group()) # 结果 :<h1>hello</h1>
ret = re.search(r"<(\w+)>\w+</\1>", "<h1>hello</h1>")
# 如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致
# 获取的匹配结果可以直接用group(序号)拿到对应的值
print(ret.group(1)) # 结果 :h1
print(ret.group()) # 结果 :<h1>hello</h1>
- 匹配整数
import re
ret = re.findall(r"\d+", "1-2*(60+(-40.35/5)-(-4*3))")
print(ret) # ['1', '2', '60', '40', '35', '5', '4', '3']
ret = re.findall(r"-?\d+\.\d*|(-?\d+)", "1-2*(60+(-40.35/5)-(-4*3))")
print(ret) # ['1', '-2', '60', '', '5', '-4', '3']
ret.remove("")
print(ret) # ['1', '-2', '60', '5', '-4', '3']
- 数字匹配
if: ^\d{4}-0?[1-9]|1[0-2]$
1 匹配一段文本中的每行的邮箱
https://blog.csdn.net/qq_41823444/article/details/100540347
2 匹配一段文本中的每行的时间字符串,比如:‘1990-07-12’
分别取出1年的12个月(^(0?[1-9]|1[0-2])$)、
一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$
3 匹配qq号。(腾讯QQ号从10000开始) [1,9][0,9]{4,}
4 匹配一个浮点数。 ^(-?\d+)(.\d+)?$ 或者 -?\d+.?\d*
5 匹配汉字。 ^[\u4e00-\u9fa5]{0,}$
6 匹配出所有整数
- 爬虫练习
import requests
import re
import json
def getPage(url):
response=requests.get(url)
return response.text
def parsePage(s):
com=re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S)
ret=com.finditer(s)
for i in ret:
yield {
"id":i.group("id"),
"title":i.group("title"),
"rating_num":i.group("rating_num"),
"comment_num":i.group("comment_num"),
}
def main(num):
url='https://movie.douban.com/top250?start=%s&filter='%num
response_html=getPage(url)
ret=parsePage(response_html)
print(ret)
f=open("move_info7","a",encoding="utf8")
for obj in ret:
print(obj)
data=json.dumps(obj,ensure_ascii=False)
f.write(data+"\n")
if __name__ == '__main__':
count=0
for i in range(10):
main(count)
count+=25
简化版
import re
import json
from urllib.request import urlopen
def getPage(url):
response = urlopen(url)
return response.read().decode('utf-8')
def parsePage(s):
com = re.compile(
'<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
'.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)
ret = com.finditer(s)
for i in ret:
yield {
"id": i.group("id"),
"title": i.group("title"),
"rating_num": i.group("rating_num"),
"comment_num": i.group("comment_num"),
}
def main(num):
url = 'https://movie.douban.com/top250?start=%s&filter=' % num
response_html = getPage(url)
ret = parsePage(response_html)
print(ret)
f = open("move_info7", "a", encoding="utf8")
for obj in ret:
print(obj)
data = str(obj)
f.write(data + "\n")
count = 0
for i in range(10):
main(count)
count += 25
简化版
flags
flags有很多可选值:
re.I(IGNORECASE)忽略大小写,括号内是完整的写法
re.M(MULTILINE)多行模式,改变^和$的行为
re.S(DOTALL)点可以匹配任意字符,包括换行符
re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用
re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag
re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释
作业
实现能计算类似
1 - 2 * ( (60-30 +(-40/5) * (9-25/3 + 7 /399/42998 +10 * 568/14 )) - (-43)/ (16-3*2) )等类似公式的计算器程序