前言
正則表達式作為一名合格的程序員的必備的基本技術之一,其有用性不言而喻。但是它為什么會非常難以掌握,甚至想用一用也都感覺難以下手呢?本文將會讓你一次就看會如何使用Python正則表達式。
1. 正則表達式的組成
在介紹如何使用Python的正則表達式時,我們需要先認識一下正則表達式的各種功能,以及其組成形式如何。
正則表達式可以從非結構化的文本中提取到我們想要的內容,其本質為模式匹配,也是體現出智能化的最初手段,現在已經廣泛應用于自動化處理信息的流程之中,從爬蟲到人工智能,無處不在,其需求也是相當的大。
一提及正則表達式的編寫,在N多的博客里都提到了一個神奇的網站http://www.txt2re.com/,這個網站我之前也用過,如果你不會正則表達式,又想偷懶自動生成,你只需要在這個網站里復制粘貼一個最復雜的情況,然后把你想匹配的內容通過可視化的點擊組合就可以自動生成你想要的正則表達式。
但是遺憾的是,這個網站目前已經打不開了。這也正是告訴我們,核心技術掌握在自己手里才是真啊?,F在我們來看看正則表達式該如何編寫。下面是使用進行正則表達式的一般流程,不同的語言其實現方法不完全相同,我們今天主要聚焦與使用python進行正則表達式的三種匹配方式,以獲得我們想要的目標片段,對于其他方法,我們以后再進行講解。
首先確定你的輸入的大致格式
在這個輸入的大致格式中定位到你需要的內容,以及你不需要的內容。
通過正則表達式將其匹配出來
抽取其臨時的結果將其保存到我們需要的數據結構中
2. 使用python表示正則表達式流程
如果我們使用python進行一個正則表達式正則時,我們主要經歷一下幾個步驟:
導入包
根據需求指定正則表達式
編譯自定義的表達式
根據其表達式進行匹配
輸出結果
1,3,5都是相對容易的部分,而其中最難的部分主要有兩步,一個是制定一個符合需求的正則表達式,另一個則是如何進行匹配。我們最后會簡單的介紹一下輸出結果。
3. 編寫正則表達式
編寫正則表達式是其中的核心,如何編寫正確的,符合我們想法的表達式呢?我們這里介紹下面兩個部分進行構建:
原封不動的單詞
這一部分并不是我們需要的,只是一些留存在我們需要的內容中間的部分。原封不動的單詞就原封不動的抄寫上去,不要增加格外的形式。
待匹配的部分
這一部分使我們想抽取出的內容,將我們想匹配的部分使用正則表達式進行表達分為兩個部分,一個部分為我們的匹配的字符,例如:
w 匹配字母數字及下劃線
W 匹配非字母數字及下劃線
s 匹配任意空白字符,等價于 [tnrf].
S 匹配任意非空字符
d 匹配任意數字,等價于 [0-9].
D 匹配任意非數字
A 匹配字符串開始
Z 匹配字符串結束,如果是存在換行,只匹配到換行前的結束字符串。
z 匹配字符串結束
G 匹配最后匹配完成的位置。
b 匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, ‘erb’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
B 匹配非單詞邊界?!甧rB’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
n, t, 等. 匹配一個換行符。匹配一個制表符。等
1…9 匹配第n個分組的內容。
10 匹配第n個分組的內容,如果它經匹配。否則指的是八進制字符碼的表達式。
另一種,則是匹配的模式,決定我們如何進行匹配:
^ 匹配字符串的開頭
$ 匹配字符串的末尾。
. 匹配任意字符,除了換行符,當re.DOTALL標記被指定時,則可以匹配包括換行符的任意字符。
[…] 用來表示一組字符,單獨列出:[amk] 匹配 ‘a’,‘m’或’k’
[^…] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re* 匹配0個或多個的表達式。
re+ 匹配1個或多個的表達式。
re? 匹配0個或1個由前面的正則表達式定義的片段,非貪婪方式
re{ n} 精確匹配 n 個前面表達式。例如, o{2} 不能匹配 “Bob” 中的 “o”,但是能匹配 “food” 中的兩個 o。
re{ n,} 匹配 n 個前面表達式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o?!皁{1,}” 等價于 “o+”?!皁{0,}” 則等價于 “o*”。
re{ n, m} 匹配 n 到 m 次由前面的正則表達式定義的片段,貪婪方式
a| b 匹配a或b
(re) 對正則表達式分組并記住匹配的文本
(?imx) 正則表達式包含三種可選標志:i, m, 或 x 。只影響括號中的區域。
(?-imx) 正則表達式關閉 i, m, 或 x 可選標志。只影響括號中的區域。
(?: re) 類似 (…), 但是不表示一個組
(?imx: re) 在括號中使用i, m, 或 x 可選標志
(?-imx: re) 在括號中不使用i, m, 或 x 可選標志
(?#…) 注釋.
(?= re) 前向肯定界定符。如果所含正則表達式,以 … 表示,在當前位置成功匹配時成功,否則失敗。但一旦所含表達式已經嘗試,匹配引擎根本沒有提高;模式的剩余部分還要嘗試界定符的右邊。
(?! re) 前向否定界定符。與肯定界定符相反;當所含表達式不能在字符串當前位置匹配時成功
(?> re) 匹配的獨立模式,省去回溯。
兩者搭配即可完成我們想要的結果,雖然這里列舉了很多,但是我們常用的就那幾個,正則表達式的簡單劃分將正則表達式劃分為元字符、反義、量詞和懶惰限定詞。我會在后面的部分給出一個實例。當我們的正則表達式撰寫完畢后,我們使用下面的函數獲得我們的匹配模板。
re.compile(pattern[, flags])
4. 匹配的方式
正則表達式匹配的方式主要有3種match, search和findall。如果你懂英語的話,就知道它們的區別,這里前兩個都是單一匹配,只會匹配一個流程,如果有多個符合匹配規則的,它們只會返回第一個結果,而findall會把所有符合候選的都匹配出來。而前兩個的區別就是match必須是開頭就要能夠匹配,也就是和startwith差不多的效果,而search則可以在任意位置進行匹配。
下面看一下三個方法的參數表示,其中pattern為我們制定的正則表達式,string為我們要匹配的字符串,flags表示匹配模式:
re.match(pattern, string, flags=0)
re.search(pattern, string, flags=0)
findall(string[, pos[, endpos]])
因此我們選擇方式時有以下幾個步驟:
是否需要匹配多個?是,選擇findall
是否需要從頭匹配?是,選擇match
一般情況使用search
5. 匹配結果展示
匹配結果展示主要有以下四個部分組成:
group([group1, …]) 方法用于獲得一個或多個分組匹配的字符串,當要獲得整個匹配的子串時,可直接使用 group() 或 group(0);
start([group]) 方法用于獲取分組匹配的子串在整個字符串中的起始位置(子串第一個字符的索引),參數默認值為 0;
end([group]) 方法用于獲取分組匹配的子串在整個字符串中的結束位置(子串最后一個字符的索引+1),參數默認值為 0;
span([group]) 方法返回 (start(group), end(group))。
例如下面這個例子,主要表現了我們如何調用這四個部分。
>>>import re
>>> pattern = re.compile(r'd+') # 用于匹配至少一個數字
>>> m = pattern.match('one12twothree34four') # 查找頭部,沒有匹配
>>> print m
None
>>> m = pattern.match('one12twothree34four', 2, 10) # 從'e'的位置開始匹配,沒有匹配
>>> print m
None
>>> m = pattern.match('one12twothree34four', 3, 10) # 從'1'的位置開始匹配,正好匹配
>>> print m # 返回一個 Match 對象
<_sre.SRE_Match object at 0x10a42aac0>
>>> m.group(0) # 可省略 0
'12'
>>> m.start(0) # 可省略 0
3
>>> m.end(0) # 可省略 0
5
>>> m.span(0) # 可省略 0
(3, 5)
6. 舉一個簡單的例子
一個更好的,更直觀易懂的方法是如下這個例子,相比較剛才使用數字索引,它將每一個匹配內容語義化,使得代碼更加容易理解。
contactInfo = ' ( Nucleus (span 2 3) (rel2par span)'
pattern=re.compile(r'(?P<nuclearity>w+) (span (?P<start>w+) (?P<end>w+)) (rel2par (?P<relation>w+))')
match = pattern.search(contactInfo)
print(match.group()) # Nucleus (span 2 3) (rel2par span)
print(match.group("nuclearity")) # Nucleus
print(match.group("start")) # 2
print(match.group("end")) # 3
print(match.group("relation")) # span
從上述的例子中我們就可以獲得最直觀的結果,我們只需要將這些結果存入到我們需要的數據結構中即可。
7. 其他一些補充知識
7.1 匹配常用的一些格式
如果我們只需要匹配一些常用的格式,如姓名、身份證、郵箱、電話號碼等,都是有現成的工具直接生成,不需要我們進行再次編寫。
7.2 匹配中文字符
如果你只是想匹配若干個中文漢字,使用下面的正則表達式:
[u4E00-u9FA5\s]+ 多個漢字,包括空格
[u4E00-u9FA5]+ 多個漢字,不包括空格
[u4E00-u9FA5] 一個漢字
這里還有匹配更全的中文字的方法。
提到用正則表達式匹配漢字,很容易搜到這個[u4e00-u9fa5],但是它不算全面,不包含一些生僻漢字。
本文對此問題做一個梳理。
以下是比較全面的漢字Unicode分布,參考Unicode 10.0標準(2017年6月發布):


★ 如果想表示最普遍的漢字,用:
[u4E00-u9FFF] 或 [一-?]
共有20950個漢字,包括了常用簡體字和繁體字,镕等字。
基本就是GBK的所有(21003個)漢字。也包括了BIG5的所有(13053個)繁體漢字。
一般情況下這個就夠用了。
說明:
僅僅未包括出現在GBK里的CJK兼容漢字的21個漢字:郎涼秊裏隣兀嗀﨎﨏﨑﨓﨔禮﨟蘒﨡﨣﨤﨧﨨﨩
CJK兼容漢字用于轉碼處理,日常中是用不到的,所以不包括也沒什么問題。
注意此涼非彼涼,兀也不是常用的那個,雖然用眼睛看是一樣的,參見
http://www.zhihu.com/question/20697984
★ 如果想表示BMP之內的漢字,也就是Unicode值<=0xFFFF之內的所有漢字,用:
[u4E00-u9FFFu3400-u4DBFuF900-uFAFF]
這個包含但不限于GBK定義的漢字,共有28025個漢字。
說明:
和上面相比,主要是多了CJK統一漢字擴展A區,這是1999年收錄到Unicode 3.0標準里的6,582個漢字。
CJK統一漢字擴展A區,包括了東亞各地區(陸港臺日韓新越)的漢字,有很多康熙字典的繁體字。
★ 如果想盡可能表示所有的漢字,用:
[u4E00-u9FFFu3400-u4DBFuF900-uFAFFU00020000-U0002EBEF]
這個包含上表的所有88342個漢字
說明:
1, 以上正則表達式不會匹配(英文、漢字的)標點符號,不會匹配韓國拼音字、日本假名。
2, 會匹配一些日本、韓國獨有的漢字。
3, 包含了一些沒有漢字的空位置,這通常不礙事。
4, u及U的正則語法在Python 3.5上測試通過。
有些正則表達式引擎不認uFFFF和UFFFFFFFF這樣的語法,可以換成x{FFFF}試一下;有些不支持BMP之外的范圍,這就沒辦法處理CJK統一漢字擴展B~E區了,如notepad++。
7.3 匹配一些特殊符號
正則表達式的各種括號的用處以及如何進行括號的匹配。
匹配小括號中的內容
import re
string = 'shain(love)fufu)'
p1 = re.compile(r'[(](.*?)[)]', re.S) #最小匹配
p2 = re.compile(r'[(](.*)[)]', re.S) #貪婪匹配
print(re.findall(p1, string))
print(re.findall(p2, string))
輸出:
[‘love’]
[‘love)fufu’]
匹配中括號中的內容
import re
string = 'shain[胖妮shain和傻夫夫fufu]fufu)'
p =r'[[][Ww]+[]]'
print(re.findall(p, string))
輸出:
[’[胖妮shain和傻夫夫fufu]’]
匹配大括號中的內容
import re
string = "shain,fsf{傻夫夫,grr},胖妮{fsf,1201}"
p = re.findall(r'({.*?})', string)
print(p)
輸出:
[’{傻夫夫,grr}’, ‘{fsf,1201}’]
版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。