Python入門系列23 - 正則表達(dá)式(一)
Python入門系列23

正則表達(dá)式(一)
本篇文字約為4100字,閱讀時間約為16分鐘。
1
前言
今天來介紹一下Python的正則表達(dá)式。先來看下定義,何為正則表達(dá)式?
正則表達(dá)式是一個特殊的字符序列,一個字符串是否與我們給定的這個字符序列相匹配。正則最重要的功能就是處理字符串,例如檢索你在某一段字符串中的特定單詞,或者將原來某個位置的特定字符換成你想要的字符。而對于爬蟲來說,正則表達(dá)式是必不可少的技能之一,要想正確提取源代碼中你想要的信息內(nèi)容,一般來說都會用到正則。
2
Python的re模塊初體驗
這里用例子來假設(shè)一個場景吧...現(xiàn)在你正打算轉(zhuǎn)行踏入程序員的領(lǐng)域,然而面臨的第一個問題就是選擇一個主修語言來作為你轉(zhuǎn)行后的學(xué)習(xí)動力。于是有個字符串language="Java,Python,Go,Js,C,C++,PHP",你下定決心要學(xué)Python,于是讓你判斷Python這門語言是否在這個字符串中存在!你會怎么做呢?
方案一:
通過python內(nèi)置函數(shù)string.index('Python')
language?=?'Java,Python,Go,Js,C,C++,PHP'
print(language.index('Python'))
>>> 5結(jié)果說明在索引下標(biāo)第5位開始,尋找到了Python字符串。
方案二:
通過in關(guān)鍵詞
language?=?'Java,Python,Go,Js,C,C++,PHP'
print('Python'?in?language)
>>>?True
結(jié)果說明Python字符串存在于language中。
方案三:
通過re模塊,需要import re。
import?re
language?=?'Java,Python,Go,Js,C,C++,PHP'
"""
???re.findall(pattern,?string,?flags=0):
??????必填的兩個參數(shù):
??????第一個參數(shù)是正則表達(dá)式的模式;
??????第二個參數(shù)是原字符串
??????選填參數(shù):flags ,傳入例如忽略大小寫的官方參數(shù)
???返回的結(jié)果是:?list
"""
result?=?re.findall('Python',language)
print(result)
>>>?['Python']
大家可以仔細(xì)看下代碼,注釋已經(jīng)寫得很清楚啦....使用re.findall,見名知意,是查找到所有匹配到的,所以返回的肯定是list。不信的話,我們將代碼修改一下,在language里多加一個Python,看下結(jié)果如何:

3
Python正則表達(dá)式的原理性概念
這個標(biāo)題起的名字有些拗口,因為我找不到好的概括詞語了,現(xiàn)在就來解釋一下吧....在2中我們通過re模塊來判斷了一個字符串包含不包含與原始字符串,實際上這種用法是沒有意義的,真正的正則表達(dá)式用法場景,應(yīng)該是依賴于規(guī)則!
1. 普通字符和元字符
還是舉例說明,還是上邊找工作的那堆語言,假設(shè)每門語言的分割,我不用逗號進(jìn)行分割了,我將各種公共電話號碼隨機(jī)插入來代替逗號,于是得到language="Java110Python120Go119Js4399C999C++666PHP"。
需求變更了,現(xiàn)在讓你提取出電話號碼!如何去做呢?
import?re
language="Java110Python120Go119Js4399C999C++666PHP"
"""
???re.findall(pattern,?string,?flags=0):
??????必填的兩個參數(shù):
??????第一個參數(shù)是正則表達(dá)式的模式;
??????第二個參數(shù)是原字符串
??????選填參數(shù):flags,傳入例如忽略大小寫的官方參數(shù)
???返回的結(jié)果是:?list
"""
result?=?re.findall('\d',language)
print(result)
>>>?['1',?'1',?'0',?'1',?'2',?'0',?'1',?'1',?'9',?'4',?'3',?'9',?'9',?'9',?'9',?'9',?'6',?'6',?'6']
由于每個數(shù)字都不近相同,你可以用一堆數(shù)字去寫,但是比較麻煩。若換成字符,難不成還寫滿了你要匹配的字符嗎?所以這里提出兩個概念:普通字符和元字符。
普通字符:re.findall('Python',language)中的'Python',就是普通字符。
元字符:re.findall('\d',language)中的'\d',就是元字符。
所謂的普通字符,就是你寫死了的固定字符,而元字符則不一樣,它代表的是一類的字符,而我現(xiàn)在寫的'\d'的含義就是匹配所有數(shù)字。需要注意的是,普通字符和元字符是在正則表達(dá)式中可以混合使用的!
而我這里不會詳細(xì)的介紹每個元字符的用法,重要的是自己查閱相關(guān)文檔的方法,而非死記硬背這些元字符的含義,可以用到的時候再去查即可。這里給出相關(guān)圖吧,有興趣的可以看下:


以上圖片原址:
https://baike.baidu.com/item/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/1700215?fr=aladdin#3
具體使用可以自行百度查看。
2. 字符集
在上面的例子中,我們可以看到,正則表達(dá)式匹配完的list得到的都是一個個拆分的字符,而所謂的字符集就是匹配出來的字符集合,也就是多個字符。如下面的例子:
import re
words = "abz,acz,adz,aez,afz"
result = re.findall('a[bcd]z', words)
print(result)
>>> ['abz', 'acz', 'adz']若想匹配到原字符串中的多個字符,我們可以用[]這樣的形式來匹配多個符合的內(nèi)容,而[]中的元素相互之間是或的關(guān)系。a,z來限定邊界, [bcd] 則是匹配a,z中間符合的bcd的元素,所以最終得到的結(jié)果如上。
import re
words = "abz,acz,adz,aez,afz"
result = re.findall('a[^bcd]z', words)
print(result)
>>> ['aez', 'afz']若在字符集的正則規(guī)定前,加上^則是代表取反的意思,可以看到結(jié)果,不匹配bcd,最終結(jié)果得到的是e,f。
當(dāng)然還有一種寫法是下面這樣:
import re
words = "abz,acz,adz,aez,afz"
result = re.findall('a[b-e]z', words)
print(result)
>>> ['abz', 'acz', 'adz', 'aez']用?-?也可以來表示一個范圍,比如A-Z,a-z,0-9等....
3. 數(shù)量詞
import re
words = "I am learning Pythonnnnnn, that 's awesome!"
result = re.findall('[a-z]{4}', words)
print(result)
>>> ['lear', 'ning', 'ytho', 'nnnn', 'that', 'awes']在字符集的基礎(chǔ)上,我們?nèi)绻胂薅ǘ嗌匍L度的字符,可以通過{}來限定匹配字符集的長度,當(dāng)我寫入4的時候,可以看到從左到右,只有當(dāng)相鄰字符組成的長度為4時,才會被匹配到。
然而這樣匹配是沒有實際意義的,我現(xiàn)在需要的是將每個詞語拆分并且匹配到,于是有了下面的寫法:
import?re
words?=?"I?am?learning?Pythonnnnnn,?that?'s?awesome!"
result?=?re.findall('[a-zA-Z]{1,11}',?words)
print(result)
>>>?['I',?'am',?'learning',?'Pythonnnnnn',?'that',?'s',?'awesome']
{最短匹配長度,最長匹配長度},在配合上字符集的正則寫法,既可拆分出原有的單詞,注意的是[a-zA-Z] 這里用到了組合的寫法,匹配字符是符合大小寫a-z的。
大家有沒有注意到一個點(diǎn),就是在數(shù)量詞這里,我對原字符串進(jìn)行正則匹配,取得長度是1~11位,那么在am時候就已經(jīng)符合一位長度了,即字母a,為什么沒有被單獨(dú)匹配出來作為結(jié)果呢?這里就涉及到下面要說的正則表達(dá)式的貪婪模式與非貪婪模式了。
4
正則表達(dá)式的貪婪與非貪婪模式
貪婪模式:意如其名,貪婪嘛,就是要多多多!多匹配既是王道!
非貪婪模式:與貪婪模式相反唄,即少匹配就是王道!
對應(yīng)上面的數(shù)量詞案例時,數(shù)量詞若是采用范圍取長度時,則是默認(rèn)使用貪婪模式,即多多匹配,所以才會看到能匹配到字符集長度多的單詞。而Python默認(rèn)傾向于是貪婪模式的!
1.? “?” 匹配0次或者1次
在實際的應(yīng)用中,我們也會遇到非貪婪的情況,即少匹配,那么如何書寫呢?
import?re
words?=?"I?am?learning?Pythonnnnnn,?that?'s?awesome!"
result?=?re.findall('[a-zA-Z]{1,11}?',?words)
print(result)
>>>?['I',?'a',?'m',?'l',?'e',?'a',?'r',?'n',?'i',?'n',?'g',?'P',?'y',?'t',?'h',?'o',?'n',?'n',?'n',?'n',?'n',?'n',?'t',?'h',?'a',?'t',?'s',?'a',?'w',?'e',?'s',?'o',?'m',?'e']
只需要在數(shù)量詞后面加一個?即可。代表的是匹配前面的數(shù)量詞0次或者1次。
特殊的小技巧:?一般可以來去掉字符后面的重復(fù)項,例如原字符串:"Pythonnnnn",可以用 'Python?' 得到Python, 這點(diǎn)自己可以思考并實驗下。
2.?“*” 匹配0次或者無限次
下面這段代碼,你認(rèn)為會打印出什么樣的結(jié)果呢?
import?rewords?=?"I?am?learning?Pytho,Python,Pythonnnnnn,?that?'s?awesome!"
result?=?re.findall('Pytho*',?words)
print(result)
輸出結(jié)果:
>>>?['Pytho',?'Pytho',?'Pytho']因為在原字符串中匹配到o,后面就截止了。
若是這樣呢:
import?rewords?=?"I?am?learning?Pytho,Python,Pythonnnnnn,?that?'s?awesome!"
result?=?re.findall('Python*',?words)
print(result)
#輸出結(jié)果
>>> ['Pytho',?'Python',?'Pythonnnnnn']
匹配*號的前一個字符無限次,所以有多少n,就會被匹配到多少n。為什么'Pytho'也能被匹配到呢?因為*號代表的是前面的字符匹配到0次也是可以的!所以pytho也會被匹配到,這里需要注意。
3. “+” 匹配1次或者無限次
import?re
words?=?"I?am?learning?Pytho,Python,Pythonnnnnn,?that?'s?awesome!"
result?=?re.findall('Python+',?words)
print(result)
# 輸出結(jié)果
>>> ['Python', 'Pythonnnnnn']+號是匹配前面的字符至少一次,或者n次。所以不會匹配到Pytho!
4. “.” 匹配除換行符\n之外的其他所有字符
import?re
words?=?"Python?\n?!@#%%$*&(%)"
result?=?re.findall('.',?words)
print(result)
>>>?['P',?'y',?'t',?'h',?'o',?'n',?'?',?'?',?'!',?'@',?'#',?'%',?'%',?'$',?'*',?'&',?'(',?'%',?')']
可以看到,除了\n以外的字符都匹配到了,包括空格以及其他奇怪的字符~
5
正則表達(dá)式組的概念
在說組之前,再來介紹一個正則的用法:
^: 匹配原字符串的首位置開始
$: 匹配原字符串的尾位置結(jié)束
如下事例:
import?re
words?=?"110120119999"
result?=?re.findall('^\d{1,9}$',?words)
print(result)
>>>?[]
因為^和$限定了原始字符串的頭和尾,從原始字符串中匹配1~9位長度的數(shù)字,并且是從頭到尾的去匹配,并沒有長度為9的數(shù)字,所以匹配出來是空的。
正則表達(dá)式組的概念:還記得之前python的基礎(chǔ)數(shù)據(jù)類型元組嗎?正則中的組,與字符集恰好相反,字符集中匹配的字符之間是或的關(guān)系,而組中匹配的字符之間則是與的關(guān)系。
場景如下,我現(xiàn)在想匹配整個hungry!!而若是通過字符匹配的話方法:
import re
words = "I'm hungry!!hungry!!hungry!!...."
result = re.findall('hungry!!{3}', words)
print(result)
>>> []可以看到,什么也沒匹配到,因為現(xiàn)在寫的意思是匹配前面是hungry!!且最后一個嘆號長度為3個,也就是說如果字符串中有hungry!!!!,即可匹配到。

如果想匹配到整個hungry!!并且連續(xù)的單詞出現(xiàn)3次,就要這么寫啦:
import re
words = "I'm hungry!!hungry!!hungry!!!!...."
result = re.findall('(hungry!!){3}', words)
print(result)
>>> ['hungry!!']用小括號將整個單詞括起來,它就是所謂的分組了。若我要是將{3}改成{4},就會發(fā)現(xiàn),匹配不到這個結(jié)果了,因為需要組內(nèi)連續(xù)出現(xiàn)4次才可以匹配到。如下:

6
總結(jié)
今天主要介紹了python的re模塊的findall方法,此方法是入門正則表達(dá)式一個比較好的學(xué)習(xí)方式,通過各種實例可以看到最終匹配到的結(jié)果,而需要注意的點(diǎn)是:普通字符和元字符,元字符相當(dāng)于是系統(tǒng)的寫法,每種寫法代表不同的含義,在使用的時候可以將二者混合使用來達(dá)到你最終想匹配的值,現(xiàn)在也許看著正則還會有很多的疑惑,后續(xù)寫到爬蟲的時候,一講就明白了!
至此完!
