目錄
初識 Python 正規表示式
正規表示式是一個特殊的字元序列,用於判斷一個字串是否與我們所設定的字元序列是否匹配,也就是說檢查一個字串是否與某種模式匹配。
Python 自 1.5 版本起增加了re 模組,它提供 Perl 風格的正規表示式模式。re 模組使 Python 語言擁有全部的正規表示式功能。
下面通過例項,一步一步來初步認識正規表示式。
比如在一段字串中尋找是否含有某個字元或某些字元,通常我們使用內建函式來實現,如下:
# 設定一個常量
a = '兩點水|twowater|liangdianshui|草根程式設計師|ReadingWithU'
# 判斷是否有 “兩點水” 這個字串,使用 PY 自帶函式
print('是否含有“兩點水”這個字串:{0}'.format(a.index('兩點水') > -1))
print('是否含有“兩點水”這個字串:{0}'.format('兩點水' in a))
複製程式碼
輸出的結果如下:
是否含有“兩點水”這個字串:True
是否含有“兩點水”這個字串:True
複製程式碼
那麼,如果使用正規表示式呢?
剛剛提到過,Python 給我們提供了 re 模組來實現正規表示式的所有功能,那麼我們先使用其中的一個函式:
re.findall(pattern, string[, flags])
複製程式碼
該函式實現了在字串中找到正規表示式所匹配的所有子串,並組成一個列表返回,具體操作如下:
import re
# 設定一個常量
a = '兩點水|twowater|liangdianshui|草根程式設計師|ReadingWithU'
# 正規表示式
findall = re.findall('兩點水', a)
print(findall)
if len(findall) > 0:
print('a 含有“兩點水”這個字串')
else:
print('a 不含有“兩點水”這個字串')
複製程式碼
輸出的結果:
['兩點水']
a 含有“兩點水”這個字串
複製程式碼
從輸出結果可以看到,可以實現和內建函式一樣的功能,可是在這裡也要強調一點,上面這個例子只是方便我們理解正規表示式,這個正規表示式的寫法是毫無意義的。為什麼這樣說呢?
因為用 Python 自帶函式就能解決的問題,我們就沒必要使用正規表示式了,這樣做多此一舉。而且上面例子中的正規表示式設定成為了一個常量,並不是一個正規表示式的規則,正規表示式的靈魂在於規則,所以這樣做意義不大。
那麼正規表示式的規則怎麼寫呢?先不急,我們一步一步來,先來一個簡單的,找出字串中的所有小寫字母。首先我們在 findall
函式中第一個引數寫正規表示式的規則,其中 [a-z]
就是匹配任何小寫字母,第二個引數只要填寫要匹配的字串就行了。具體如下:
import re
# 設定一個常量
a = '兩點水|twowater|liangdianshui|草根程式設計師|ReadingWithU'
# 選擇 a 裡面的所有小寫英文字母
re_findall = re.findall('[a-z]', a)
print(re_findall)
複製程式碼
輸出的結果:
['t', 'w', 'o', 'w', 'a', 't', 'e', 'r', 'l', 'i', 'a', 'n', 'g', 'd', 'i', 'a', 'n', 's', 'h', 'u', 'i', 'e', 'a', 'd', 'i', 'n', 'g', 'i', 't', 'h']
複製程式碼
這樣我們就拿到了字串中的所有小寫字母了。
字符集
好了,通過上面的幾個例項我們初步認識了 Python 的正規表示式,可能你就會問,正規表示式還有什麼規則,什麼字母代表什麼意思呢?
其實,這些都不急,在本章後面會給出對應的正規表示式規則列表,而且這些東西在網上隨便都能 Google 到。所以現在,我們還是進一步加深對正規表示式的理解,講一下正規表示式的字符集。
字符集是由一對方括號 “[]” 括起來的字符集合。使用字符集,可以匹配多個字元中的一個。
舉個例子,比如你使用 C[ET]O
匹配到的是 CEO 或 CTO ,也就是說 [ET]
代表的是一個 E 或者一個 T 。像上面提到的 [a-z]
,就是所有小寫字母中的其中一個,這裡使用了連字元 “-” 定義一個連續字元的字元範圍。當然,像這種寫法,裡面可以包含多個字元範圍的,比如:[0-9a-fA-F]
,匹配單個的十六進位制數字,且不分大小寫。注意了,字元和範圍定義的先後順序對匹配的結果是沒有任何影響的。
其實說了那麼多,只是想證明,字符集一對方括號 “[]” 裡面的字元關係是或關係,下面看一個例子:
import re
a = 'uav,ubv,ucv,uwv,uzv,ucv,uov'
# 字符集
# 取 u 和 v 中間是 a 或 b 或 c 的字元
findall = re.findall('u[abc]v', a)
print(findall)
# 如果是連續的字母,數字可以使用 - 來代替
l = re.findall('u[a-c]v', a)
print(l)
# 取 u 和 v 中間不是 a 或 b 或 c 的字元
re_findall = re.findall('u[^abc]v', a)
print(re_findall)
複製程式碼
輸出的結果:
['uav', 'ubv', 'ucv', 'ucv']
['uav', 'ubv', 'ucv', 'ucv']
['uwv', 'uzv', 'uov']
複製程式碼
在例子中,使用了取反字符集,也就是在左方括號 “[” 後面緊跟一個尖括號 “^”,就會對字符集取反。需要記住的一點是,取反字符集必須要匹配一個字元。比如:q[^u]
並不意味著:匹配一個 q,後面沒有 u 跟著。它意味著:匹配一個 q,後面跟著一個不是 u 的字元。具體可以對比上面例子中輸出的結果來理解。
我們都知道,正規表示式本身就定義了一些規則,比如 \d
,匹配所有數字字元,其實它是等價於 [0-9],下面也寫了個例子,通過字符集的形式解釋了這些特殊字元。
import re
a = 'uav_ubv_ucv_uwv_uzv_ucv_uov&123-456-789'
# 概括字符集
# \d 相當於 [0-9] ,匹配所有數字字元
# \D 相當於 [^0-9] , 匹配所有非數字字元
findall1 = re.findall('\d', a)
findall2 = re.findall('[0-9]', a)
findall3 = re.findall('\D', a)
findall4 = re.findall('[^0-9]', a)
print(findall1)
print(findall2)
print(findall3)
print(findall4)
# \w 匹配包括下劃線的任何單詞字元,等價於 [A-Za-z0-9_]
findall5 = re.findall('\w', a)
findall6 = re.findall('[A-Za-z0-9_]', a)
print(findall5)
print(findall6)
複製程式碼
輸出結果:
['1', '2', '3', '4', '5', '6', '7', '8', '9']
['1', '2', '3', '4', '5', '6', '7', '8', '9']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '&', '-', '-']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '&', '-', '-']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '1', '2', '3', '4', '5', '6', '7', '8', '9']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '1', '2', '3', '4', '5', '6', '7', '8', '9']
複製程式碼
數量詞
來,繼續加深對正規表示式的理解,這部分理解一下數量詞,為什麼要用數量詞,想想都知道,如果你要匹配幾十上百的字元時,難道你要一個一個的寫,所以就出現了數量詞。
數量詞的詞法是:{min,max} 。min 和 max 都是非負整數。如果逗號有而 max 被忽略了,則 max 沒有限制。如果逗號和 max 都被忽略了,則重複 min 次。比如,\b[1-9][0-9]{3}\b
,匹配的是 1000 ~ 9999 之間的數字( “\b” 表示單詞邊界),而 \b[1-9][0-9]{2,4}\b
,匹配的是一個在 100 ~ 99999 之間的數字。
下面看一個例項,匹配出字串中 4 到 7 個字母的英文
import re
a = 'java*&39android##@@python'
# 數量詞
findall = re.findall('[a-z]{4,7}', a)
print(findall)
複製程式碼
輸出結果:
['java', 'android', 'python']
複製程式碼
注意,這裡有貪婪和非貪婪之分。那麼我們先看下相關的概念:
貪婪模式:它的特性是一次性地讀入整個字串,如果不匹配就吐掉最右邊的一個字元再匹配,直到找到匹配的字串或字串的長度為 0 為止。它的宗旨是讀儘可能多的字元,所以當讀到第一個匹配時就立刻返回。
懶惰模式:它的特性是從字串的左邊開始,試圖不讀入字串中的字元進行匹配,失敗,則多讀一個字元,再匹配,如此迴圈,當找到一個匹配時會返回該匹配的字串,然後再次進行匹配直到字串結束。
上面例子中的就是貪婪的,如果要使用非貪婪,也就是懶惰模式,怎麼呢?
如果要使用非貪婪,則加一個 ?
,上面的例子修改如下:
import re
a = 'java*&39android##@@python'
# 貪婪與非貪婪
re_findall = re.findall('[a-z]{4,7}?', a)
print(re_findall)
複製程式碼
輸出結果如下:
['java', 'andr', 'pyth']
複製程式碼
從輸出的結果可以看出,android 只列印除了 andr ,Python 只列印除了 pyth ,因為這裡使用的是懶惰模式。
當然,還有一些特殊字元也是可以表示數量的,比如:
?
:告訴引擎匹配前導字元 0 次或 1 次
+
:告訴引擎匹配前導字元 1 次或多次
*
:告訴引擎匹配前導字元 0 次或多次
把這部分的知識點總結一下,就是下面這個表了:
貪 婪 | 惰 性 | 描 述 |
---|---|---|
? | ?? | 零次或一次出現,等價於{0,1} |
+ | +? | 一次或多次出現 ,等價於{1,} |
* | *? | 零次或多次出現 ,等價於{0,} |
{n} | {n}? | 恰好 n 次出現 |
{n,m} | {n,m}? | 至少 n 次枝多 m 次出現 |
{n,} | {n,}? | 至少 n 次出現 |
邊界匹配符和組
將上面幾個點,就用了很大的篇幅了,現在介紹一些邊界匹配符和組的概念。
一般的邊界匹配符有以下幾個:
語法 | 描述 |
---|---|
^ | 匹配字串開頭(在有多行的情況中匹配每行的開頭) |
$ | 匹配字串的末尾(在有多行的情況中匹配每行的末尾) |
\A | 僅匹配字串開頭 |
\Z | 僅匹配字串末尾 |
\b | 匹配 \w 和 \W 之間 |
\B | [^\b] |
分組,被括號括起來的表示式就是分組。分組表示式 (...)
其實就是把這部分字元作為一個整體,當然,可以有多分組的情況,每遇到一個分組,編號就會加 1 ,而且分組後面也是可以加數量詞的。
此處本應有例子,考慮到篇幅問題,就不貼了
re.sub
實戰過程中,我們很多時候需要替換字串中的字元,這時候就可以用到 def sub(pattern, repl, string, count=0, flags=0)
函式了,re.sub 共有五個引數。其中三個必選引數:pattern, repl, string ; 兩個可選引數:count, flags .
具體引數意義如下:
引數 | 描述 |
---|---|
pattern | 表示正則中的模式字串 |
repl | repl,就是replacement,被替換的字串的意思 |
string | 即表示要被處理,要被替換的那個 string 字串 |
count | 對於pattern中匹配到的結果,count可以控制對前幾個group進行替換 |
flags | 正規表示式修飾符 |
具體使用可以看下下面的這個例項,註釋都寫的很清楚的了,主要是注意一下,第二個引數是可以傳遞一個函式的,這也是這個方法的強大之處,例如例子裡面的函式 convert
,對傳遞進來要替換的字元進行判斷,替換成不同的字元。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
a = 'Python*Android*Java-888'
# 把字串中的 * 字元替換成 & 字元
sub1 = re.sub('\*', '&', a)
print(sub1)
# 把字串中的第一個 * 字元替換成 & 字元
sub2 = re.sub('\*', '&', a, 1)
print(sub2)
# 把字串中的 * 字元替換成 & 字元,把字元 - 換成 |
# 1、先定義一個函式
def convert(value):
group = value.group()
if (group == '*'):
return '&'
elif (group == '-'):
return '|'
# 第二個引數,要替換的字元可以為一個函式
sub3 = re.sub('[\*-]', convert, a)
print(sub3)
複製程式碼
輸出的結果:
Python&Android&Java-888
Python&Android*Java-888
Python&Android&Java|888
複製程式碼
re.match 和 re.search
re.match 函式
語法:
re.match(pattern, string, flags=0)
複製程式碼
re.match 嘗試從字串的起始位置匹配一個模式,如果不是起始位置匹配成功的話,match() 就返回 none。
re.search 函式
語法:
re.search(pattern, string, flags=0)
複製程式碼
re.search 掃描整個字串並返回第一個成功的匹配。
re.match 和 re.search 的引數,基本一致的,具體描述如下:
引數 | 描述 |
---|---|
pattern | 匹配的正規表示式 |
string | 要匹配的字串 |
flags | 標誌位,用於控制正規表示式的匹配方式,如:是否區分大小寫 |
那麼它們之間有什麼區別呢?
re.match 只匹配字串的開始,如果字串開始不符合正規表示式,則匹配失敗,函式返回 None;而 re.search 匹配整個字串,直到找到一個匹配。這就是它們之間的區別了。
re.match 和 re.search 在網上有很多詳細的介紹了,可是再個人的使用中,還是喜歡使用 re.findall
看下下面的例項,可以對比下 re.search 和 re.findall 的區別,還有多分組的使用。具體看下注釋,對比一下輸出的結果:
示例:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 提取圖片的地址
import re
a = '<img src="https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg">'
# 使用 re.search
search = re.search('<img src="(.*)">', a)
# group(0) 是一個完整的分組
print(search.group(0))
print(search.group(1))
# 使用 re.findall
findall = re.findall('<img src="(.*)">', a)
print(findall)
# 多個分組的使用(比如我們需要提取 img 欄位和圖片地址欄位)
re_search = re.search('<(.*) src="(.*)">', a)
# 列印 img
print(re_search.group(1))
# 列印圖片地址
print(re_search.group(2))
# 列印 img 和圖片地址,以元祖的形式
print(re_search.group(1, 2))
# 或者使用 groups
print(re_search.groups())
複製程式碼
輸出的結果:
<img src="https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg">
https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg
['https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg']
img
https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg
('img', 'https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg')
('img', 'https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg')
複製程式碼
最後,正規表示式是非常厲害的工具,通常可以用來解決字串內建函式無法解決的問題,而且正規表示式大部分語言都是有的。python 的用途很多,但在爬蟲和資料分析這連個模組中都是離不開正規表示式的。所以正規表示式對於學習 Python 來說,真的很重要。最後,附送一些常用的正規表示式和正規表示式和 Python 支援的正規表示式元字元和語法文件。
github:https://github.com/TwoWater/Python/blob/master/python14/%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md
歡迎大家 start ,https://github.com/TwoWater/Python 一下,這是草根學 Python 系列部落格的庫。也可以關注我的微信公眾號: