python模块之re正则表达式
引子
请从以下文件里取出所有的手机号
姓名 地区 身高 体重 电话
况咏蜜 北京 171 48 13651054608
王心颜 上海 169 46 13813234424
马纤羽 深圳 173 50 13744234523
乔亦菲 广州 172 52 15823423525
罗梦竹 北京 175 49 18623423421
刘诺涵 北京 170 48 18623423765
岳妮妮 深圳 177 54 18835324553
贺婉萱 深圳 174 52 18933434452
叶梓萱 上海 171 49 18042432324
杜姗姗 北京 167 49 13324523342
用平常的解法
contacts = [] with open('re_practice.txt', 'r', encoding='utf-8') as f: for line in f: line = line.split() for phone in line: if phone.isdigit() and len(phone) == 11: contacts.append(phone) print(contacts)
用正则表达式
import re with open('re_practice.txt', 'r', encoding='utf-8') as f: data = f.read() phone = re.findall('1[0-9]{10}', data) # 找11个0-9里的数字,第一个必须是1,后面取10位 # 扩展 13[0-9]{9} 前两个必须是13 print(phone)
re的匹配语法
- re.match 从头开始匹配(字符串的开头,就是只匹配第一个,第一个匹配不到就不往后匹配了)
- re.search 全局匹配,找到就返回 应用场景:找手机号或身份证号,第一个必须是数字
- re.findall 把所有符合的匹配都找到并放到列表里
import re s = 'abc1d3e' # match 从头开始匹配(字符串的开头,就是只匹配第一个) print(re.match('[0-9]', s)) # None # search 全局匹配,找到就返回 应用场景:找手机号或身份证号,第一个必须是数字。 print(re.search('[0-9]', s)) # <_sre.SRE_Matc print(re.search('[0-9]', s).group()) # 1 一定要 # findall 把所有符合的匹配都找到并放到列表里 print(re.findall('[0-9]', s)) # ['1', '3']
常用的表达式规则(覆盖95%的场景)
import re s = 'abc1d3e' # '.' 默认匹配除\n之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行 print(re.search('.', s)) # <_sre.SRE_Match object; span=(0, 1), match='a'> # '^' 匹配字符开头,若指定flags MULTILINE,这种也可以匹配上(r"^a","\nabc\neee",flags=re.MULTILINE) print(re.search('^ab', 'abd')) # None 等于 print(re.match('ab', 'abd')) # '$' 匹配字符结尾, 若指定flags MULTILINE ,re.search('foo.$','foo1\nfoo2\n',re.MULTILINE).group() 会匹配到foo1 print(re.search('b$', 'ab')) # <_sre.SRE_Match object; span=(1, 2), match='b'> 一般用match就不用$ # '*' 匹配*号前的字符0次或多次,如果是https*,那么就是匹配*号前面也就是s的多次,也就是httpsssss......都可以匹配到。其实就是匹配http+0次或任意次s print(re.search('a*', 'Aaaalex')) # <_sre.SRE_Match object; span=(0, 0), match=''> 匹配到0次 print(re.search('aa*', 'Aaaalex')) # <_sre.SRE_Match object; span=(1, 4), match='aaa'> 要以两个为单位匹配,所以能匹配到 print(re.findall('a*', 'Aalaaeaaax')) # ['', 'a', '', 'aa', '', 'aaa', '', ''] print(re.search('https*', 'ahttphttps').group()) # 匹配到http,后面是h而不是s,就返回http,因为用的是search方法 print(re.findall('https*', 'ahttphttpsssssss')) # ['http', 'httpsssssss'] findall会找出所有的,匹配前一个的(也就是s)一次或多次 # +' 匹配前一个字符1次或多次,re.findall("ab+","ab+cd+abb+bba") 结果['ab', 'abb'] print(re.search('a+', 'abbaaalex').group()) # a print(re.search('ab+', 'abbaaalex').group()) # abb # '?' 匹配前一个字符1次或0次 print(re.search('https?', 'httpsssssss').group()) # https # '{m}' 匹配前一个字符m次 ,re.search('b{3}','alexbbbs').group() 匹配到'bbb' print(re.search('https{3}', 'httpsssssss').group()) # httpsss # '{n,m}' 匹配前一个字符n到m次 print(re.search('[a-z]{1,2}', 'a2lex').group()) # a # 先找到哪个返回哪个 print(re.search('[a-z]{1,2}', '2lex').group()) # le # 按最大数值来匹配 print(re.search('[a-z]{1,4}', '2lex').group()) # lex # '|' 或 匹配|左或|右的字符 print(re.search('alex|Alex', 'Alex').group()) # Alex print(re.search('alex|Alex', 'alex').group()) # alex print(re.search('[a]|[A]', 'Alex').group()) # 匹配单个字母要用中括号括起来 print(re.search('[a]|[A]lex', 'alex').group()) # a print(re.search('[a]|[A]lex', 'Alex').group()) # Alex 注意|后面是匹配Alex不是A
分组匹配
import re print(re.search('[a-z]+[0-9]+', 'alex123').group()) # alex123 # 现在的需求是要数字是数字,字母是字母 print(re.search('([a-z]+)([0-9]+)', 'alex123').groups()) # ('alex', '123') 注意要用groups,groups只和分组相对应。 # '\A' 只从字符开头匹配 print(re.search('\Aalex', 'alex')) # alex 其实就是等于re.search('^alex', 'alex'),只是两种不同的写法 # '\Z' 匹配字符结尾,同$ # '\d' 匹配数字0-9 其实就是[0-9] print(re.search('\d+', 'alex212312312asd123').group()) # 212312312 贪婪匹配能匹配多少匹配多少,但是search匹配到一个就不管了 # '\D' 匹配非数字 print(re.search('\D+', '22alexAAAB!@#$%^&*()_+asd212312312asd123').group()) # alexAAAB!@#$%^&*()_+asd # '\w' 匹配[A-Za-z0-9] 也就是除了特殊字符之外都匹配 print(re.search('\w+', '22alex!@#$%^&*()_+asd212312312asd123').group()) # 22alex # '\W' 匹配非[A-Za-z0-9] 也就是只匹配特殊字符 print(re.search('\W+', '22alex!@#$%^&*()_+asd212312312asd123').group()) # !@#$%^&*() # '\s' 匹配空白字符、\t、\n、\r s = 'alex\njack\tasd\r' print(s) ''' alex jack ''' print(re.findall('\s+', s)) # ['\n', '\t', '\r']
复杂的分组匹配 '(?P<name>...)'
import re s = '130704200005250613' res = re.search('(?P<province>\d{3})(?P<city>\d{3})(?P<born_year>\d{4})', s) print(res.groupdict()) # {'province': '130', 'city': '704', 'born_year': '2000'} print(res.groupdict()['province']) # 130
re的匹配语法(不常用的)
import re # re.split 以匹配到的字符当做列表分隔符 s = 'alex22jack23rain31jinxing50' print(re.split('\d', s)) # 按数字分开 ['alex', '', 'jack', '', 'rain', '', 'jinxing', '', ''] print(re.split('\d+', s)) # ['alex', 'jack', 'rain', 'jinxing', ''] 和findall正好相反 s2 = 'alex22jack23rain31jinxing50|mack-oldboy' print(re.split('\d+|\||-', s2)) # 或数字、或以|或以-分开,加斜杠就把管道符转义成字符串了,而不是语法。['alex', 'jack', 'rain', 'jinxing', ''] s3 = '9-2*5/3+7/3*99/4*2998+10*568/14' print(re.split('[-\*/+]', s3)) # 写到中括号里是都包含,不写到中括号里就得按顺序排列 ['9', '2', '5', '3', '7', '3', '99', '4', '2998', '10', '568', '14'] print(re.split('[-\*/+]', s3, maxsplit=2)) # 分割两次,findall没有这个功能 ['9', '2', '5/3+7/3*99/4*2998+10*568/14'] # # 转义 s4 = 'alex22jack23rain31\inxing50|mack-oldboy' print(re.split('\\\\', s4)) # 匹配一个斜杠需要用三个 ['alex22jack23rain31', 'inxing50|mack-oldboy'] # re.sub 匹配字符并替换 s = 'alex22jack23rain31\inxing50|mack-oldboy' print(re.sub('\d+', '_', s)) # alex_jack_rain_\inxing_|mack-oldboy print(re.sub('\d+', '_', s, count=2)) # 只有前两个替换 # alex_jack_rain31\inxing50|mack-oldboy # re.fullmatch 全部匹配 print(re.fullmatch('alex', 'alex123')) # None print(re.fullmatch('alex123', 'alex123').group()) # alex123 print(re.fullmatch('\w+@\w+\.(com|cn|edu)', "alex@oldboyedu.cn")) # <_sre.SRE_Match object; span=(0, 17), match='alex@oldboyedu.cn'>
re.compile
Compile a regular expression pattern, returning a pattern object.
pattern = re.compile('\w+@\w+.(com|cn|edu)') print(pattern.fullmatch('edward@gmail.com')) # <_sre.SRE_Match object; span=(0, 16), match='edward@gmail.com'>
这是先把规则储存到内存并编译好,用的话直接掉就可以了,这样会省掉在重新编译正则表达式的过程。如果某个公式用的多了,就用这种方法,效率更高。比如一个公式调用1000次,不用这种方法的话,就会编译正则表达式1000次。
Flags标志符
# re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同) 经常用 print(re.search('a', 'Alex')) # None print(re.search('a', 'Alex', re.I)) # <_sre.SRE_Match object; span=(0, 1), match='A'> # re.M(MULTILINE): 多行模式,改变'^'和'$'的行为 很少用 print(re.search('foo.$', 'foo1\nfoo2\n').group()) # foo2 print(re.search('foo.$', 'foo1\nfoo2\n', re.M).group()) # foo1 加上M后相当于把foo1当成一行,把foo2当成一行,所以匹配到第一行结尾的foo1 print(re.search('foo.$', 'foo1foo2', re.M).group()) # foo2 # re.S(DOTALL): 改变'.'的行为,,make the '.' special character match any character at all, including a newline; without this flag, '.' will match anything except a newline. print(re.search('.', '\n')) # None print(re.search('.', '\n', re.S)) # <_sre.SRE_Match object; span=(0, 1), match='\n'> 真的是匹配任意字符了 # re.X(re.VERBOSE) 可以给你的表达式写注释,使其更可读,下面这2个意思一样 print(re.search('.#test', 'alex')) # None 此时#test会被当做语法 print(re.search('.#test', 'alex', re.X)) # 加X之后就被当做注释了 <_sre.SRE_Match object; span=(0, 1), match='a'>