Python 正则表达式

正则表达式

regular expression,用来简洁表达一组字符串的表达式

在 Python 中,通过引入 Re 库,来进行正则表达式的操作

语法

正则表达式语法由字符和操作符构成

常用操作符

操作符 说明 实例
. 表示任何单个字符
[ ] 字符集,对单个字符给出取值范围 [abc] 表示a、b、c,[a‐z] 表示 a 到 z 单个字符
[^ ] 非字符集,对单个字符给出排除范围 [^abc] 表示非 a 或 b 或 c 的单个字符
* 前一个字符 0 次或无限次扩展 abc* 表示 ab、abc、abcc、abccc 等
+ 前一个字符 1 次或无限次扩展 abc+ 表示 abc、abcc、abccc 等
? 前一个字符 0 次或 1 次扩展 abc? 表示 ab、abc
\ 左右表达式任意一个 abc\ def 表示 abc、def
{m} 扩展前一个字符 m 次 ab{2}c 表示 abbc
{m,n} 扩展前一个字符 m 至 n 次( 含 n ) ab{1,2}c 表示 abc、abbc
^ 匹配字符串开头 ^abc 表示 abc 且在一个字符串的开头
$ 匹配字符串结尾 abc$ 表示 abc 且在一个字符串的结尾
( ) 分组标记,内部只能使用 \ 操作符 (abc) 表示 abc,(abc\ def) 表示 abc、def
\d 数字,等价于 [0‐9]
\w 单词字符,等价于 [A‐Za‐z0‐9_]

示例

1
2
3
4
5
6
正则表达式                    对应字符串
P(Y|YT|YTH|YTHO)?N 'PN'、'PYN'、'PYTN'、'PYTHN'、'PYTHON'
PYTHON+ 'PYTHON''PYTHONN''PYTHONNN'
PY[TH]ON 'PYTON''PYHON'
PY[^TH]?ON 'PYON'、'PYaON'、'PYbON'、'PYcON'…
PY{:3}N 'PN''PYN''PYYN''PYYYN'

经典正则表达式示例

1
2
3
4
5
6
7
^[A‐Za‐z]+$                 由 26 个字母组成的字符串
^[A‐Za‐z0‐9]+$ 由 26 个字母和数字组成的字符串
^‐?\d+$ 整数形式的字符串
^[09]*[19][09]*$ 正整数形式的字符串
[19]\d{5} 中国境内邮政编码,6
[\u4e00‐\u9fa5] 匹配中文字符
\d{3}‐\d{8}|\d{4}‐\d{7} 国内电话号码,01068913536

IP 地址的正则表达式

1
2
3
4
5
6
7
8
9
10
IP 地址字符串形式的正则表达式(IP 地址分 4 段,每段 0255
\d+.\d+.\d+.\d+ 或 \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}

精确写法
0‐99: [1‐9]?\d
100199: 1\d{2}
200249: 2[04]\d
250255: 25[05]

(([1‐9]?\d|1\d{2}|2[0‐4]\d|25[0‐5]).){3}([1‐9]?\d|1\d{2}|2[0‐4]\d|25[0‐5])

Re 库的基本使用

raw string 类型(原生字符串类型)

re 库采用 raw string 类型表示正则表达式,表示为:

r’text’

例如

1
2
r'[1‐9]\d{5}'
r'\d{3}‐\d{8}|\d{4}‐\d{7}'

raw string 是不包含对转义符再次转义的字符串

re 库也可以采用 string 类型表示正则表达式

例如

1
2
'[1‐9]\\d{5}'
'\\d{3}‐\\d{8}|\\d{4}‐\\d{7}'

建议:当正则表达式包含转义符时,使用 raw string

Re 库主要功能函数

函数 说明
re.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回 match 对象
re.match() 从一个字符串的开始位置起匹配正则表达式,返回 match 对象
re.findall() 搜索字符串,以列表类型返回全部能匹配的子串
re.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型
re.finditer() 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是 match 对象
re.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串
re.search(pattern, string, flags=0)

在一个字符串中搜索匹配正则表达式的第一个位置,返回 match 对象

  • pattern : 正则表达式的字符串或原生字符串表示

  • string : 待匹配字符串

  • flags : 正则表达式使用时的控制标记

    | 常用标记 | 说明 |
    | :——————-: | :———————————————————-: |
    | re.I re.IGNORECASE | 忽略正则表达式的大小写,[A‐Z] 能够匹配小写字符 |
    | re.M re.MULTILINE | 正则表达式中的 ^ 操作符能够将给定字符串的每行当作匹配开始 |
    | re.S re.DOTALL | . 操作符能够匹配所有字符(包括换行符),默认只匹配除换行外的所有字符 |

1
2
3
4
import re
match = re.search(r'[1-9]\d{5}', "BIT 100081")
if match:
print(match.group(0)) # 100081
re.match(pattern, string, flags=0)

从一个字符串的开始位置起匹配正则表达式,返回 match 对象。参数同 search()

1
2
3
4
5
import re
match = re.match(r'[1-9]\d{5}', "BIT 100081")
print(type(match)) # <class 'NoneType'>
if match: # 没有匹配到数据,下面的代码不会执行
print(match.group(0))
re.findall(pattern, string, flags=0)

搜索字符串,以列表类型返回全部能匹配的子串。参数同 search()

1
2
ls = re.findall(r'[1-9]\d{5}', "BIT100081 TSU100083")
print(ls) # ['100081', '100083']
re.split(pattern, string, maxsplit=0, flags=0)

将一个字符串按照正则表达式匹配结果进行分割,返回列表类型。参数同 search()

  • maxsplit: 最大分割数,剩余部分作为最后一个元素输出
1
2
3
4
5
import re
ls = re.split(r'[1-9]\d{5}', "BIT100081 TSU100083")
print(ls) # ['BIT', ' TSU', '']
ls = re.split(r'[1-9]\d{5}', "BIT100081 TSU100083", maxsplit=1)
print(ls) # ['BIT', ' TSU100083']
re.finditer(pattern, string, flags=0)

搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是 match 对象。参数同 search()

1
2
3
4
5
6
import re
it = re.finditer(r'[1-9]\d{5}', "BIT100081 TSU100083")
for m in it:
print(m.group(0))
# 100081
# 100083
re.sub(pattern, repl, string, count=0, flags=0)

在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串。参数同 search()

  • repl : 替换匹配字符串的字符串
  • count : 匹配的最大替换次数
1
2
3
4
5
import re
s = re.sub(r'[1-9]\d{5}', ':zipcode', 'BIT100081 TSU100083')
print(s) # BIT:zipcode TSU:zipcode
s = re.sub(r'[1-9]\d{5}', ':zipcode', 'BIT100081 TSU100083', count=1)
print(s) # BIT:zipcode TSU100083

Re 库的另一种等价用法

1
2
3
4
5
import re
rst = re.search(r'[1‐9]\d{5}', 'BIT 100081')
# 等价于
pat = re.compile(r'[1‐9]\d{5}')
rst = pat.search('BIT 100081')
regex = re.compile(pattern, flags=0)

将正则表达式的字符串形式编译成正则表达式对象

函数 说明
regex.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回 match 对象
regex.match() 从一个字符串的开始位置起匹配正则表达式,返回 match 对象
regex.findall() 搜索字符串,以列表类型返回全部能匹配的子串
regex.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型
regex.finditer() 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是 match 对象
regex.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

Re 库的 Match 对象

Match 对象是一次匹配的结果,包含匹配的很多信息

1
2
3
4
5
import re
match = re.search(r'[1-9]\d{5}', "BIT 100081")
if match:
print(match.group(0)) # 100081
print(type(match)) # <class '_sre.SRE_Match'>
Match 对象的属性
属性 说明
.string 待匹配的文本
.re 匹配时使用的 patter 对象(正则表达式)
.pos 正则表达式搜索文本的开始位置
.endpos 正则表达式搜索文本的结束位置
Match 对象的方法
方法 说明
.group(0) 获得匹配后的字符串
.start() 匹配字符串在原始字符串的开始位置
.end() 匹配字符串在原始字符串的结束位置
.span() 返回 (.start(), .end())
1
2
3
4
5
6
7
8
9
10
import re
m = re.search(r'[1-9]\d{5}', 'BIT100081 TSU100083')
print(m.string) # BIT100081 TSU100083
print(m.re) # re.compile('[1-9]\\d{5}')
print(m.pos) # 0
print(m.endpos) # 19
print(m.group(0)) # 100081
print(m.start()) # 3
print(m.end()) # 9
print(m.span()) # (3, 9)

Re 库的贪婪匹配和最小匹配

默认贪婪匹配,即输出匹配最长的子串

贪婪匹配
1
2
match = re.search(r'PY.*N', 'PYANBNCNDN')
print(match.group(0)) # PYANBNCNDN
最小匹配

只要长度输出可能不同的,都可以通过在操作符后增加 ? 变成最小匹配

操作符 说明
*? 前一个字符 0 次或无限次扩展,最小匹配
+? 前一个字符 1 次或无限次扩展,最小匹配
?? 前一个字符 0 次或 1 次扩展,最小匹配
{m,n}? 扩展前一个字符 m 至 n 次( 含 n ),最小匹配
1
2
match = re.search(r'PY.*?N', 'PYANBNCNDN')
print(match.group(0)) # PYAN

实例

淘宝商品比价定向爬虫示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import requests
import re


def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except Exception as e:
return ""


def parsePage(infoList, html):
try:
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"', html)
tlt = re.findall(r'\"raw_title\"\:\".*?\"', html)
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
infoList.append([price, title])
except Exception as e:
print()


def printGoodsList(infoList):
template = "{:4}\t{:8}\t{:16}"
print(template.format("序号", "价格", "商品名称"))
count = 0
for g in infoList:
count += 1
print(template.format(count, g[0], g[1]))


def main():
goods = "书包"

start_url = "https://s.taobao.com/search?q=" + goods
infoList = []

html = getHTMLText(start_url)
parsePage(infoList, html)

# 由于添加 s 参数淘宝做了登录验证,下面的代码先放着
# depth = 2
# for i in range(depth):
# try:
# url = start_url + "&s=" + str(44 * i)
# html = getHTMLText(url)
# parsePage(infoList, html)
# except Exception as e:
# continue
printGoodsList(infoList)


main()

股票数据定向爬虫示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import requests
from bs4 import BeautifulSoup
import traceback
import re


def getHTMLText(url, code='utf-8'):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = code
return r.text
except:
return ""


def getStockList(sList, sUrl):
html = getHTMLText(sUrl, 'GB2313')
soup = BeautifulSoup(html, 'html.parser')
aList = soup.find_all('a')
for a in aList:
try:
href = a.attrs['href']
sList.append(re.findall(r's[hz]\d{6}', href)[0])
except:
continue


def getStockInfo(sList, sUrl, fPath):
count = 0
for stock in sList:
url = sUrl + stock + ".html"
html = getHTMLText(url)

try:
if html == "":
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div', attrs={'class': 'stock-bets'})

name = stockInfo.find_all(attrs={'class': 'bets-name'})[0]
infoDict.update({'股票名称': name.text.split()[0]})

keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
value = valueList[i].text
infoDict[key] = value

with open(fPath, 'a', encoding='utf-8') as f:
f.write(str(infoDict) + "\n")
count += 1
print('\r当前进度:{:.2f}%'.format(count * 100 / len(sList)), end="")
except:
traceback.print_exc()
count += 1
print('\r当前进度:{:.2f}%'.format(count * 100 / len(sList)), end="")
continue


def main():
stock_list_url = "http://quote.eastmoney.com/stocklist.html"
stock_info_url = "https://gupiao.baidu.com/stock/"
output_file = "BaiduStockInfo.txt"
sList = []
getStockList(sList, stock_list_url)
getStockInfo(sList, stock_info_url, output_file)


main()