python正则表达式与re模块-02
正则表达式
正则表达式与python的联系
# 正则表达式不是Python独有的,它是一门独立的技术,所有的编程语言都可以使用正则 # 但要在python中使用正则表达式,就必须依赖于python内置的re 模块
验证手机号是否合法的小案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
phone_number = input('please input your phone number : ') if len(phone_number) == 11 \ and phone_number.isdigit() \ and (phone_number.startswith('13') \ or phone_number.startswith('14') \ or phone_number.startswith('15') \ or phone_number.startswith('18') \ or phone_number.startswith('19')): print('是合法的手机号码') else: print('不是合法的手机号码')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import re phone_number = input('please input your phone number : ') if re.match('^(13|14|15|18|19)[0-9]{9}$', phone_number): print('是合法的手机号码') else: print('不是合法的手机号码')
从上面两段代码中很容易就可以看得出来,使用正则表达式来校验手机号明显要比纯python 代码验证来的精简得多。
下面就正式介绍一下正则表达式的一些基本知识
正则表达式
正则表达式: # 一种匹配字符串的规则
官方定义: # 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
常见的应用场景: # 爬虫 , # 数据分析 , # 各种数据校验(身份证号、手机号...)
如果你想系统的学习,可以去了解一下《正则指引》这本书
在开始讲正则语法之前,先推荐大家一个验证正则的网站,可以在线测试你的正则表达式能否满足你的预期效果:【正则表达式在线测试】 ,大家可以边学习边在该网站上面验证
你改变正则表达式或者下方的待处理字符串他会自动重新匹配
字符组
# 在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示
常见的字符组(一个字符组中的数据都是 '或' 的关系)
注意: # 字符组可以用 '-' 表示范围,左右两边要按照ASCII码表的顺序书写,可以 0-9 不可以 9-0,9的ASCII值比0大
字符
常见元字符(推荐按不同颜色来分组记)
注意: # ^ 与 & 会精准限制匹配的内容,两者中间写什么,待匹配的字符串就必须是什么,多一个少一个都不行 , # 运用 | (“或”)的时候一定要把长的写在前面,否则匹配到短的就匹配完成了,会有很多被截断的
分组(): # 当多个正则符号需要重复多次的时候,或者当做一个整体,进行其他操作,那么可以用分组的形式,分组在正则的语法中就是一个小括号 '(表达式)' ,里面放表达式即可
转义字符
观察上面的元字符可以发现其中有 \n \t 等代表特殊含义的元字符,如果就是要匹配一个字符串 '\n',则会与元字符冲突,故需要在 '\' 前面再加上一个 '\',来防止转义,即要表示字符串 '\n' 正则中需要写成 '\\n'
量词
只能跟在元字符/字符组/正则组后面,限制其左边紧挨着的那个正则表达式(元字符/字符组/正则组),不可两个量词连在一起(除了 '?',用来解除正则的贪婪模式)
*、+、? 推荐如图所示的画坐标系的方式记忆
贪婪匹配与非贪婪匹配(惰性匹配)
贪婪匹配: # 在满足匹配时,匹配尽可能长的字符串
非贪婪匹配: # 在满足匹配时,匹配尽可能短的字符串
python的匹配模式默认为贪婪匹配,在量词后面加上 ? 可以将其匹配模式改为非贪婪模式,会匹配尽量少的字符串(仅量词作用的那个对象会受影响)
贪婪匹配原理个人理解: # 先匹配到目标字符串 '<',然后直接读取到后面所有的字符串,从倒数第一个字符开始往回找,找到 '>',则将前面的那个位置至这个位置之间的数据返回 ,注意这里是 .* 任意字符 无限多次!!!
非贪婪原理个人理解: # 先匹配到目标字符串 '<',从该位置开始往后寻找字符 '>',找到则这对数据为一个匹配返回结果 ,注意这里是 .* 任意字符 无限多次!!!
案例练习
推荐案例 【正则表达式练习题】 ,可以根据案例巩固知识
如果你看了本文觉得描述不清,可以参考 【re模块 】这篇博客,亦或是下图的出处的博客 【python正则表达式指南】 (前者的博客页面排版看着美观一些,但大部分数据均来源于后者)
python 中的re模块
上面只是介绍了正则表达式的一些基础知识,它是一门独立的技术,要想在python中使用正则表达式,自然就需要通过学习python内置的re模块了(也可以通过其他的一些函数方法等方式使用正则)
要使用 re模块,请不要忘记先导模块( import re )
本文此部分的参考性可能不高,内容也不是很全面,仅作为个人小结,如果有兴趣可以参考 【re模块】 里面有更详细的讲解以及丰富的案例(另:本模块知识很少用到,所以仅做笔记翻阅)
常见重点函数 findall、search、match
findall 找出字符串所有符合内容成一个列表 search 查找匹配结果 match 从开头开始比对
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import re ''' findall 所有符合正则表达式的所有内容 search 有没有符合正则表达式的内容 match 是不是正则表达式对应的开头 ''' # findall 找出字符串中符合正则表达式的所有内容,并且返回一个列表,列表中的元素就是正则表达式匹配到的结果 res = re.findall('[a-z]+', 'nice toZmeet you') # 匹配任意多个小写字母(一个及以上),空格、大写字母,均不符合要求 print(res) # ['nice', 'to', 'meet', 'you'] # search 不会直接返回匹配到的结果,而是给你返回一个对象,这个对象需要调用,通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。 # 注意:1.search 只会依据正则查找一次,只要查到了结果,就不会往后查找了 # 2.当查找的结果不存在的情况下,调用group会直接报错 res = re.search('ou', 'nice toZmeet you') res2 = res.group() # 必须调用group 才能看到匹配到的结果 print(res, res2) # <_sre.SRE_Match object; span=(14, 16), match='ou'> ou res1 = re.search(r'\.', 'nice toZmeet you') # 匹配字符串中是否有 . 这个字符 print(res1) # None # print(res1.group()) # 直接报错,可先判断 res1 是不是 None,再进行取值 # match # 注意:1.match 只会匹配字符串的开头部分 # 2.当字符串的开头与正则表达式不符合匹配规则的情况下,返回的也是None,调用 .group 也会报错 res = re.match('s', 'sda e rf a f') print(res) # <_sre.SRE_Match object; span=(0, 1), match='s'> print(res.group()) # s res = re.match('a', 'sda e rf a f') print(res) # None # print(res.group()) # 直接报错,'NoneType' object has no attribute 'group' ''' 正则表达式,返回类型为表达式对象的,如:<_sre.SRE_Match object; span=(6, 7), match='a'>,返回对象时,需要用正则方法取字符串,方法有: .group() # 获取匹配到的所有结果,不管有没有分组将匹配到的全部拿出来,有参取匹配到的第几个如2 .groups() # 获取模型中匹配到的分组结果,只拿出匹配到的字符串中分组部分的结果 .groupdict() # 获取模型中匹配到的分组结果,只拿出匹配到的字符串中分组部分定义了key的组结果 '''
不常用函数 split、sub、compile、finditer
split 切割 sub 替换 compile 将正则编译成一个对象,可对象. 调用上述方法 finditer 返回一个放返回结果的迭代器
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import re # split 类似于字符串的切割split,返回一个列表(他会把被替换掉的字符变成空格) res = re.split('[ab]', 'sabcasbdsafafabfas') print(res) # ['s', '', 'c', 's', 'ds', 'f', 'f', '', 'f', 's'] # sub 类似于字符串的replace 方法,返回替换完成的字符串,可指定替换次数 # sub('正则表达式', '新的内容', '带匹配的字符串', [要替换的次数]) # 先按正则表达式查找所有符合该表达式的内容,统一替换成'新的内容',还可以通过n来控制替换的个数 ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1) # 将数字替换成'H',参数1表示只替换1个 print(ret) # evaHegon4yuan4 # subn() 字符串replace的加强版,返回替换完成的字符串与总共替换的次数(封成了一个元组) # 返回的是一个元组,元组的第一个元素是替换完成后的结果,第二个元素代表的是替换的个数 ret = re.subn('\d', 'H', 'eva3egon4yuan4') # 将数字替换成'H',返回元组(替换的结果,替换了多少次) print(ret) # ('evaHegonHyuanH', 3) # compile 将正则编译成一个对象,后期可以直接用它来调用 findall、search 等方法 obj = re.compile('\d{3}') # 将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字 ret = obj.search('abc123eeee') # 正则表达式对象调用search,参数为待匹配的字符串 print(ret.group()) # 123 # finditer 返回一个存放匹配结果的迭代器,可以使用前面学习到的 迭代器对象.__next__() 方法调用 ---> 简写 next(迭代器对象) ret = re.finditer('\d', 'ds3sy4784a') print(ret) # <callable_iterator object at 0x000001AE62617160> print(next(ret).group()) # 查看第一个结果 # 3 print(next(ret).group()) # 查看第二个结果 # 4 print([i.group() for i in ret]) # 查看剩余的左右结果 # ['7', '8', '4']
扩展
各方法分组的区别
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import re # search与match均支持获取分组内容的操作 跟正则无关,是python机制 res = re.search('^[1-9]\d{14}(\d{2}[0-9x])?$', '110105199812067023') print(res.group()) # 110105199812067023 print(res.group(1)) # 获取正则表达式括号阔起来分组的内容(最后三位是:两个数字 加上 0-9或者x) # 023 # print(res.group(2)) # 报错:IndexError: no such group ,取不到 # 而针对findall它没有group取值的方法,所以它默认就是分组优先获取的结果 ret = re.findall('www.(baidu|google).com', 'www.google.com') print(ret) # 这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消分组优先即可 # ['google'] ret = re.findall('www.(?:baidu|google).com', 'www.google.com') # ?:取消分组优先 print(ret) # ['www.google.com']
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import re ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>", "<h1>hello</h1>") # 还可以在分组中利用 ?<name> 的形式给分组起名字 # 获取的匹配结果可以直接用 .group('名字') 拿到对应的值 print(ret.group('tag_name')) # h1 print(ret.group()) # <h1>hello</h1> """ 注意 ?P=tag_name 相当于引用之前正则表达式,并且匹配到的值必须和前面的正则表达式一模一样 """ # 案例 # 匹配字符串中的所有数字 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']
爬虫小案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
''' 本爬虫案例思路: 分析 https://movie.douban.com/top250?start=%0&filter= 页面得知,每页有25条数据 通过分次请求该地址,将返回的HTML代码通过正则匹配,解析成想要的字符串格式, 分页分条存入文件中去(用到了 分组 和 取别名 的知识点) ''' import re 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.txt", "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
数据小样
上面仅仅只是一个简单的爬虫案例,如果你想成为一名爬虫工程师,那么你必须的能够熟练地写出正则表达式,言下之意就是除了本文的内容,你还需要好好去学学正则表达式的内容