Python 正则表达式基础

后端 / 2020-03-27

正则表达式

基础篇

> 是什么 ?


正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。

正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。

通俗来讲正则表达式就是为了匹配文本而诞生的.

runoob

> 叫什么 ?


正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

特点
1. 灵活性、逻辑性和功能性非常强;
2. 可以迅速地用极简单的方式达到字符串的复杂控制。
3. 对于刚接触的人来说,比较晦涩难懂。

因为 正则表达式十分强大 因此掌握它 就显得尤为重要

> 怎么用 ?

好了扯了那么久犊子,我们也该进入话题了.

我们先简单了解下正则表达

移步 开源中国工具集

我们先看下如何从一段话中匹配出邮件地址.
demo.png

我们在下方看到了 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该标志通过 给予你更灵活的格式 以便你将正则表达式写个更易于理解

转义匹配


当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。

实战篇

抓取猫眼电影排行


下面我们来个实战,抓取猫眼电影今日票房

https://maoyan.com/board

#!/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

结束