瞭解了正則表達式,想必通常狀況下的匹配都不會出現什麼問題,可是若是一些特殊狀況,可能須要用到一些更高級的正則表達式匹配操做,本節咱們來講明一下正則表達式的一個較經常使用又比較重要的知識點——零寬斷言。
正則表達式
首先咱們來看一個例子,這裏有一段問答對話:瀏覽器
問:我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? 答:在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 問:爲何我看到的卡號輸入框顯示爲*符號? 答:您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇"安全" |"Internet"|"自定義級別",在彈出的對話框中選擇"重置爲 安全級-中" , 點"重置"按鈕,肯定。 問:看了以上幾個問題,仍是不能登陸,怎麼辦? 答:您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 問:沒法出現我的網上銀行大衆版登陸界面。 答:這種狀況是因爲您的機器沒法和我行服務器創建安全鏈接,一般是由於代理服務器設置錯誤引發。若是您是撥號上網,請不要使用代理服務器;若是您過去安裝過我行SSL安全代理,請調用「添加-刪除程序」刪除SSL安全代理;若是您是通過代理訪問Internet,請聯繫您所在網的網絡管理員設置代理服務器。IE5.0瀏覽器設置代理服務器的步驟: Internet選項-->鏈接-->局域網設置-->使用代理服務器-->高級。 問:我在輸入帳號和卡號時,總出錯,該怎樣輸? 答:存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。 問:個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額? 答:存摺必須設有密碼方可在 我的網上銀行大衆版 中查詢,所以請您到存摺開戶行給您的存摺設置密碼。 注:網上我的銀行是招商銀行爲我的客戶提供的網上銀行。 本頁面內容僅供參考,部分業務以當地網點的公告與具體規定爲準。安全
咱們須要將這段對話中的問題和答案對提取出來,即提取出以下內容:服務器
Q:我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件?網絡
A:在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。app
Q:爲何我看到的卡號輸入框顯示爲*符號?ide
A:您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇"安全" |"Internet"|"自定義級別",在彈出的對話框中選擇"重置爲 安全級-中" , 點"重置"按鈕,肯定。工具
...ui
若是要用 Python 實現的話,那麼咱們極可能天然而然想到 split() 或 findall() 方法,若是用 split() 方法,咱們可能會這麼寫:spa
import re results = re.split('問:| 答:', text)
for index, result in enumerate(results[1:]): print(('Q' if index%2 == 0 else 'A') + ': ' + result)
這裏 split() 方法的第一個參數傳入了 `問:| 答:` 這個正則表達式,意思是將這段話用 `問:` 或者 `答:` 分開,這個功能是正則表達式對字符串進行分割的方法,相比直接字符串的 split() 方法功能更爲強大。這裏其實獲得的結果是一個列表,長度是一個奇數,若是咱們把 results 打印出來,結果是這樣的:
['', '我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件?', '在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 ', '爲何我看到的卡號輸入框顯示爲*符號?', '您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇"安全" |"Internet"|"自定義級別",在彈出的對話框中選擇"重置爲 安全級-中" , 點"重置"按鈕,肯定。 ', '看了以上幾個問題,仍是不能登陸,怎麼辦?', '您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 ', '沒法出現我的網上銀行大衆版登陸界面。', '這種狀況是因爲您的機器沒法和我行服務器創建安全鏈接,一般是由於代理服務器設置錯誤引發。若是您是撥號上網,請不要使用代理服務器;若是您過去安裝過我行SSL安全代理,請調用「添加-刪除程序」刪除SSL安全代理;若是您是通過代理訪問Internet,請聯繫您所在網的網絡管理員設置代理服務器。IE5.0瀏覽器設置代理服務器的步驟: Internet選項-->鏈接-->局域網設置-->使用代理服務器-->高級。 ', '我在輸入帳號和卡號時,總出錯,該怎樣輸?', '存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。 ', '個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額?', '存摺必須設有密碼方可在 我的網上銀行大衆版 中查詢,所以請您到存摺開戶行給您的存摺設置密碼。 注:網上我的銀行是招商銀行爲我的客戶提供的網上銀行。 本頁面內容僅供參考,部分業務以當地網點的公告與具體規定爲準。 ']
這是由於咱們分割使用的字符自己就處於整個文本的字符,因此一上來就找到了分割的標誌 `問:`,因此它左側的結果就是空字符串了,因此最終獲得的結果第一個內容就是空字符串,後續的內容即是正常的一問一答的短句。因此這裏咱們還須要對結果進行切片操做,去除第一個元素,而後將其遍歷打印輸出,最終結果以下:
Q: 我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? A: 在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 Q: 爲何我看到的卡號輸入框顯示爲*符號? A: 您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇"安全" |"Internet"|"自定義級別",在彈出的對話框中選擇"重置爲 安全級-中" , 點"重置"按鈕,肯定。 Q: 看了以上幾個問題,仍是不能登陸,怎麼辦? A: 您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 Q: 沒法出現我的網上銀行大衆版登陸界面。 A: 這種狀況是因爲您的機器沒法和我行服務器創建安全鏈接,一般是由於代理服務器設置錯誤引發。若是您是撥號上網,請不要使用代理服務器;若是您過去安裝過我行SSL安全代理,請調用「添加-刪除程序」刪除SSL安全代理;若是您是通過代理訪問Internet,請聯繫您所在網的網絡管理員設置代理服務器。IE5.0瀏覽器設置代理服務器的步驟: Internet選項-->鏈接-->局域網設置-->使用代理服務器-->高級。 Q: 我在輸入帳號和卡號時,總出錯,該怎樣輸? A: 存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。 Q: 個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額? A: 存摺必須設有密碼方可在 我的網上銀行大衆版 中查詢,所以請您到存摺開戶行給您的存摺設置密碼。 注:網上我的銀行是招商銀行爲我的客戶提供的網上銀行。 本頁面內容僅供參考,部分業務以當地網點的公告與具體規定爲準。
這樣確實沒問題,咱們能夠順利地提取出來,可是總感受這個解法並不那麼優雅,由於咱們這裏是將問題和答案的內容都單獨切出來了,並無將問答對一塊提取,並且 split() 方法返回的結果的第一個元素還不是咱們想要的結果,因此還須要進行一些切片操做來去除,因此整個寫法感受實現起來並不完美。
因此咱們又想到了 findall() 方法,這時咱們會這麼寫:
import re results = re.findall('問:(.*?) 答:(.*?)', text, re.S)
for result in results: print('Q: ' + result[0], 'A: ' + result[1], sep='\n')
表面上看彷佛是把問題答案對用正則表示出來了,並且使用了非貪婪匹配,可是很明顯,在末尾咱們並無指定匹配的終點,因此整個的結果就會致使回答是徹底匹配不到的,運行結果以下:
Q: 我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? A: Q: 爲何我看到的卡號輸入框顯示爲*符號? A: Q: 看了以上幾個問題,仍是不能登陸,怎麼辦? A: Q: 沒法出現我的網上銀行大衆版登陸界面。 A: Q: 我在輸入帳號和卡號時,總出錯,該怎樣輸? A: Q: 個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額? A:
好,那麼咱們加上匹配的終點吧,如下一個的 `問:` 做爲咱們正則表達式匹配的終點總能夠了吧?因此咱們可能會改寫成這樣子:
import re results = re.findall('問:(.*?) 答:(.*?)問:', text, re.S)
for result in results: print('Q: ' + result[0], 'A: ' + result[1], sep='\n')
這樣寫彷佛看起來是能夠了,但結果倒是這樣的:
Q: 我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? A: 在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 Q: 看了以上幾個問題,仍是不能登陸,怎麼辦? A: 您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 Q: 我在輸入帳號和卡號時,總出錯,該怎樣輸? A: 存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。
結果只剩三個問題答案對了,有三個問答對被「吃」掉了,其實這是由於咱們的正則表達式最後加了 問:的緣故,findall() 方法它會查找全部符合正則表達式的結果,但其中匹配的時候它內部也是有一個查找索引在掃描的。在查找第一個符合要求的結果時,因爲咱們是根據正則表達式結尾的 問:來做爲結束標誌,因此在找到第一個符合要求的結果時,咱們的查找索引就已經移動到了第二個問答對開頭的 問: 上面,即查找索引就已經進入到了第二個問答對的位置了,而在下一次查找符合要求的結果時,索引會繼續日後移動進行掃描,因此它是從第二個問答對的 問: 後面繼續掃描的,因此對於第二個問答對,實際上已經被割裂了,因此它只能查找到第三個問答對的時候才能夠發現符合正則表達式的內容。所以,咱們能夠觀察到,返回的結果只是第1、3、五三個問答對。
因此,若是咱們想要用該方法找到完整的留個問答對,就須要用到零寬斷言了。
解法以下:
import re results = re.findall('問:(.*?) 答:(.*?)(?=問:|\Z)', text, re.S)
for result in results: print('Q: ' + result[0], 'A: ' + result[1], sep='\n')
運行結果以下:
Q: 我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? A: 在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 Q: 爲何我看到的卡號輸入框顯示爲*符號? A: 您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇"安全" |"Internet"|"自定義級別",在彈出的對話框中選擇"重置爲 安全級-中" , 點"重置"按鈕,肯定。 Q: 看了以上幾個問題,仍是不能登陸,怎麼辦? A: 您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 Q: 沒法出現我的網上銀行大衆版登陸界面。 A: 這種狀況是因爲您的機器沒法和我行服務器創建安全鏈接,一般是由於代理服務器設置錯誤引發。若是您是撥號上網,請不要使用代理服務器;若是您過去安裝過我行SSL安全代理,請調用「添加-刪除程序」刪除SSL安全代理;若是您是通過代理訪問Internet,請聯繫您所在網的網絡管理員設置代理服務器。IE5.0瀏覽器設置代理服務器的步驟: Internet選項-->鏈接-->局域網設置-->使用代理服務器-->高級。 Q: 我在輸入帳號和卡號時,總出錯,該怎樣輸? A: 存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。 Q: 個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額? A: 存摺必須設有密碼方可在 我的網上銀行大衆版 中查詢,所以請您到存摺開戶行給您的存摺設置密碼。 注:網上我的銀行是招商銀行爲我的客戶提供的網上銀行。 本頁面內容僅供參考,部分業務以當地網點的公告與具體規定爲準。
這裏咱們其實是使用了 (?=)
這樣的形式來構建了整個表達式,等號後面的內容是 問:
或者結束符 \Z
,這樣其實就保證了在匹配的時候,查找索引不會繼續向後移,但這也同時標誌告終束標誌,所以它就能夠查找到完整的內容了。
零寬斷言,顧名思義,是一種零寬度的匹配,它匹配的內容不會保存到匹配結果中,表達式的匹配內容只是表明了一個位置而已,如標明某個字符的右邊界是怎樣的構造。
在前面咱們使用了 ?=
來進行了實例講解,這是其中一個用法,另外還有 ?<=
、?!
、?<!
,下面咱們來依次進行講解說明。
?=
表明零寬度正預測先行斷言,它斷言自身出現的位置的後面能夠匹配後面跟的表達式。
?<=
表明零寬度正回顧後發斷言,它斷言自身出現的位置的前面能夠匹配後面跟的表達式。
?!
表明零寬度負預測先行斷言,它斷言自身出現的位置的後面不能夠匹配後面跟的表達式。
?<!
表明零寬度負回顧後發斷言,它斷言自身出現的位置的後面不能夠匹配後面跟的表達式。
首先咱們來看下 ?=
的用法,它斷言自身出現的位置的後面能夠匹配後面跟的表達式。
好比咱們這裏有這樣的一個字符串:
str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
在這裏咱們想把個人我的郵箱這句話和我的郵箱單獨摘出來,假如咱們不使用零寬斷言的話,咱們須要給我的郵箱後面這一句加一個結束標識符或者單獨匹配郵箱做爲標識符,咱們可能會這麼寫:
import re str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
result = re.search('個人我的郵箱是(.*?),我的博客', str) print('整句結果:' + result.group(), '第一個匹配結果:' + result.group(1), sep='\n')
在正則表達式的最後咱們加了,我的博客
做爲匹配的結束符,而後郵箱部分用非貪婪匹配的模式進行匹配,咱們看下運行結果:
整句結果:個人我的郵箱是cqc@cuiqingcai.com,我的博客
第一個匹配結果:cqc@cuiqingcai.com
咱們能夠看到第一個匹配結果成功獲得了郵箱信息,可是咱們看整句結果缺並不理想,它多匹配了咱們加入的結尾標識,並無獲得正常的一句話。
這時候若是咱們改用 ?=
來匹配,結果就不會帶有此標識符了,改寫以下:
import re str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
result = re.search('個人我的郵箱是(.*?)(?=,我的博客)', str) print('整句結果:' + result.group(), '第一個匹配結果:' + result.group(1), sep='\n')
在這裏咱們將結尾標識符改爲了 (?=,我的博客)
,這樣就將此部份內容做爲零寬度匹配,它表明後面須要跟 ,我的博客
,可是它不會出如今匹配結果中。
運行結果以下:
整句結果:個人我的郵箱是cqc@cuiqingcai.com
第一個匹配結果:cqc@cuiqingcai.com
能夠看到整句結果中已經沒有無用的後綴字符了。
接下來咱們再看下 ?<=
的用法,它表明零寬度正回顧後發斷言,其實就是匹配前面的標識,好比這裏咱們仍是以上面的例子爲例,匹配出我的博客這句話,代碼以下:
import re str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
result = re.search('(?<=,)我的博客是(.*?)(?=,)', str) print('整句結果:' + result.group(), '第一個匹配結果:' + result.group(1), sep='\n')
這裏咱們在我的博客
前面加了一個零寬斷言的逗號符號做爲開頭,使用的就是 ?<=
,句子結尾是用的 ?=
,這樣先後的標識都不會匹配到了,運行結果以下:
整句結果:我的博客是cuiqingcai.com 第一個匹配結果:cuiqingcai.com
能夠看到獲得的整句結果也是完整的一句話。
?!
表明表明零寬度負預測先行斷言,它斷言自身出現的位置的後面不能夠匹配後面跟的表達式。也是用來匹配後面的文本,但這裏是取反,它指定了後面出現的內容不匹配該標識,咱們在前面的例子基礎上修改以下:
import re str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
result = re.search('個人我的郵箱是(.*?)(?!,我的公衆號)(?=,我的博客)', str) print('整句結果:' + result.group(), '第一個匹配結果:' + result.group(1), sep='\n')
原本是 (?=,我的博客)
的標識符,不過這裏咱們使用 ?!
來指定了另外一個標識符,我的公衆號
,這就表明這句話後面跟的須要是(?=,我的博客)
而不是,我的公衆號
,運行結果以下:
整句結果:個人我的郵箱是cqc@cuiqingcai.com
第一個匹配結果:cqc@cuiqingcai.com
?<!
表明零寬度負回顧後發斷言,它斷言自身出現的位置的後面不能夠匹配後面跟的表達式。咱們在前面的例子基礎上加以修改:
import re str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
result = re.search('(?<=,)(?<!。)我的博客是(.*?)(?=,)', str) print('整句結果:' + result.group(), '第一個匹配結果:' + result.group(1), sep='\n')
這裏咱們寫了 ?<!
標識符,後面跟了一個句號,這表明前面不該該出現句號。
運行結果以下:
整句結果:我的博客是cuiqingcai.com 第一個匹配結果:cuiqingcai.com
其實上面的示例中咱們使用了 search() 方法進行了內容匹配,其實這並不經常使用,由於通常咱們更關注的是匹配分組結果的內容,其實更多的用法是用在了 findall() 方法上,它用來匹配多個結果,也就相似於咱們一開始的實例同樣,這裏咱們仍是以剛纔的字符串爲例,來輸出一下我的郵箱、我的博客、我的公衆號三個內容,代碼以下:
import re str = '個人我的郵箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公衆號是進擊的Coder'
results = re.findall('我的(.*?)是(.*?)(?=,|\Z)', str)
for result in results: print(result[0] + ': ' + result[1])
這裏咱們匹配了我的
二字,而後後面跟了非貪婪匹配,而後加了一個是
字,最關鍵的是結尾標識符,這裏必需要使用零寬斷言才能夠匹配出三個結果,這裏匹配的內容是 ,|\Z
,意思是匹配逗號或結束符。
運行結果以下:
郵箱: cqc@cuiqingcai.com
博客: cuiqingcai.com
公衆號: 進擊的Coder
這樣咱們就成功輸出了郵箱、博客及公衆號的內容了,匹配很是順利方便。
經過本節,咱們應該大致能夠了解了正則表達式中零寬斷言的基本用法和適用場景,相信理解了零寬斷言以後,咱們再作正則匹配時會更加駕輕就熟。