Date: 2019-07-03
Author: Sun
本節目的:
(1)掌握正則表達式和re模塊使用
(2)python操作正則表達式,匹配貪婪和非貪婪模式使用
(3)掌握常見函數find, findall, search, match, split等用法
正則表達式
正則表達式(Regular Expression)是一種文本模式,包括普通字符(例如,a 到 z 之間的字母)和特殊字符(稱為"元字符")。
正則表達式使用單個字符串來描述、匹配一系列匹配某個句法規則的字符串。
1 為什么使用正則表達式?
列舉幾個比較鮮明的例子幫助你理解。
(1)判斷一個字符串里是否包含數字,如果有,返回true;否則返回false;
(2)給定字符串 str,檢查其是否包含連續重復的字母(a-zA-Z),包含返回 true,否則返回 false
(3) 從一個大文本里面,提取出我們想要數據。
再者比如在工作中我們經常遇到這樣的需求:
1.給你一個字符串,把字符串里面的鏈接、數字、電話等顯示不同的顏色;
2.給你一個包含自定義表情的文字,找出里面的表情,替換成本地的表情圖片;
3.根據用戶的輸入內容,判斷是否是微信號、手機號、郵箱、純數字等;
提示:
對於1和2的情景,我們使用正則表達式+富文本 便可以輕松應對。
對於3,我們只需根據正則表達式的規則,封裝好自己的正則庫,就可以做到一勞永逸了!
**常用的正則匹配工具 **
在線匹配工具:
正則匹配軟件
McTracer (https://pan.baidu.com/s/19Yn49)
2 什么是正則表達式?
正則表達式(regular expression)描述了一種字符串匹配的模式(pattern),可以用來檢查一個串是否含有某種子串、將匹配的子串替換或者從某個串中取出符合某個條件的子串等。
例如:
- runoo+b,可以匹配 runoob、runooob、runoooooob 等,+ 號代表前面的字符必須至少出現一次(1次或多次)。
構造正則表達式的方法和創建數學表達式的方法一樣。也就是用多種元字符與運算符可以將小的表達式結合在一起來創建更大的表達式。正則表達式的組件可以是單個的字符、字符集合、字符范圍、字符間的選擇或者所有這些組件的任意組合。
正則表達式是由普通字符(例如字符 a 到 z)以及特殊字符(稱為"元字符")組成的文字模式。模式描述在搜索文本時要匹配的一個或多個字符串。正則表達式作為一個模板,將某個字符模式與所搜索的字符串進行匹配。
普通字符
普通字符包括沒有顯式指定為元字符的所有可打印和不可打印字符。這包括所有大寫和小寫字母、所有數字、所有標點符號和一些其他符號。
非打印字符
非打印字符也可以是正則表達式的組成部分。下表列出了表示非打印字符的轉義序列:
字符 | 描述 |
---|---|
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一個 Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個原義的 'c' 字符。 |
\f | 匹配一個換頁符。等價於 \x0c 和 \cL。 |
\n | 匹配一個換行符。等價於 \x0a 和 \cJ。 |
\r | 匹配一個回車符。等價於 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價於 [ \f\n\r\t\v]。注意 Unicode 正則表達式會匹配全角空格符。 |
\S | 匹配任何非空白字符。等價於 [^ \f\n\r\t\v]。 |
\t | 匹配一個制表符。等價於 \x09 和 \cI。 |
\v | 匹配一個垂直制表符。等價於 \x0b 和 \cK。 |
特殊字符
所謂特殊字符,就是一些有特殊含義的字符,如上面說的 runoo*b 中的 ,簡單的說就是表示任何字符串的意思。如果要查找字符串中的 * 符號,則需要對 * 進行轉義,即在其前加一個 : runo*ob 匹配 runoob。
許多元字符要求在試圖匹配它們時特別對待。若要匹配這些特殊字符,必須首先使字符"轉義",即,將反斜杠字符\ 放在它們前面。下表列出了正則表達式中的特殊字符:
特別字符 | 描述 |
---|---|
$ | 匹配輸入字符串的結尾位置。如果設置了 RegExp 對象的 Multiline 屬性,則 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,請使用 $。 |
( ) | 標記一個子表達式的開始和結束位置。子表達式可以獲取供以后使用。要匹配這些字符,請使用 ( 和 )。 |
* | 匹配前面的子表達式零次或多次。要匹配 * 字符,請使用 *。 |
+ | 匹配前面的子表達式一次或多次。要匹配 + 字符,請使用 +。 |
. | 匹配除換行符 \n 之外的任何單字符。要匹配 . ,請使用 . 。 |
[ | 標記一個中括號表達式的開始。要匹配 [,請使用 [。 |
? | 匹配前面的子表達式零次或一次,或指明一個非貪婪限定符。要匹配 ? 字符,請使用 ?。 |
\ | 將下一個字符標記為或特殊字符、或原義字符、或向后引用、或八進制轉義符。例如, 'n' 匹配字符 'n'。'\n' 匹配換行符。序列 '\' 匹配 "",而 '(' 則匹配 "("。 |
^ | 匹配輸入字符串的開始位置,除非在方括號表達式中使用,此時它表示不接受該字符集合。要匹配 ^ 字符本身,請使用 ^。 |
{ | 標記限定符表達式的開始。要匹配 {,請使用 {。 |
| | 指明兩項之間的一個選擇。要匹配 |,請使用 |。 |
特殊字符
(1)^ $ * ? + {2} {2, } {2, 5} |
(2)[], [^], [a-z], [0-9], [4|5]
(3) \s, \S, \w, \W
(4) [\u4E00-\u9FA5] () \d
限定符
限定符用來指定正則表達式的一個給定組件必須要出現多少次才能滿足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6種。
正則表達式的限定符有:
字符 | 描述 |
---|---|
* | 匹配前面的子表達式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等價於{0,}。 |
+ | 匹配前面的子表達式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。 |
? | 匹配前面的子表達式零次或一次。例如,"do(es)?" 可以匹配 "do"(or does) 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等價於 {0,1}。 |
{n} | n 是一個非負整數。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。 |
{n,} | n 是一個非負整數。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價於 'o+'。'o{0,}' 則等價於 'o*'。 |
{n,m} | m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價於 'o?'。請注意在逗號和兩個數之間不能有空格。 |
3. 正則表達式元字符和語法
符號 | 說明 | 實例 |
---|---|---|
. | 表示任意字符,如果說指定了 DOTALL 的標識,就表示包括新行在內的所有字符。 | 'abc' >>>'a.c' >>>結果為:'abc' |
^ | 表示字符串開頭。 | 'abc' >>>'^abc' >>>結果為:'abc' |
$ | 表示字符串結尾。 | 'abc' >>>'abc$' >>>結果為:'abc' |
*, +, ? | '*'表示匹配前一個字符重復 0 次到無限次,'+'表示匹配前一個字符重復 1次到無限次,'?'表示匹配前一個字符重復 0 次到1次 | 'abcccd' >>>'abc*' >>>結果為:'abccc''abcccd' >>>'abc+' >>>結果為:'abccc''abcccd' >>>'abc?' >>>結果為:'abc' |
*?, +?, ?? | 前面的*,+,?等都是貪婪匹配,也就是盡可能多匹配,后面加?號使其變成惰性匹配即非貪婪匹配 | 'abc' >>>'abc*?' >>>結果為:'ab''abc' >>>'abc??' >>>結果為:'ab''abc' >>>'abc+?' >>>結果為:'abc' |
{m} | 匹配前一個字符 m 次 | 'abcccd' >>>'abc{3}d' >>>結果為:'abcccd' |
{m,n} | 匹配前一個字符 m 到 n 次 | 'abcccd' >>> 'abc{2,3}d' >>>結果為:'abcccd' |
{m,n}? | 匹配前一個字符 m 到 n 次,並且取盡可能少的情況 | 'abccc' >>> 'abc{2,3}?' >>>結果為:'abcc' |
** | 對特殊字符進行轉義,或者是指定特殊序列 | 'a.c' >>>'a.c' >>> 結果為: 'a.c' |
**[] ** | 表示一個字符集,所有特殊字符在其都失去特殊意義,只有: ^ - ] \ 含有特殊含義 | 'abcd' >>>'a[bc]' >>>結果為:'ab' |
| | 或者,只匹配其中一個表達式 ,如果|沒有被包括在()中,則它的范圍是整個正則表達式 | 'abcd' >>>'abc|acd' >>>結果為:'abc' |
( … ) | 被括起來的表達式作為一個分組. findall 在有組的情況下只顯示組的內容 | 'a123d' >>>'a(123)d' >>>結果為:'123' |
(?#...) | 注釋,忽略括號內的內容 特殊構建不作為分組 | 'abc123' >>>'abc(?#fasd)123' >>>結果為:'abc123' |
(?= … ) | 表達式’…’之前的字符串,特殊構建不作為分組 | 在字符串’ pythonretest ’中 (?=test) 會匹配’ pythonre ’ |
(?!...) | 后面不跟表達式’…’的字符串,特殊構建不作為分組 | 如果’ pythonre ’后面不是字符串’ test ’,那么 (?!test) 會匹配’ pythonre ’ |
(?<= … ) | 跟在表達式’…’后面的字符串符合括號之后的正則表達式,特殊構建不作為分組 | 正則表達式’ (?<=abc)def ’會在’ abcdef ’中匹配’ def ’ |
(?:) | 取消優先打印分組的內容 | 'abc' >>>'(?:a)(b)' >>>結果為'[b]' |
?P<> | 指定Key | 'abc' >>>'(?P
|
表 2. 正則表達式特殊序列
特殊表達式序列 | 說明 |
---|---|
\A | 只在字符串開頭進行匹配。 |
\b | 匹配位於開頭或者結尾的空字符串 |
\B | 匹配不位於開頭或者結尾的空字符串 |
\d | 匹配任意十進制數,相當於 [0-9] |
\D | 匹配任意非數字字符,相當於 [^0-9] |
\s | 匹配任意空白字符,相當於 [ \t\n\r\f\v] |
\S | 匹配任意非空白字符,相當於 [^ \t\n\r\f\v] |
\w | 匹配任意數字和字母,相當於 [a-zA-Z0-9_] |
\W | 匹配任意非數字和字母的字符,相當於 [^a-zA-Z0-9_] |
\Z | 只在字符串結尾進行匹配 |
4. 正則匹配re模塊
Python 自1.5版本起增加了re 模塊,它提供 Perl 風格的正則表達式模式。
re 模塊使 Python 語言擁有全部的正則表達式功能。
compile 函數根據一個模式字符串和可選的標志參數生成一個正則表達式對象。該對象擁有一系列方法用於正則表達式匹配和替換。
4.1、 re.findall(pattern, string[, flags]):
方法能夠以列表的形式返回能匹配的子串。先看簡單的例子:
import re
a = 'one1two2three3four4'
ret = re.findall(r'(\d+)', a)
print(ret)
['1', '2', '3', '4']
從上面的例子可以看出返回的值是個列表,並且返回字符串中所有匹配的字符串。
上述寫法也可以寫成如下方式,效果一樣:
import re
p = re.compile(r"(\d+)")
a = 'one1two2three3four4'
res = p.findall(a)
print(res)
['1', '2', '3', '4']
a = 'hello alex alex adn acd'
n = re.findall('(a)(\w+)',a)
print(n) #從左到右,從外到內
#[('a', 'lex'), ('a', 'lex'), ('a', 'dn'), ('a', 'cd')]
4.2 re.match函數
re.match 嘗試從字符串的起始位置匹配一個模式,如果不是起始位置匹配成功的話,match()就返回none。
函數語法:
re.match(pattern, string, flags=0)
函數參數說明:
參數 | 描述 |
---|---|
pattern | 匹配的正則表達式 |
string | 要匹配的字符串。 |
flags | 標志位,用於控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等。參見:正則表達式修飾符 - 可選標志 |
匹配成功re.match方法返回一個匹配的對象,否則返回None。
我們可以使用group(num) 或 groups() 匹配對象函數來獲取匹配表達式。
匹配對象方法 | 描述 |
---|---|
group(num=0) | 匹配的整個表達式的字符串,group() 可以一次輸入多個組號,在這種情況下它將返回一個包含那些組所對應值的元組。 |
groups() | 返回一個包含所有小組字符串的元組,從 1 到 所含的小組號。 |
實例
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/7/03 上午9:48'
import re
line = "liu dehua was older than you"
matchObj = re.match(r'^liu (.*) was (.*?) .*', line, re.M | re.I)
if matchObj:
print("matchObj.group() : ", matchObj.group())
print("matchObj.group(1) : ", matchObj.group(1))
print("matchObj.group(2) : ", matchObj.group(2))
else:
print("No match!!")
執行結果如下:
matchObj.group() : liu dehua was older than you
matchObj.group(1) : dehua
matchObj.group(2) : older
4.3 re.search 函數
re.search 掃描整個字符串並返回第一個成功的匹配。
函數語法:
re.search(pattern, string, flags=0)
函數參數說明:
參數 | 描述 |
---|---|
pattern | 匹配的正則表達式 |
string | 要匹配的字符串。 |
flags | 標志位,用於控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等。 |
匹配成功re.search方法返回一個匹配的對象,否則返回None。
我們可以使用group(num) 或 groups() 匹配對象函數來獲取匹配表達式。
匹配對象方法 | 描述 |
---|---|
group(num=0) | 匹配的整個表達式的字符串,group() 可以一次輸入多個組號,在這種情況下它將返回一個包含那些組所對應值的元組。 |
groups() | 返回一個包含所有小組字符串的元組,從 1 到 所含的小組號。 |
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/7/03 上午9:48'
import re
line = "liu dehua was older than you"
matchObj = re.search(r'^liu (.*) was (.*?) .*', line, re.M | re.I)
if matchObj:
print("matchObj.group() : ", matchObj.group())
print("matchObj.group(1) : ", matchObj.group(1))
print("matchObj.group(2) : ", matchObj.group(2))
else:
print("No match!!")
運行效果:
matchObj.group() : liu dehua was older than you
matchObj.group(1) : dehua
matchObj.group(2) : older
re.match與re.search的區別
re.match是從字符串的起始點開始進行匹配,如果字符串開始不符合正則表達式,則匹配失敗,函數返回None;
re.search匹配整個字符串,直到找到一個匹配。
import re
ret_match = re.match("c", "abcde") # 從字符串開頭匹配,匹配到返回match的對象,匹配不到返回None
if (ret_match):
print("ret_match:" + ret_match.group())
else:
print("ret_match:None")
ret_search = re.search("c", "abcde") # 掃描整個字符串返回第一個匹配到的元素並結束,匹配不到返回None
if (ret_search):
print("ret_search:" + ret_search.group())
返回的結果:
ret_match:None
ret_search:c
group()方法可以指定組號,如果組號不存在則返回indexError異常看如下例子:
import re
a = "123abc456"
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(0) # 123abc456,返回整體默認返回group(0)
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(1) # 123
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(2) # abc
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(3) # 456
總結:
match: 從字符串的第一個字符匹配查找
search: 從整個串找符合條件的。
4.4 re.sub和re.subn 函數
兩種方法都是用來替換匹配成功的字串,值得一提的時,sub不僅僅可以是字符串,也可以是函數。subn函數返回元組,看下面例子:
import re
# sub
ret_sub = re.sub(r'(one|two|three)', 'ok', 'one word two words three words')
print(ret_sub)
# subn
import re
ret_subn = re.subn(r'(one|two|three)', 'ok',
'one word two words three words')
print(ret_subn)
# ok word ok words ok words
# ('ok word ok words ok words', 3) 3,表示替換的次數
4.5 re.split 函數
re.split(pattern, string, maxsplit=0)
通過正則表達式將字符串分離。如果用括號將正則表達式括起來,那么匹配的字符串也會被列入到list中返回。maxsplit是分離的次數,maxsplit=1分離一次,默認為0,不限制次數。
import re
ret = re.split('\d+',
'one1two2three3four4')
print(ret)
####output####
# 匹配到1的時候結果為'one'和'two2three3four4',匹配到2的時候結果為'one',
# 'two'和'three3four4', 所以結果為:
#['one', 'two', 'three', 'four', '']
4.6 python正則匹配貪婪和非貪婪模式
正則表達式通常用於在文本中查找匹配的字符串。Python里數量詞默認是貪婪的(在少數語言里也可能是默認非貪婪),總是嘗試匹配盡可能多的字符;非貪婪則相反,總是嘗試匹配盡可能少的字符。在"*","?","+","{m,n}"后面加上?,使貪婪變成非貪婪。
>>> s="This is a number 234-235-22-423"
>>> r=re.match(".+(\d+-\d+-\d+-\d+)",s) #貪婪
>>> r.group(1)
'4-235-22-423'
>>> r=re.match(".+?(\d+-\d+-\d+-\d+)",s) #非貪婪
>>> r.group(1)
'234-235-22-423'
正則表達式模式中使用到通配字,那它在從左到右的順序求值時,會盡量“抓取”滿足匹配最長字符串,在我們上面的例子里面,“.+”會從字符 串的啟始處抓取滿足模式的最長字符,其中包括我們想得到的第一個整型字段的中的大部分,“\d+”只需一位字符就可以匹配,所以它匹配了數字“4”,而“.+”則匹配了從字符串起始到這個第一位數字4之前的所有字符。
解決方式:非貪婪操作符“?”,這個操作符可以用在"*","+","?"的后面,要求正則匹配的越少越好。
#貪婪
>>> re.match(r"aa(\d+)","aa2343ddd").group(1)
'2343'
#非貪婪
>>> re.match(r"aa(\d+?)","aa2343ddd").group(1)
'2'
>>> re.match(r"aa(\d+)ddd","aa2343ddd").group(1)
'2343'
>>> re.match(r"aa(\d+?)ddd","aa2343ddd").group(1)
'2343'
>>>
#貪婪
ret_greed= re.findall(r'a(\d+)','a23b')
print(ret_greed)
['23']
#非貪婪
ret_no_greed= re.findall(r'a(\d+?)','a23b')
print(ret_no_greed)
['2']
如下例子可以看看加入了?和不加?的區別
str = "i love 2,45 china v5 , 6666, yes"
res = re.findall(r".*?(.*)yes$", str)
print(res)
如何匹配一個數字串,滿足
開頭的一個數是6,中間五個是整數,后一位是1,2或者4,代碼如下:
p = re.compile(r"^(6\d{5}[1,2,4])")
print(p.match("6256432"))
5. 正則表達式案例分析
(1)校驗密碼強度
密碼的強度必須是包含大小寫字母和數字的組合,不能使用特殊字符,長度在8-10之間。
^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
(2)校驗中文
字符串僅能是中文。
^[\u4e00-\u9fa5]+$
(3) 由數字、26個英文字母或下划線組成的字符串
^\\w+$
(4)校驗E-Mail 地址
同密碼一樣,下面是E-mail地址合規性的正則檢查語句。
^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$
(5)校驗身份證號碼
下面是身份證號碼的正則校驗。 18位。
18位:
^([1-9]\d{5}[12]\d{3}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])\d{3}[0-9xX])$
(6)校驗金額
金額校驗,精確到2位小數。
^[0-9]+(.[0-9]{2})?$
(7)校驗手機號
下面是國內 13、15、18開頭的手機號正則表達式。(可根據目前國內收集號擴展前兩位開頭號碼),假設是11位的手機號碼
^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
(^(13\d|14[57]|15[^4\D]|17[13678]|18\d)\d{8}|170[^346\D]\d{7})$
(8)校驗IP-v4地址
^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
(9)檢查URL的前綴
應用開發中很多時候需要區分請求是HTTPS還是HTTP,通過下面的表達式可以取出一個url的前綴然后再邏輯判斷。
if (!s.match(/^[a-zA-Z]+:\\/\\//))
{
s = 'http://' + s;
}
(10)提取URL鏈接
下面的這個表達式可以篩選出一段文本中的URL。
^(f|ht){1}(tp|tps):\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w- ./?%&=]*)?
(11) 提取網頁圖片
假若你想提取網頁中所有圖片信息,可以利用下 面的表達式。
\\< *[img][^\\\\>]*[src] *= *[\\"\\']{0,1}([^\\"\\'\\ >]*)
(12)提取頁面超鏈接
提取html中的超鏈接。
(<a\\s*(?!.*\\brel=)[^>]*)(href="https?:\\/\\/)((?!(?:(?:www\\.)?'.implode('|(?:www\\.)?', $follow_list).'))[^"]+)"((?!.*\\brel=)[^>]*)(?:[^>]*)>
綜合案例:
輸入一個字符串,判斷是否是身份證號碼(校驗身份證號碼)
輸入字符串:422101198808100412 (18位)
校驗身份證號碼的合法性?
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/7/03 下午3:24'
import re
def check_card_isvalid(card_str):
p = re.compile(r"^([1-9]\d{5}[12]\d{3}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])\d{3}[0-9xX])$")
return p.match(card_str)
card_str = "422101198808100412"
res = check_card_isvalid(card_str)
print(res)
案例擴展:
采用正則表達式匹配日志文件內容,獲取每個日志部分
[DEBUG][2018-09-10 09:10:34][192.169.11.34][function1][this is our log file, has error]
代碼如下:
'''
正則表達式匹配
'''
regstr = "[DEBUG][2018-09-10 09:10:34][192.169.11.34][function1]" \
"[this is our log file, has error]"
p = re.compile(r"\[(?P<log_level>.*)\]\[(?P<time_local>.*)\]"
r"\[(?P<ip_address>\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\]")
res = p.findall(regstr)
print(res)
print(dir(res))
參考書籍:
《精通正則表達式 (第三版).pdf》