re模块

re模块

一、正则表达式

re模块与正则表达式之间的关系

​ 正则表达式不是python独有的,它是一门独立的技术

​ 所有的编程语言都可以使用正则

​ 但是如果你想要的在python中使用,就必须依赖于re模块。

正则就是用来筛选字符串中的特定的内容的。

讲正题之前我们先来看一个例子:[https://reg.jd.com/reg/person?ReturnUrl=https%3A//www.jd.com/]

这是京东的注册页面,打开页面我们就看到这些要求输出个人信息的提示。加入我们随意的在手机号码这一栏输入一个1111111,它会提示我们格式有误。这个功能是怎么实现的呢?假如现在你用python写一段代码,类似:

phone_number = input('please input your phone number')

你怎么判断这个phone_nmber是合法呢?

根据手机号码一共11位并且是只以13,14,15,18开头的数字这些特点,我们用python写了如下代码:

while True:
    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.statrswith('14') or phone_number.startswith('15') or phone_number.startswith('18'):
        print('是合法的手机号码')
   	else:
    	print('不是合法的手机号码')
import re
phone_number = input('please input your phone_number:')
if re,match('^(13|14|15|18)[0-9]{0-9}{9}$',phone_number):
    print('是合法的手机号码')
else:
    print('不是合法的手机号码')

正则表达式本身也和python没有关系就是匹配字符串内容的一种规则。

官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的一种过滤逻辑。

正则在线测试工具[ http://tool.chinaz.com/regex/]

首先你要知道的是,谈到正则,就只和字符串相关了。在我给你提供的工具中,你输入的每一个字都是一个字符串。其次,如果在一个位置的一个值,不会出现什么变化,那么是不需要规则的,

比如你要用"1"去匹配"1",或者用"2"去匹配"2",直接就可以匹配上。这连python的字符串操作都可以轻松做到。那么在之后我们更多要考虑的是在同一个位置上可以出现的字符的范围。

字符组:[字符组]
在同一位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示
字符分为很多类,比如数字、字母、标点等等。
假如你现在要求一个位置"只能出现一个数字”,那么这个位置上的字符只能是0、1、2....9这10个数之一。

小例子

  • 匹配0-9数字([0123456789]也支持简写[0-9])(如果想匹配横杆,转义即可)
  • 匹配A-Z字母(依据上面的简写规则[A-Z])
  • 匹配a-z字母(依据上面的简写规则[a-z])

ps:这种上面到上面的范围必须是从小到大[a-Z]不行,[A-z]可以(但是内部有几个特殊符号),因为内部对应的ascii码中A在所有字母里面最小,z在所有字母里面最大。

正则			带匹配字符			匹配结果			说明
[0123456789]     8                 True             在一个字符组里枚举合法的所有字符,字符组里的任意一个字符和"待匹配字符"相同都视为可以匹配。
[0123456789]     a                 False            由于字符组中没有"a"字符,所以不能匹配
[0-9]            7                 True             也可以用-表示范围,[0-9]就和[0123456789]是一个意思
[a-z]            s                 True             同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示
[A-Z]            B                 True             [A-Z]就表示所有的大写字母
[0-9a-fA-F]      e                 True             可以匹配数字,大小写形式的a~f,用来验证十六进制字符

字符:

元字符					匹配内容
.                     匹配除换行符(\n)以外的任意字符
\w                    匹配字母或数字或下划线
\s                    匹配任意的空白符
\d                    匹配数字
\W                    匹配非字母或数字或下划线
\D                    匹配非数字
\S                    匹配非空白符
\b                    匹配一个单词的结尾
\n                    匹配一个换行符
\t                    匹配一个制表符
^                     匹配字符串的开始
$                     匹配字符串的结尾
a|b                   匹配字符a或字符b
()                    匹配括号内的表达式,也表示一个组
[....]                匹配字符组中的字符
[^....]               匹配除了字符组中字符的所有字符

\w,\s,\d与\W,\S,\D相反的匹配关系(对应的两者结合就是匹配全局)

其中^与$符连用,会精准限制匹配的内容,两者中间写什么,匹配的字符串就必须是什么,多一个也不行少一个也不行。

abc|ab 一定要将长的放在前面

^ 直接写在外面, 限制字符串的开头。

[^] 取非,[]写的字符,其他都要。

上面的是匹配单个字符的符号,接下来需要学习匹配个数的限制个数的限制符号。

.^$

正则			带匹配字符			匹配			说明
海.           海燕海角海东         海燕海角海东   匹配所有"海."的字符
^海.           海燕海角海东         海燕          只从开头匹配"海."
海.$           海燕海角海东         海东          只匹配结尾的海."$"

量词:

量词                    用法说明
*                      重复0次或更多次
+                      重复1次或更多次
?                     停止符,找到一个,就会停止。重复0次或1次
{n}                    重复n次,明确指定的个数
{n,}                   重复n次或更多次
{n,m}                  重复n到m次

ps:

​ 1.对于这个0次也能匹配出来的结果,暂时不用考虑

​ 2.*,+,?的工作区间可以用坐标轴的形式表示出来

注意:量词需要写在匹配符号的后面,只对紧挨着它的那个元字符(正则表达式)起作用

*+?{}

正则			带匹配字符			     匹配结果			   说明
李.?			 李杰和李莲英和李二棍子 	  李杰,李莲、李二       ?表示重复0次或一次,即只匹配"李"后面一个任意字符
李.*          李杰和李莲英和李二棍子      李杰和李莲英和李二棍子  *表示重复0次或多次,即匹配"李"后面0个或多个任意字符
李.+          李杰和李莲英和李二棍子      李杰和李莲英和李二棍子  +表示重复1次或多次,即只匹配"李"后面1一个或多个任意字符
李.{1,2}      李杰和李莲英和李二棍子      李杰和,李莲英,李二棍  {1,2}匹配1到2次任意字符

                       

注意:前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成非贪婪匹配(惰性匹配)

正则				带匹配字符			   匹配结果			     说明
李.*?             李杰和李莲英和李二棍子    李,李,李            非贪婪匹配,只匹配"李"后面的0个字符

分组

  • 一次性匹配a1b2c3

    用[a-z]][1-9]会匹配到这三个结果。

    但是重复写三次,太麻烦,加量词的话只能限制离得最近的正则表达式

    这个时候就可以用分组([a-z][1-9])+

  • 匹配身份证号

​ 依据博客依次分析(下面的正则之所以加^和$是因为我们仅仅只想匹配身份证号,教学演示方便没不加照样也能匹配上,只不过前后可以出现很多其他字符)

​ 1.[1]\d{13,16}[0-9x]$:不完善的地方在于默认是x的情况只在18位才可能出现,但是这个表达式没有做着一层的限制

​ 2.[2]\d{14}(\d{2}[0-9x])?$

​ 3.^([1-9]\d{16}[0-9x]|[1-9]\d{14})$

转义符

在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s等,如果要在正则中匹配正常的"\n"而不是"换行符"就需要对"\n"进行转义,变成'\'。

在python中,无论是正则表达式,还是带匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。

所以如果匹配一次"\n",字符串中要写成"\\n"

所以如果匹配一次"\\n",字符串中要写成"\\\\n"

简便操作,利用r可以让整个字符串都不再转义(了解:r其实就是real的意思,真实不转义)

贪婪匹配与非贪婪匹配

贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配。

正则          待匹配字符            匹配结果                说明
<.*>         <script>...<script>  <script>...<script>   默认为贪婪匹配模式,会匹配尽量长的字符串
<.*?>       r'd'                 <script><script>      加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串

几个常用的非贪匹配Pattern

*?重复任意次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
??重复0次或1次,但尽可能少重复
{n,m}?重复n到m次,但可能少重复
{n,}?重复n次以上,但尽可能逗号重复

.*?的用法

.是任意字符
* 是取0至无限长度
?是非贪婪模式
合在一起就是取尽量少的任意字符,一般不会写这么单独写,它大多用在:
.*?x
就是取前面任意长度的字符,直到出现一个x

  • <.*>:先拿里面的.*去匹配所有的内容,然后再根据>往回退这着找,遇到即停止
  • <.*?>:先拿着?后面的>取匹配符合条件的最少的内容,然后把匹配的结果返回。

ps:根据匹配的内部原理可以很好的理解

.*?x
就是取前面任意长度的字符,直到一个x出现

至此整个后期项目里面能用到的正则表达式就已经给你讲完了,把这些记住足够你在后面的项目和爬虫中用了,讲了这么久,一点python的事儿都还没扯,现在就要来学在python里面如何使用。

字符集[][^]

正则				带匹配字符				匹配结果  			  说明
李[杰莲英二棍子]*   李杰和李莲英和李二棍子     李杰,李莲英,李二棍子  表示匹配"李"字后面[杰莲英二棍子]的字符任意次
李[^和]*          李杰和李莲英和李二棍子     李杰,李莲英,李二棍子  表示匹配"李"字后面一个不是"和"的字符任意次
[\d]             456bdha3               4,5,6,3              表示匹配任意一个数字,匹配到4个结果
[\d]+            456dbha3               456,3                表示匹配任意个数字,匹配到两个结果

分组()与|或[^]

身份证号是一个长度为15或18个字符的字符串,如果是15位则全部由数字组成,首位不能为0;如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:

正则			带匹配字符					匹配结果				说明
^[1-9]\d{13,16}[0-9x]$   # 以数字1-9开头,重复13次或16次匹配数字,末位为0-9或x的字符
110101198001017032       110101198001017032      表示可以匹配一个正确的身份证号
^[1-9]\d{13,16}[0-9x]$   1101011980010170    1101011980010170    表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字
^[1-9]\d{14}(\d{2}[0-9x])?$  # 以数字1-9开头,重复14次匹配数字,()表示分组,将\d{2}[0=9x]分成一组,就可以整体约束他们出现的次数为0-1次,并且不会匹配错误的身份证号了

re模块的使用

三个必须掌握的方法

  • findall 找到所有
  • search 搜索
  • match 匹配
import re

# 第一个参数就是正则表达式,第二个参数是待匹配的文本内容

ret = re.fildall('a','eva egon yuan')  # 返回所有满足匹配条件的结果,放在列表里
print(ret)   # ['a', 'a']

ret = re.search('a','eva egon yuan')  # 第一个匹配是eva中的'a' 得到一个对象
print(ret,group())  # 'a'
# 函数会在字符串内查找模式匹配,直到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None,并且需要注意的是,如果ret是None,再调用.group()会直接报错。这一易错点可以通过if判断来进行筛选。
if ret:
    print(ret.group())
     
ret = re.match('a','eva egon jason').group() # 同search,不过仅在字符串开始处进行匹配
print(ret)  # 'a'
# match是从头开始匹配,如果正则规则从头匹配上,就返回一个对象,需要用group才能显示,如果没匹配上就返回None,调用group()就会报错
注意:
	1.match只会匹配字符串的开头部分
    2.当字符串的开头不符合匹配规则的情况下,返回的也是None,调用group也会报错。
    

四个必须了解的方法

  • split 切割
  • sub 替换
  • subn 替换
  • compile 编写
  • finditer 将找到的结果生成一个迭代器
ret = re.split('[ab]','abcd')  # 先按'a'分割得到''和’bcd‘,再对''和'bcd'分别按'b'分割
print(ret)  # ['','','cd']

ret1=re.split("(\d+)","eva3egon4yuan")
print(ret1) #结果 : ['eva', '3', 'egon', '4', 'yuan']

ret = re.sub('\d','H','eva3egon4yuan4',1)  # 将数字替换成'H',参数1表示只替换1个
print(ret)  # evaHegon4yuan4

ret = re.subn('\d','H','eva3egon4yuan4')  # 讲数字替换成'H',返回元组(替换的结果,替换了多少次)
print(ret)

obj = re.compile('\d{3}')  # 将正则表达式编译成为一个正则表达式对象,规则要匹配的是3个数字
ret = obj.search('abc123eee')  # 正则表达式对象调用search,参数为待匹配的字符串
print(ret.group())  # 结果为:123

import re
ret = re.finditer('\d', 'ds3sy4784a')   #finditer返回一个存放匹配结果的迭代器
print(ret)  # <callable_iterator object at 0x10195f940>
print(next(ret).group())  #查看第一个结果
print(next(ret).group())  #查看第二个结果
print([i.group() for i in ret])  #查看剩余的左右结果

扩展

分组优先机制

import re
res = re.search('^[1-9]\d{14}(\d{2}[0-9x])?$','110105199812067023')
print(res.group())
print(res.group(1))  # 获取正则表达式括号括起来分组的内容 023
print(res.group(2))  # search与match均支持获取分组内容的操作   跟正则无关是python机制  没有第二个会报错

# 而针对findall,它没有group取值的方法,所以它默认就是分组优先获取的结果
ret = re.findall('www.(baidu|oldboy).com','www.oldboy.com')
print(ret)  # ['oldboy']  这是因为findall会优先把组里匹配结果内容返回,如果想要匹配结果,取消权限即可

ret = re.findall('www.(?:baidu|oldboy).com','www.oldboy.com')  # ?:取消分组优先
print(ret)  # ['www.oldboy.com']

补充?P用法

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','3','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']

  1. 1-9 ↩︎

  2. 1-9 ↩︎

posted @ 2019-08-01 21:00  最后的别离  阅读(298)  评论(0)    收藏  举报