正则校验:我需要的正则表达式知识
正则表达式由正则表达式引擎提供支持,不同编程环境有不同的正则表达式引擎,在实际使用正则表达式的过程中会有一些差别;
什么是正则表达式
正则表达式是用于描述匹配复杂字符串规则的工具,一个正则表达式对应着一个文本规则;
EP1:查找一段文本中的字符串hq,这是一个姓名的缩写,需要是单独的一个单词
- 精确匹配字符串hq,处理正则时一般支持忽略大小写的选项,如果选中,那么就可以匹配到hq,HQ,Hq,hQ四种;
- 如果直接查找字符串HQ,那么可能匹配到其他包含该字符串的单词,而我们要的是精确匹配;
//精确匹配单词hq
"\bhq\b"
\b
是正则支持的特殊代码,也叫元字符(metacharacter),表示单词的分界处,即单词的开头和结尾位置;
EP1-1:hq后面不远处跟着一个dm,应该使用如下正则表达式
//hq 任意个任意字符(但不能有换行) 最后还是dm这个单词
"\bhq\b.*\bdm\b"
.
也是一个元字符,可以匹配除了换行符以外的任意字符;
*
同样是元字符,它不代表字符或位置,而是代表数量,表示*
前面的内容可以连续重复使用任意次(可能0次);
EP2:以0开头 然后是两个数字 然后是一个连字符“-”,最后是8个数字
"0\d\d-\d\d\d\d\d\d\d\d"
\d
元字符,匹配一位数字;
-
不是元字符 只匹配自己;
EP2-1:为了避免重复,可以修改正则如下
"0\d{2}-\d{8}"
这里{n}
表示前面的\d
必须连续重复匹配2次/8次;
元字符
现在我们已经知道了\b
、.
、*
、\d
这几个元字符,还有更多,我们结合例子一起看;
\s
元字符,匹配任意的空白符,包括 空格、制表符(Tab)、换行符、中文全角空格等;
\w
元字符,匹配字母或数字或下划线或汉字等;
EP3:匹配以字母a开头的单词
"\ba\w*\b"
EP3-1:匹配刚好6个字符的单词
"\b\w{6}\b"
EP4:匹配一个或多个连续的数字
"\d+"
+
元字符,和*
类似,表示匹配重复1次或更多次;
EP5:填写的QQ号必须为5到12位的数字
"^\d{5,12}$"
^
元字符,匹配字符串的开始位置;
$
元字符,匹配字符串的结束位置;
这里使用了^
和$
,则表示校验的整个字符串从开始到结束,都要用\d{5,12}
进行匹配;如果不使用,只能保证字符串中包含5到12位的连续数字;
这里的{5,12}
表示重复次数不少于5,不大于12;
处理正则时一般支持处理多行的选项,如果选中,^
和$
就表示匹配行的开始和结束位置;【?
】
JavaScript中的正则表达式
参考JavaScript RegExp 对象;
字符转义
如果要查找元字符本身的话,需要使用\
来取消这些符号的特殊意义,如\.
、\*
、\\
;
例如:匹配C:\Windows 使用 C:\\Windows
;匹配deerflower.cn使用deerflower\.cn
;
重复
代码/语法 |
说明 |
* |
重复0次或多次 |
+ |
重复1次或多次 |
? |
重复0次或1次 |
{n} |
重复n次 |
{n,} |
重复n次或多次 |
{n,m} |
重复n次到m次 |
例如:匹配一行/整段字符串的第一个单词 使用^\w+
;
字符类
对于数字 字母 空白符等已经有了对应这些字符集合的元字符,但如果想匹配自定义的字符集合,可以使用方括号;
EP6:匹配任何一个英文元音字母
"[aeiou]"
EP6-1:匹配三个标点符号中的一个
"[.?!]"
EP7:匹配字符范围 0-9 中的一个
"[0-9]"//与\d完全一致
EP7-1:匹配字符范围 0-9大小写字母 中的一个
"[a-z0-9A-Z]"//只考虑英文的话,与\w完全一致
请尝试分析下这个正则:\(?0\d{2}[) -]?\d{8}
(
和 )
都是元字符,所以这里用了转义;
- 首先是一个转义字符
\(
,它能出现0次或1次(?
),然后是一个0,后面跟着2个数字(\d{2}
),然后是)
或-
或空格
中的一个,它出现1次或不出现(?
),最后是8个数字(\d{8}
)
上边这个正则会匹配到010)12345678
或(022-87654321
这样的格式(我们所希望的是匹配(010)88886666
,或022-22334455
,或029 12345678
这样的格式),解决这个问题需要用到分支条件;
分支条件
分支条件指有几种规则,满足其中任意一个就算匹配成功,具体使用|
把不同的规则分隔开;
分析正则 0\d{2}-\d{8}|0\d{2}-\d{7}
- 可以匹配到 两种以连字符分隔的电话号码,一种三位区号、一种四位区号;
需要注意:使用分支条件时,要注意各个条件的顺序;
分析正则:\d{5}-\d{4}|\d{5}
,如果其条件顺序倒置会发生什么;
- 如果倒置为
\d{5}|\d{5}-\d{4}
,就是能匹配到5位数字(以及9位数字的前5位);
- 匹配条件分支时,会从左到右测试每个条件,如果满足某个分支的话,就不会再去匹配其他条件了;
分组
重复单个字符时,需要在字符后加上限定符;要重复多个字符串,则需要使用小括号里指定子表达式(也叫分组);小括号
在这里是元字符,表示分组;
分析正则:(\d{1,3}\.){3}\d{1,3}
,这是一个简单的IP地址匹配表达式;
-
(\d{1,3}\.){3}
匹配三位数字加上一个英文句号(整体作为一个分组)重复3次;
- 最后再加上一个1到3位的数字;
但是他可能会匹配到不正确的IP地址,所以实际匹配IP地址会用到这个下面这个冗长的分组:
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
仔细分析下,现在我们已经能解读出这个正则表达式所匹配的含义了;
反义
如查找除xxx以外的任意字符,需要用到反义;
反义语法 |
描述 |
\W |
匹配任意不是 字母数字下划线汉字的字符; |
\S |
匹配任意不是 空白符的字符; |
\D |
匹配任意非 数字的字符; |
\B |
匹配不是单词开头或结束的位置; |
[^x] |
匹配除了x以外的任意字符; |
[^aeiou] |
匹配除了aeiou以外的任意字符; |
反义示例:
-
\S+
匹配 不包含空白字符的字符串;
-
<a[^>]+>
匹配 用尖括号括起来的以a开头的字符串;
- 向后引用:通过组号/组名可以实现向后引用之前分组已经匹配的内容;
- 零宽断言:零宽度正预测先行断言
(?=exp)
,零宽度正回顾后发断言(?<=exp)
;
- 负向零宽度断言:零宽度负预测先行断言
(?!exp)
,零宽度负回顾后发断言(?<!exp)
;
- 注释:
(?#comment)
;
- 贪婪与懒惰:表5.懒惰限定符;
- 处理选项:IgnoreCase(忽略大小写) Multiline(多行模式) Singleline(单行模式) IgnorePatternWhitespace(忽略空白) ExplicitCapture(显式捕获);
- 平衡组/递归匹配;
- 其他内容:表7.尚未详细讨论的语法;
这些内容个人觉得相对高级,读了两遍有了一个大概的认识,后续有相关应用再予以深入。
应用实例:
[abc] a或b或c
. 任意单个字符
a? 零个或一个a
[^abc] 任意不是abc的字符
\s 空格
a* 零个或多个a
[a-z] a-z的任意字符
\S 非空格
a+ 一个或多个a
[a-zA-Z] a-z或A-Z
\d 任意数字
a{n} 正好出现n次a
^ 一行开头
\D 任意非数字
a{n,} 至少出现n次a
$ 一行末尾
\w 任意字母数字或下划线
a{n,m} 出现n-m次a
(...) 括号用于分组
\W 任意非字母数字或下划线
a*? 零个或多个a(非贪婪)
(a|b) a或b
\b 单词边界
(a)...\1 引用分组
(?=a) 前面有a
(?!a) 前面没有a
\B 非单词边界
import re
originstr = 'http://upos-sz-mirrorhw.bilivideo.com/upgcxcode/95/97/25aa6429795/256429795-1-32.flv?e=ig8euxZM2rNcNbN37zUVhoMgnwuBhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNo8g2ENvNo8i8o859r1qXg8xNEVE5XREto8GuFGv2U7SuxI72X6fTr859r1qXg8gNEVE5XREto8z5JZC2X2gkX5L5F1eTX1jkXlsTXHeux_f2o859IB_&uipk=5&nbs=1&deadline=1605519071&gen=playurl&os=hwbv&oi=737348678&trid=6dc40e32c716403881b67bf67f596472u&platform=pc&upsig=1a8747c29f3fbebd9754e8517eb2107c&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=0&orderid=0,3&agrr=1&logo=80000000'
# 匹配以/开头 以?结尾(由于两者是特殊标记 需要进行转义)的字符 且其中不包含/
bstr = r'\/([^\/]*)\?'
b = re.compile(bstr , re.DOTALL)
str_list = b.findall(originstr)
print(str_list)
按固定格式和位数提取字符串
bstr = r"""
^(?<recordType>[\w]{3,3})(?<senderType>[\w]{2,2})(?<senderId>\d{9,9})(?<senderName>[\x00-\xFF]{45,45})(?<ediStandard>[\x00-\xFF]{5,5})(?<creationDateTime>\d{14,14})(?<transmissionDate>\d{8,8})(?<characterSet>[\w\s]{0,15})"""
# 按固定格式提取字符串
b = re.compile(bstr , re.DOTALL)
str_list = b.match(content_str)
if str_list:
linebits = str_list.groupdict()
for k, v in linebits.items() :
# 去除解析之后的字符串的首尾空格
linebits[k] = v.strip()
return linebits
使用否定式前向查找提取字符串
import re
originstr = """
<script>
var liveUrl = "http://pull102.gzlz307.com/home/6d944f6ab72b3d069517146587a23c39/playlist.m3u8?only-audio=1";
var liveId = "5190879905407162422";
var liveStartTime = 1626761082249;
var isSubscribed = !!0;
</script>
"""
# 使用 否定式前向查找?! 来模拟逆向匹配:不包含某一字符串 的字符串 ((?!str).)*
bstr = r'liveUrl(((?!var).)*)var'
b = re.compile(bstr , re.DOTALL)
str_list = b.findall(originstr)
print(str_list[0][0])
# = "http://pull102.gzlz307.com/home/6d944f6ab72b3d069517146587a23c39/playlist.m3u8?only-audio=1";
对提取的日期进行判断是否是有效日期
import re
test_dates_str = """
19971-01-01
1960-00-00
1987-00-00
1981/05/20
1971-1-1
1971-1-0
"""
re_date_get = r'\d{4,5}[-/ ]\d{1,2}[-/ ]\d{1,2}'
re_date_normal = r'((^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(10|12|0?[13578])([-\/\._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(11|0?[469])([-\/\._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(0?2)([-\/\._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([3579][26]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][13579][26])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][13579][26])([-\/\._])(0?2)([-\/\._])(29)$))'
test_dates = re.compile(re_date_get, re.DOTALL).findall(test_dates_str)
test_dates_dict = {}
for item in test_dates:
s_item = re.compile(re_date_normal, re.DOTALL).search(item)
if s_item:
test_dates_dict[item] = s_item.group(0)
else:
test_dates_dict[item] = None
test_dates_dict
log:
{'19971-01-01': None,
'1960-00-00': None,
'1987-00-00': None,
'1981/05/20': '1981/05/20',
'1971-1-1': '1971-1-1',
'1971-1-0': None}