正则表达式
基础篇
> 是什么 ?
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。
正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
通俗来讲正则表达式就是为了匹配文本而诞生的.
> 叫什么 ?
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
特点 |
---|
1. 灵活性、逻辑性和功能性非常强; |
2. 可以迅速地用极简单的方式达到字符串的复杂控制。 |
3. 对于刚接触的人来说,比较晦涩难懂。 |
因为 正则表达式十分强大 因此掌握它 就显得尤为重要
> 怎么用 ?
好了扯了那么久犊子,我们也该进入话题了.
我们先简单了解下正则表达
移步 开源中国工具集
我们先看下如何从一段话中匹配出邮件地址.
我们在下方看到了 email 地址 ,是不是很神奇,这就是正则表达式的功效.
在这里我们使用了正则表达式,也就是用一定规则将特定文本提取出来.
比如电子邮件是一段字符串,然后是一个@符号,最后是某个域名,这是由特定的组成格式的.
另外对于url 开头是协议类型,然后是冒号加双斜线 最后是域名 加 路径.
那么 我们就可以使用下面语句来匹配
[a-zA-z]+://[^\s]*
然后我们就成功匹配到了邮件地址
下面我们来简单了解下正则表达式的基本语句吧
进阶篇
基础
模式 | 描述 |
---|---|
\ | 将下一个字符标记为特殊字符或字面值^ |
^ | 匹配输入的开始位置 |
$ | 匹配输入的结束位置 |
* | 匹配前一个字符零次或几次 |
+ | 匹配前一个字符一次或几次 |
? | 匹配前一个字符零次或一次 |
. | 匹配换行符以外的任何字符 |
(pattern) | 与模式匹配并记住匹配 |
x|y | 匹配x或y |
n为非负的整数。匹配恰好n次 | |
{n,} | n为非负的整数。匹配至少n次 |
{n,m} | m和n为非负的整数。匹配至少n次,至多m次 |
[xyz] | 一个字符集。与括号中字符的其中之一匹配 |
[^xyz] | 一个否定的字符集。匹配不在此括号中的任何字符 |
[0-9] | 匹配0-9之间的任意1个数字 |
[a-z] | a"与"z"之间的任何1个小写字母 |
[A-Z] | a"与"z"之间的任何1个大写字母 |
\t | 匹配一个制表符 |
[^m-z] | 不在指定区间内的字符匹配 |
\b | 与单词的边界匹配,即单词与空格之间的位置 |
\B | 与非单词边界匹配 |
\d | 与一个数字字符匹配 |
\D | 与非数字的字符匹配 |
\f | 与分页符匹配 |
\n | 与换行符字符匹配 |
\r | 与回车字符匹配 |
\s | 与任何白字符匹配,包括空格、制表符、分页符等 |
\S | 与任何非空白的字符匹配 |
\t | 与制表符匹配 |
\v | 与垂直制表符匹配 |
\w | 数字 字母 下划线 |
\W | 与任何非单词字符匹配 |
\num | 匹配num个 "(.)\1"匹配两个连续的相同的字符。 |
\n | 匹配n,其中n是一个八进制换码值 |
\xn | 匹配n,其中n是一个十六进制的换码值。 |
(.*?) | 万能表达式 |
下面我们就来一些小实践,来巩固一下
- match(成功返回 一个匹配对象 否则返回 None)
- pattern
- string
- flags
- search(扫描整个字符串返回第一个成功匹配)
- pattern
- string
- flags
- find all(在字符串中找到正则表达式所匹配的所有子串,并返回一个列表)
- string
- pos
- endpos
- sub(用于替换字符串中的匹配项)
- prttern
- repl
- string
- count
- compile(生成一个正则表达式( Pattern )对象)
- pattern
- flags
- finditer(返回一个迭代器)
- split(匹配的子串将字符串分割后返回列表)
小试牛刀
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
def main():
content='Hello 123 4567 World_This is a Regex Demo'
print(len(content))
result=re.match(r'^Hello\s\d\d\d\s\d{4}\s\w{10}',content)
print(result)
print(result.group())
print(result.span())
if __name__ == '__main__':
main()
41
<re.Match object; span=(0, 25), match='Hello 123 4567 World_This'>
Hello 123 4567 World_This
(0, 25)
首先 我们声明了一个字符串,包含英文字母,空白字符,数字等。
接下来我们写了个正则表达式
^Hello\s\d\d\d\s\d{4}\s\w{10}
- ^
匹配字符串的开头,也就是以Hello 开头
- \s
匹配空白字符 (这里是空格)
- \d\d\d
匹配数字 三个就是匹配 123
- \d{4}
匹配前面规则4次,匹配4次数字
- \s
匹配一次空格
- \w{10}
匹配10个字母 数字 下划线
匹配目标
刚才,我们用match()方法可以 匹配到字符串内容,但是如果想从字符串中提取一部分,该怎么办呢?
实际上,我们可以用() 将想提取的子字符串括起来,()标记了一个子表达式并对应一个分组.调用**group()**方法传入分组索引即可。
通用匹配(.*?)
刚才我们写的正则表达式其实比较复杂,重写空白字符我们就用**\s匹配,出现数字我们就用\d**匹配,这样的工作量非常大.其实完全没必要这样做.
还有一个万能匹配可以用,那就是**. *,其中 . 可以匹配任意字符 ***** 代表前面匹配的字符无限次,结合在一起 就可以匹配任意字符**了
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
def main():
content='Hello 123 4567 World_This is a Regex Demo'
print(len(content))
result=re.match(r'^Hello.*Demo$',content)
print(result)
print(result.group())
print(result.span())
if __name__ == '__main__':
main()
41
<re.Match object; span=(0, 41), match='Hello 123 4567 World_This is a Regex Demo'>
Hello 123 4567 World_This is a Regex Demo
(0, 41)
贪婪 与 非贪婪
使用上面通用匹配.*时,可能有时候匹配到的并不是我们想要的结果.
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))
这里我们依然想获取中间的数字,所以中间依然写的是(\d+)
。而数字两侧由于内容比较杂乱,所以想省略来写,都写成 .*
。最后,组成^He.*(\d+).*Demo$
,看样子并没有什么问题。我们看下运行结果:
<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
7
奇怪的事情发生了,我们只得到了7这个数字,这是怎么回事呢?
因为在贪婪匹配下,.*
会匹配尽可能多的字符。正则表达式中.*
后面是\d+
,也就是至少一个数字,并没有指定具体多少个数字,因此,.*
就尽可能匹配多的字符,这里就把123456
匹配了,给\d+
留下一个可满足条件的数字7,最后得到的内容就只有数字7了。
解决办法
这里我们只是将第一个.*
改成了.*?
,转变为非贪婪匹配。
import re
content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))
贪婪匹配是尽可能匹配多的字符,非贪婪匹配就是尽可能匹配少的字符。
所以说 匹配的时候,字符串中间尽量使用非贪婪匹配也就是用.*?
来代替.*
,以免出现匹配结果缺失的情况。
修饰符
修饰符 | 描 述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化(locale-aware)匹配 |
re.M | 多行匹配 影响 ^ 和 $ |
re.S | 使.匹配包括换行在内的所有字符 |
re.U | 根据unicode 字符集解析字符 |
re.X | 该标志通过 给予你更灵活的格式 以便你将正则表达式写个更易于理解 |
转义匹配
当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。
实战篇
抓取猫眼电影排行
下面我们来个实战,抓取猫眼电影今日票房
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
__author='luzhenfang'
# 2019年7月29日
import re
import requests
import json
# 获取源数据
def get_page():
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
response = requests.get('https://maoyan.com/board', headers=headers)
if response.status_code == 200:
return response.text
return None
# 获取页面 返回迭代器
def parse_page_one(html):
pattern = r'<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?' \
r'title="(.*?)".*?"star">(.*?)</p>.*?releasetime">(.*?)</p>'
items = re.findall(pattern, html, re.S)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2].strip(),
'actor': item[3].strip()[3:] if len(item[3]) > 3 else '',
'time': item[4].strip()[5:] if len(item[4]) > 5 else ''
}
# 获取页面 返回 列表
def parse_page(html):
pattern = r'<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?' \
r'title="(.*?)".*?"star">(.*?)</p>.*?releasetime">(.*?)</p>'
items = re.findall(pattern, html, re.S)
list=[]
for item in items:
dict = {
'index': 'NULL',
'image': 'NULL',
'title': 'NULL',
'actor': 'NULL',
'time': 'NULL'
}
dict['index']=item[0]
dict['image']=item[1]
dict['title']=item[2].strip()
dict['actor']=item[3].strip()[3:] if len(item[3]) > 3 else ''
dict['time']=item[4].strip()[5:] if len(item[4]) > 5 else ''
list.append(dict)
return list
# 生成 json
def be_json(title,list):
count = len(list)
# 构造一个字典
dict={'title':title,'Data':list,'count':count}
result=json.dumps(dict)
return result
def main():
html=get_page()
a= parse_page(html)
JSON=be_json('测试标题',a)
print(JSON)
if __name__ == '__main__':
main()
{
"title": "测试标题",
"Data": [
{
"index": "1",
"image": "https://p0.meituan.net/movie/005955214d5b3e50c910d7a511b0cb571445301.jpg@160w_220h_1e_1c",
"title": "哪吒之魔童降世",
"actor": "吕艳婷,囧森瑟夫,瀚墨",
"time": "2019-07-26"
},
{
"index": "2",
"image": "https://p0.meituan.net/movie/bb9f75599bfbb2c4cf77ad9abae1b95c1376927.jpg@160w_220h_1e_1c",
"title": "银河补习班",
"actor": "邓超,白宇,任素汐",
"time": "2019-07-18"
},
{
"index": "3",
"image": "https://p0.meituan.net/movie/30b20139e68c46d02e0893277d633b701292458.jpg@160w_220h_1e_1c",
"title": "千与千寻",
"actor": "柊瑠美,周冬雨,入野自由",
"time": "2019-06-21"
},
{
"index": "4",
"image": "https://p0.meituan.net/movie/03b6254e94e18c99c6b080f2fecada2414201605.jpg@160w_220h_1e_1c",
"title": "游戏人生 零",
"actor": "松冈祯丞,茅野爱衣,日笠阳子",
"time": "2019-07-19"
},
{
"index": "5",
"image": "https://p0.meituan.net/movie/8d3efdc44af04c3254fc9e4ad5334ae32660685.jpg@160w_220h_1e_1c",
"title": "扫毒2天地对决",
"actor": "刘德华,古天乐,苗侨伟",
"time": "2019-07-05"
},
{
"index": "6",
"image": "https://p0.meituan.net/moviemachine/5dac476535359b7bb951ff15d37a9d0b153821.jpg@160w_220h_1e_1c",
"title": "蜘蛛侠:英雄远征",
"actor": "汤姆·赫兰德,杰克·吉伦哈尔,塞缪尔·杰克逊",
"time": "2019-06-28"
},
{
"index": "7",
"image": "https://p0.meituan.net/moviemachine/350582b51ae828c837ec00bf8aac2a30548615.jpg@160w_220h_1e_1c",
"title": "爱宠大机密2",
"actor": "帕顿·奥斯瓦尔特,冯绍峰,凯文·哈特",
"time": "2019-07-05"
},
{
"index": "8",
"image": "https://p0.meituan.net/movie/7b40e56e644cd04915e6e9cc09c1bdb1331242.jpg@160w_220h_1e_1c",
"title": "命运之夜——天之杯II :迷失之蝶",
"actor": "杉山纪彰,下屋则子,神谷浩史",
"time": "2019-07-12"
},
{
"index": "9",
"image": "https://p0.meituan.net/moviemachine/7b9b0725ab5feae642e1fbba9fbb90fe3702078.jpg@160w_220h_1e_1c",
"title": "狮子王",
"actor": "唐纳德·格洛弗,塞斯·罗根,詹姆斯·厄尔·琼斯",
"time": "2019-07-12"
},
{
"index": "10",
"image": "https://p0.meituan.net/movie/536657ddfd4cf30cd1b185c860335c6e538231.jpg@160w_220h_1e_1c",
"title": "巴比龙",
"actor": "查理·汉纳姆,拉米·马雷克,汤米·弗拉纳根",
"time": "2019-07-26"
}
],
"count": 10
}
上面一个简单的实战,我们更加深入了解了正则表达式, 以及 简单的接口封装
匹配国内电话号
import re def main(): result = re.match(r'\d{3,4}[\s,-]?\d{7,8}','0511-4405222') print(result) if __name__ =='__main__': main()
<re.Match object; span=(0, 12), match='0511-4405222'>
匹配手机号码
1[3,4,5,8]\d[\s,-]?\d{4}[\s,-]?\d{4}+
匹配QQ号
[1-9][0-9]{4,}
匹配邮编
[1-9]\d{5}(?!\d)
匹配身份证号
[1-9][0-9,X]{14,17}
匹配特定数字
^[1-9]\d*$ //匹配正整数 ^-[1-9]\d*$ //匹配负整数 ^-?[1-9]\d*$ //匹配整数 ^[1-9]\d*|0$ //匹配非负整数(正整数 + 0) ^-[1-9]\d*|0$ //匹配非正整数(负整数 + 0) ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ //匹配正浮点数 ^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ //匹配负浮点数 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ //匹配浮点数 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ //匹配非负浮点数(正浮点数 + 0) ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$ //匹配非正浮点数(负浮点数 + 0)
匹配 HTML 标记
<(\S*?)[^>]*>.*?</\1>|<.*? />
匹配链接地址
href *= *['"]*(\S+)["']
匹配标题和链接地址
\<a href *= *['"]*(\S+)["'].*\>(.[^\<]*)?\</a>
匹配url
[a-zA-z]+://[^\s]*
匹配网站域名
[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?
匹配ip地址
\d+\.\d+\.\d+\.\d+
匹配email地址
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
匹配图片引用地址
\<img.*src *= *['"]*(\S+)["'].*\>
匹配账号是否合法
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
匹配密码是否合法
^[a-zA-Z]\w{5,17}$
匹配中文字符
[\u4e00-\u9fa5]
匹配双字节字符
[^\x00-\xff]
匹配空白行
\n\s*\r
匹配首尾空白字符
^\s*|\s*$
匹配特定字符串
^[A-Za-z]+$ //匹配由26个英文字母组成的字符串 ^[A-Z]+$ //匹配由26个英文字母的大写组成的字符串 ^[a-z]+$ //匹配由26个英文字母的小写组成的字符串 ^[A-Za-z0-9]+$ //匹配由数字和26个英文字母组成的字符串 ^\w+$ //匹配由数字、26个英文字母或者下划线组成的字符串
匹配多行文本
Start([\s\S]*?)END