数据清洗–2. regular expression 正则表达
目录
前言
正则表达是数据清洗中不可或缺的一项技能。
一、正则表达是什么
正则表达是用来描述文本规律/模式的一系列的符号。
二、正则表达能干什么
正则表达式在从文本(如日志文件、HTML/XML文件和其他文档)中查找、替换和提取信息时非常有用。
比如提取邮箱或验证输入文本是否符合邮箱格式
figure generate by https://regexper.com
三、正则表达的语法
字母候选集(Character sets)
可以用 gr[ea]y 来匹配
注意 :
- 只匹配候选集中1个字母;
- 候选集中的顺序是无影响的;
- 匹配是大小写敏感的。
即 grAy, graay, 和 graey 是不会被匹配到的
字母候选域(Character ranges)
[0 − 9], [a − z] or [A − Z]
这里先给一个错误的示范:
想匹配50-99之间的数字,并不能用[50-99]表示。
[50-99]表示的是
也就是说跟[0-9]是一样的。
现在在给大家展示一个正确的例子:
答案: [A-Z0-9][A-Z][A-Z]\s[0-9][A-Z0-9][A-Z0-9]
反字母候选集(Negative character sets)
除了用正则表达式可以表达想要哪些字母,还可以用正则表达式表达不要哪些字母,如:
你可以用 :
思考:
see[^mn] 会匹配“see”,“see ”中的哪个,还是都会匹配,亦或者都不匹配??
Try the regular expression in Pythex~
特殊字符在候选集内(Metacharacters inside character sets)
注意:第一个表达式是正确的,需要用"" 来取消第一个"]“的代码意义变成一个单纯的字符,否则系统会认为表达式2中倒数第二个 “]” 和倒数第四个”["是一对。
下表是一些Re的缩写
思考:
[^\d\s] 和[\D\S] 是一样的吗?
答案: 必然不一样
第一个是:既不是数字,也不是空格类
第二个是:不是数字或者不是空格类
重复元字符(Repetition Expressions)
*: 匹配0个或多个该字符
+:匹配1个或多个该字符
?:匹配0个或1个该字符
假设我们想匹配
- oo*ps
- ooo*ps
- oo+ps
- oo?ps
自己去试试吧~ tips: 上面有效的 re 不止一个哦~
另外,还可以定量的重复
- \d{2} 只匹配2个数字
- \d{2,4} 匹配2~4个数字
- \d{2,} 至少匹配2个数字
当然这个方法也可以用在上面那个例子, 试试 o{2:}ps
再举个例子
匹配
我们可以用什么表达式呢?
通过观察,有效的字符串由几个字母,一个下划线,2-4位数字,下划线,1-2位数字组成。
re可以写作:
\w+_\d{2,4}_\d{1,2}
字符组(Grouping)
可以把想捕捉的固定字符串组合放到()中,来实现对固定字符串的捕捉。
比如:
例二:
想匹配 I *** you:
可以用
ps: “.” 表示除换行符(\n)之外的任意单字符,".*"表示在 I 和 you 之间有0个或多个任意字符。
贪婪或非贪婪(greedy or lazy)
贪婪匹配策略:会匹配满足条件的最常的字符串,* 和 + 都是贪婪匹配。
非贪婪匹配策略: 会匹配满足条件的最短的字符串,? 是非贪婪匹配
举个例子
我们有这样一串string
尝试以下两种re,我们会获得不一样的pattern(匹配片段)
-
(".+"),*
re1 是贪婪匹配策略,会找到符合条件的最长的片段,即整个string - (".+?"),*
re2 加入了 “?” 匹配策略变成了非贪婪,就会找满足re的最短的片段,即每个单词。
所以当你编写的re 不能很好的找出想要的片段时,不妨试着改变一下匹配策略。
或(Or)
当想匹配(A or B) + C 这样的pattern的时候就需要使用or的逻辑了, 符号为 ”|“。
举例:
以上一堆果汁中,只要匹配苹果汁和橙汁时,就需要用到or的逻辑
Re可以写作:
(apple|orange) juice
四、代码部分(Python)
以下练习以Python语言为例
贪婪&非贪婪匹配初体验
#pip install re
import re # regular expression的包
# 首先我们来看一下不同的匹配策略对于相同的一段string,
# 是如何匹配的
# 我们可以用re.findall(re,string) 来匹配我们编写的正则表达式
str1 = re.findall(r'.*', 'Please find all.')
print (str1)
# output: ['Please find all.', '']
str2 = re.findall(r'.?', 'Please find all.')
print (str2)
# output: ['P', 'l', 'e', 'a', 's', 'e', ' ', 'f', 'i', 'n', 'd', ' ', 'a', 'l', 'l', '.', '']
str3 = re.findall(r'.+', 'Please find all.')
print (str3)
# output: ['Please find all.']
str1 = re.findall(r'l+', 'Please find all')
print (str1)
# output: ['l', 'll']
判断一个日期是不是正确的日期
# task:判断输入的日期是不是一个正确的日期。
# 日期是日月年格式,年份可能是2位,也可能是4位数字表示
# 假设所有年份都是闰年,即2月份有29天。
# 判断输入的日期是不是一个正确的日期
def date(pattern, m):
if re.match(pattern, m):
print (m + " is a date")
else:
print (m + " is NOT a date")
# 当regular expression比较复杂时,可以用
# ```(?x)```来表示,然后再告诉系统 "?x" 到底是什么
# ```xxx```在Python中表示多行raw string
# 逻辑看不懂的同学,可以把代码放到
# https://regexper.com 里看看具体逻辑
regex = r'''(?x)
(?:
# February (29 days every year)
(([12]\d|0?[1-9])[-/ ]0?2)|
# 30-day months
([12]\d|0?[1-9]|30)[-/ ](0?[469]|11)|
# 31-day months
([12]\d|0?[1-9]|3[01])[-/ ](0?[13578]|1[02])
) [-/ ]
# Year
(\d{2,4})
'''
date(regex, "28/02/2019")
date(regex, "31/04/2019")
date(regex, "29/05/2019")
date(regex, "31/06/2019")
# 28/02/2019 is a date
# 31/04/2019 is NOT a date
# 29/05/2019 is a date
# 31/06/2019 is NOT a date
有兴趣的小伙伴可以思考一下如何完善日期的判断,把是否是闰年考虑进去。
提取IP地址
# IP地址的规律是4段1~3位数字,中间用 . 隔开。
# 当然,这种re并不包括判断IP的有效性,
# 有兴趣的同学可以考虑考虑如何实现判断IP的有效性
pattern_ip = r'\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}'
all_ip = re.findall(pattern_ip, text)
提取邮箱
pattern_email = r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
str_email = re.findall(pattern_email, text)
总结
以上就是今天整理的内容,本文仅仅简单介绍了正则表达式的使用,其实还有很多进阶的正则表达式使用方法,比如定界符 “^” “$”, uncapture group (非捕获组) “?:” “?=” “?<=” “?!” “?<!” 等等。。。
在实际操作中还是有很多困难等着大家解决的[手动捂脸],代码和核心还是多想,多看,多练。与君共勉~