0%

正则表达式学习总结

什么是正则表达式

正则表达式(Regular Expression),我一直觉得将regular译成“正则”是一件很荒唐的是,不仅体现在正则表达式,包括初接触神经网络时的概念正则化也是如此,其实就是标准化。regular expression就是标准表达式,目的就是提出一套标准规则,用来匹配字符串,以达到提取或过滤某种特定形式的字符串的作用。


正则表达式的作用

正则表达式十分强大,可以匹配任意的字符串。可以应用在各个方面,例如想提取一大段话中的邮箱地址,提取IP地址,提取电话号码,提取年月日等等,换言之,这些具有一定规律的字符串格式都可以用正则表达式来批量化提取。

  • 邮箱地址
1
2
3
4
5
6
7
8
9
10
11
12
13
import re

email1 = 'eric6465@163.com'
email2 = '1135310887@qq.com'
email3 = 'frontop137@gmail.com'
email4 = '201730055081@scut.edu.cn'

email__pattern = r'[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9\-\.]+\.[a-z]+'

print(re.match(email__pattern, email1))
print(re.match(email__pattern, email2))
print(re.match(email__pattern, email3))
print(re.match(email__pattern, email4))

运行结果

1
2
3
4
<re.Match object; span=(0, 16), match='eric6465@163.com'>
<re.Match object; span=(0, 17), match='1135310887@qq.com'>
<re.Match object; span=(0, 20), match='frontop137@gmail.com'>
<re.Match object; span=(0, 24), match='201730055081@scut.edu.cn'>

以上是我的四个邮箱,全部匹配正确。匹配的邮箱的关键在于@和顶级域名。

  • IP地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re

ip_pattern = r'^((\d|[1-9]\d|1[0-9]{2}|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1[0-9]{2}|2[0-4]\d|25[0-5])$'

ip1 = '192.168.0.1'
ip2 = '255.255.255.0'
ip3 = '1.1.1.256'
ip4 = '1.1.1.01'
ip5 = '1.1.1.00'
ip6 = '1.1.1.1111'
print(re.match(ip_pattern, ip1))
print(re.match(ip_pattern, ip2))
print(re.match(ip_pattern, ip3))
print(re.match(ip_pattern, ip4))
print(re.match(ip_pattern, ip5))
print(re.match(ip_pattern, ip6))

运行结果

1
2
3
4
5
6
<re.Match object; span=(0, 11), match='192.168.0.1'>
<re.Match object; span=(0, 13), match='255.255.255.0'>
None
None
None
None

前两个都是我们常见的合法的IP地址,后四个分别出现了一些错误,全部无法匹配。匹配IP地址的关键在于,IP地址由4个8位二进制码组成,其间用‘.’隔开,转换为十进制后,范围在0-255。若是1位,则0-9均可;若2位,则十位不能为0,个位任意;若3位,则百位为1-2,当百位为1时,剩余两位任意,当百位为2时,后两位最大值为55,即十位为0-4时个位任意,十位为5时个位只能为0-5。

  • 电话号码
1
2
3
4
5
6
7
8
9
10
11
import re

phone_pattern = r'^[0-9]{3,4}\-[0-9]{7,8}$'

ph1 = '020-87114442'
ph2 = '0351-1234567'
ph3 = '6666666'

print(re.match(phone_pattern, ph1))
print(re.match(phone_pattern, ph2))
print(re.match(phone_pattern, ph3))

运行结果

1
2
3
<re.Match object; span=(0, 12), match='020-87114442'>
<re.Match object; span=(0, 12), match='0351-1234567'>
None

匹配电话号码相对简单,只要注意是否有连字符‘-’,并且两边各有几位数字即可。

  • 日期

这里我们尝试抓取某个网页内的日期信息

网页链接: 关于2020-2021学年度第一学期部分通选课停开的通知

网页截图:

网页截图

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import re
import requests

url = 'http://jw.scut.edu.cn/zhinan/cms/article/view.do?type=posts&id=ff8080817443bcba0174bf0232b500ec'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ('
'KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36'
}
response = requests.get(url, headers=headers)
response.encoding = response.apparent_encoding
# content为此静态网页的代码
content = response.text
date_pattern = r'((19|20)\d{2}\-)(0[1-9]|1[0-2])\-(0[1-9]|[1-2][0-9]|3[0-1])'
print(re.search(date_pattern, content))

运行结果

1
<re.Match object; span=(7049, 7059), match='2020-09-24'>

由于是从网页中提取日期,而非判断是否是合法日期,所以我们只需要匹配“yyyy-mm-dd”即可,而不需要判断是否是闰年,是大月还是小月。

</br>

通过上述四个例子,想必我们已经知道了正则表达式的最大的作用,即可以提取我们所需的特定格式的字符串。下面我们来具体说说正则表达式的语法。


正则表达式的语法

  • 普通字符
字符 描述
[ABC] 匹配包含ABC任意一个的单个字符
ABC 匹配任意不包含ABC的单个字符
[A-Za-z0-9] 匹配A-Z,a-z,0-9中任意一个的单个字符
\w 匹配A-Z,a-z,0-9和下划线中任意一个的单个字符

</br>

  • 非打印字符(常用)
字符 描述
\n 匹配一个换行符
\t 匹配一个制表符
\s 匹配任意空格字符

</br>

  • 特殊字符
字符 描述
$ 匹配结尾
() 分部分匹配
. 匹配除换行符外任意字符
\ 转义符号
^ 匹配开始,若放在[]中使用则代表非运算

</br>

  • 限定符
字符 描述
* 匹配前一个字符0次或无穷次,等价于{0,}
+ 匹配前一个字符1次或无穷次,等价于{1,}
? 匹配前一个字符0次或1次,等价于{0,1}
{} {n,m}代表匹配前一个字符不少于n次,不多于m次

注意:*和+都是贪婪的,?是非贪婪的。通过在*和+后添加?,达到最小长度匹配的效果。*?表示匹配前面的字符任意次,但是越少越好;+?表示匹配前面的字符1次或1次以上,但是越少越好。


Python中的re模块

许多编程语言都支持用正则表达式的操作,以Python为例,其中的re模块功能十分强大,为Python语言提供了正则表达式的全部功能。下面着重介绍几个比较常用的函数。

  • search()

用法:

尝试搜索整个字符串,直到找到第一个符合的匹配,停止匹配。

1
2
3
4
re.search(pattern, string, flags=0)
# pattern: 匹配的正则表达式
# string: 待匹配的字符串
# return: 若匹配成功则返回一个匹配的对象,否则返回None
  • match()

用法:

尝试从字符串的起始位置匹配。

1
2
3
4
re.match(pattern, string, flags=0)
# pattern: 匹配的正则表达式
# string: 待匹配的字符串
# return: 若匹配成功则返回一个匹配的对象,否则返回None
  • findall()

用法:

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。

1
2
3
4
re.findall(pattern, string, flags=0)
# pattern: 匹配的正则表达式
# string: 待匹配的字符串
# return: 若匹配成功则返回一个包含所有匹配到的字符串的列表,否则返回空列表
  • finditer()

用法:

和findall() 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。

1
2
3
4
re.finditer(pattern, string, flags=0)
# pattern: 匹配的正则表达式
# string: 待匹配的字符串
# return: 若匹配成功则返回一个包含所有匹配到的字符串的迭代器
  • group() & groups()

用法:

匹配对象的函数,用于获取匹配到的表达式

方法 描述
group() 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组
groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号
  • compile()

用法:

用于编译正则表达式,当我们要重复多次匹配某一固定的正则表达式的时候,我们可以在一开始就进行re.compile()的操作,然后直接对返回结果应用search(),match(),findall()等方法,这样可以省略调用这些函数时重复编译正则表达式的时间。

1
2
3
re.compile(pattern[, flags])
# pattern: 匹配的正则表达式
# flag: 匹配模式

</br>

其实掌握了最基本的正则表达式语法,就可以很轻松的应用到不同的编程语言中,具体的针对不同语言的函数可以在做的时候再去查找。


关于爬虫

众所周知,最基本的网络爬虫就是从网络页面中爬取信息,同样以Python语言为例,我们常用的解析网页的工具有BeautifulSoup模块和lxml模块中的xpath()。这些的原理都是解析网页中的html标签。我比较常用xpath去解析,所以就拿xpath来举个例子:当网页源代码和浏览器渲染出来的代码不一样的时候,我们可能无法直接在浏览器定位我们要查找的标签或者直接copy xpath,同时由于没有缩进,我们也很难直接从源代码中看出某个标签的具体索引,这时我们就可以采用简单粗暴的正则表达式,直接去匹配。另一种情况,就是动态网页加载出来的页面的内容,可能不是完全的json格式,前面可能附带着一段参数,这时我们无法采用json.loads()去编译,我们去要将其中的json格式的数据抓取出来,这时我们也可以用正则表达式直接去抓取。

用正则表达式爬虫可能不是一种很好的选择,主要是因为过于繁琐,但是遇到解决不了的问题,遇到无法解析的标签,正则表达式可以100%保证顺利爬取下来。


推荐在线编译工具

初次遇到正则表达式,可能会觉得像是火星文一样复杂,但是无非就是一些规则和语法,只要多加练习就可以掌握。下面推荐两个在线的正则表达式测试网站:


本文参考