re模塊
正則表達式使用單個字符串來描述、匹配一系列符合某個句法規則的字符串,在文本處理方面功能非常強大,也經常用作爬蟲,來爬取特定內容,Python本身不支持正則,但是通過導入re模塊,Python也能用正則表達式,下面就來講一下python正則表達式的用法。正則表達式默認以單行開始匹配的
一、匹配規則

二、re的相關方法
re.findall()
findall(),可以將匹配到的結果以列表的形式返回,如果匹配不到則返回一個空列表,下面來看一下代碼中的使用
import re
def re_method(): s1 = 'Hello, this is Joey' s2 = 'The first price is $9.90 and the second price is $100' print(re.findall(r'\w+',s1)) print(re.findall(r'\d+\.?\d*',s2)) if __name__ == '__main__': re_method()
re.finditer()
可以將匹配到的結果生成一個迭代器
import re
def re_method4():
# finditer
s2 = 'The first price is $9.90 and the second price is $100'
i = re.finditer(r'\d+\.?\d*',s2)
for m in i:
print(m.group())
if __name__ == '__main__':
re_method4()
re.search()
search是匹配整個字符串直道匹配到一個就返回。
import re
def re_demo():
txt = 'If you puchase more than 100 sets, the price of product A is $9.90.'
m = re.search(r'(\d+).*\$(\d+\.?\d*)',txt)
print(m.groups())
if __name__ == '__main__':
re_demo()
re.match()
match從要匹配的字符串的開頭開始,嘗試匹配,如果字符串開始不符合正則表達式,則匹配失敗,函數返回None,匹配成功的話用group取出匹配的結果
import re
def re_method():
# search vs match
s = 'abcdc'
print(re.search(r'c',s)) #search是從開頭到結尾的匹配到第一個匹配的
print(re.search(r'^c', s))
print(re.match(r'c',s)) #match是開頭開始匹配
print(re.match(r'.*c', s))
def re_match_object():
# match對象
s1 = 'Joey Huang'
m = re.match(r'(\w+) (\w+)',s1)
print(m.group(0,1,2))
print(m.groups())
m1 = re.match(r'\w+ (\w+)', s1)
print(m1.group(1))
print(m1.groups())
if __name__ == '__main__':
re_method()
re_match_object()
re.split()
split能夠將匹配的子串分割后返回列表
import re
def re_method1():
# split
s = 'This is Joey Huang'
print(re.split(r'\W', s))
if __name__ == '__main__':
re.method1()
re.sub()、re.subn()
sub能將匹配到的字段用另一個字符串替換返回替換后的字符串,subn還返回替換的次數
import re
def re_method2():
# sub
s2 = 'The first price is $9.90 and the second price is $100'
print(re.sub(r'\d+\.?\d*','<number>',s2,2)) # 還能指定替換的次數
def re_method3():
# subn
s2 = 'The first price is $9.90 and the second price is $100'
print(re.subn(r'\d+\.?\d*','<price>',s2))
if __name__ == '__main__':
re_method2()
re_method3()
三、re的flags標識位
re.DOTALL
# 正則表達式默認以單行開始匹配的
import re
def re_pattern_syntax():
# .表示任意單一字符
# *表示前一個字符出現>=0次
# re.DOTALL就可以匹配換行符\n,默認是以行來匹配的
print(re.match(r'.*', 'abc\nedf').group())
print('*' * 80)
print(re.match(r'.*', 'abc\nedf',re.DOTALL).group())
if __name__ == '__main__':
re_pattern_syntax()
re.MULTILINE
# 正則表達式默認以單行開始匹配的
import re
def re_pattern_syntax1():
# ^表示字符串開頭(單行)
# re.MULTILINE多行匹配字符串開頭
print(re.findall(r'^abc', 'abc\nedf'))
print('*' * 80)
print(re.findall(r'^abc', 'abc\nabc',re.MULTILINE))
def re_pattern_syntax2():
# $表示字符串結尾
# re.MULTILINE表示行的結束
print(re.findall(r'abc\d$', 'abc1\nabc2'))
print('*' * 80)
print(re.findall(r'abc\d$', 'abc1\nabc2',re.MULTILINE))
if __name__ == '__main__':
re_pattern_syntax1()
re_pattern_syntax2()
?非貪婪模式
import re
def re_pattern_syntax4():
# greedy貪婪/non-greedy非貪婪,默認的是貪婪的匹配
s = '<H1>title</H1>'
print(re.match(r'<.+>', s).group()) #貪婪模式會匹配盡量多的匹配
print(re.match(r'<.+?>', s).group()) #非貪婪模式匹配盡量少的匹配
print(re.match(r'<(.+)>', s).group(1))
print(re.match(r'<(.+?)>', s).group(1))
def re_pattern_syntax5():
# {m}/{m,}/{m,n}
print(re.match(r'ab{2,4}', 'abbbbbbb').group()) #貪婪模式盡量匹配多
print(re.match(r'ab{2,4}?', 'abbbbbbb').group()) #非貪婪模式盡量匹配少
print('*' * 80)
if __name__ == '__main__':
re_pattern_syntax4()
re_pattern_syntax5()
re.I/re.IGNORECASE
import re
def re_pattern_flags():
# re.I/re.IGNORECASE
print(re.match(r'(Name)\s*:\s*(\w+)','NAME : Joey',re.IGNORECASE).groups())
print('*' * 80)
if __name__ == '__main__':
re_pattern_syntax_meta_char()
re.VERBOSE
import re
def re_pattern_flags1():
# re.VERBOSE此標識位可以添加注釋/re.compile
s = 'the number is 20.5'
r = re.compile(r'''
\d+ # 整數部分
\.? # 小數點,可能包含也可能不包含
\d* # 小數部分,可選
''',re.VERBOSE)
print(re.search(r,s).group())
print(r.search(s).group())
print('*' * 80)
if __name__ == '__main__':
re_pattern_syntax_meta_char1()
四、原生字符串、編譯、分組
1、原生字符串
細心的人會發現,我每一次在寫匹配規則的話,都在前面加了一個r,為什么要這樣寫,下面從代碼上來說明,
import re
#“\b”在ASCII 字符中代表退格鍵,\b”在正則表達式中代表“匹配一個單詞邊界”
print(re.findall("\bblow","jason blow cat")) #這里\b代表退格鍵,所以沒有匹配到
print(re.findall("\\bblow","jason blow cat")) #用\轉義后這里就匹配到了 ['blow']
print(re.findall(r"\bblow","jason blow cat")) #用原生字符串后就不需要轉義了 ['blow']
你可能注意到我們在正則表達式里使用“\d”,沒用原始字符串,也沒出現什么問題。那是因為ASCII 里沒有對應的特殊字符,所以正則表達式編譯器能夠知道你指的是一個十進制數字。但是我們寫代碼本着嚴謹簡單的原理,最好是都寫成原生字符串的格式。
2、編譯
如果一個匹配規則,我們要使用多次,我們就可以先將其編譯,以后就不用每次都在去寫匹配規則,下面來看一下用法
import re
def re_pattern_flags1():
# re.VERBOSE此標識位可以添加注釋/re.compile
s = 'the number is 20.5'
r = re.compile(r'''
\d+ # 整數部分
\.? # 小數點,可能包含也可能不包含
\d* # 小數部分,可選
''',re.VERBOSE)
print(re.search(r,s).group())
print(r.search(s).group())
print('*' * 80)
if __name__ == '__main__':
re_pattern_syntax_meta_char1()
3、分組
除了簡單地判斷是否匹配之外,正則表達式還有提取子串的強大功能。用()表示的就是要提取的分組,可以有多個組,分組的用法很多,
記住正則分組: 去已經匹配到的數據中提取數據
re.match()有無分組比較
# 無分組
r = re.match("h\w+", origin)
print(r.group()) # 獲取匹配到的所有結果
print(r.groups()) # 獲取模型中匹配到的分組結果
print(r.groupdict()) # 獲取模型中匹配到的分組結果
# 有分組
# 為何要有分組?提取匹配成功的指定內容(先匹配成功全部正則,再匹配成功的局部內容提取出來)
r = re.match("h(\w+).*(?P<name>\d)$", origin)
print(r.group()) # 獲取匹配到的所有結果
print(r.groups()) # 獲取模型中匹配到的分組結果
print(r.groupdict()) # 獲取模型中匹配到的分組中所有執行了key的組
re.search()有無分組比較
# 無分組
r = re.search("a\w+", origin)
print(r.group()) # 獲取匹配到的所有結果
print(r.groups()) # 獲取模型中匹配到的分組結果
print(r.groupdict()) # 獲取模型中匹配到的分組結果
# 有分組
r = re.search("a(\w+).*(?P<name>\d)$", origin)
print(r.group()) # 獲取匹配到的所有結果
print(r.groups()) # 獲取模型中匹配到的分組結果
print(r.groupdict()) # 獲取模型中匹配到的分組中所有執行了key的組
re.findall()有無分組比較
# 無分組
r = re.findall("a\w+",origin)
print(r)
# 有分組
origin = "hello alex bcd abcd lge acd 19"
r = re.findall("a((\w*)c)(d)", origin)
print(r)
re.split()有無分組比較
# 無分組
origin = "hello alex bcd alex lge alex acd 19"
r = re.split("alex", origin, 1)
print(r)
# 有分組
origin = "hello alex bcd alex lge alex acd 19"
r1 = re.split("(alex)", origin, 1)
print(r1)
r2 = re.split("(al(ex))", origin, 1)
print(r2)
再來一些例子:
import re
print(re.findall(r'(\d+)-([a-z])','34324-dfsdfs777-hhh')) # [('34324', 'd'), ('777', 'h')]
print(re.search(r'(\d+)-([a-z])','34324-dfsdfs777-hhh').group(0)) # 34324-d 返回整體
print(re.search(r'(\d+)-([a-z])','34324-dfsdfs777-hhh').group(1)) # 34324 獲取第一個組
print(re.search(r'(\d+)-([a-z])','34324-dfsdfs777-hhh').group(2)) # d 獲取第二個組
print(re.search(r'(\d+)-([a-z])','34324-dfsdfs777-hhh').group(3)) # IndexError: no such group
print(re.search(r"(jason)kk\1","xjasonkkjason").group()) #\1表示應用編號為1的組 jasonkkjason
print(re.search(r'(\d)gg\1','2j333gg3jjj8').group()) # 3gg3 \1表示使用第一個組\d
# 下面的返回None 為什么是空?而匹配不到3gg7,因為\1的不僅表示第一組,而且匹配到的內容也要和第一組匹配到的內容相同,第一組匹配到3,第二組匹配到7 不相同所以返回空
print(re.search(r'(\d)gg\1','2j333gg7jjj8'))
print(re.search(r'(?P<first>\d)abc(?P=first)','1abc1')) # 1abc1 聲明一個組名,使用祖名引用一個組
r=re.match('(?P<n1>h)(?P<n2>\w+)','hello,hi,help') # 組名的另外一種用法
print(r.group()) # hello 返回匹配到的值
print(r.groups()) # ('h', 'ello')返回匹配到的分組
print(r.groupdict()) # {'n2': 'ello', 'n1': 'h'} 返回分組的結果,並且和相應的組名組成一個字典
# 分組是從已經匹配到的里面去取值
origin ="hello alex,acd,alex"
print(re.findall(r'(a)(\w+)(x)',origin)) # [('a', 'le', 'x'), ('a', 'le', 'x')]
print(re.findall(r'a\w+',origin)) # ['alex', 'acd', 'alex']
print(re.findall(r'a(\w+)',origin)) # ['lex', 'cd', 'lex']
print(re.findall(r'(a\w+)',origin)) # ['alex', 'acd', 'alex']
print(re.findall(r'(a)(\w+(e))(x)',origin)) # [('a', 'le', 'e', 'x'), ('a', 'le', 'e', 'x')]
r=re.finditer(r'(a)(\w+(e))(?P<name>x)',origin)
for i in r :
print(i,i.group(),i.groupdict())
'''
[('a', 'le', 'e', 'x'), ('a', 'le', 'e', 'x')]
<_sre.SRE_Match object; span=(6, 10), match='alex'> alex {'name': 'x'}
<_sre.SRE_Match object; span=(15, 19), match='alex'> alex {'name': 'x'}
'''
print(re.findall('(\w)*','alex')) # 匹配到了alex、但是4次只取最后一次即 x 真實括號只有1個
print(re.findall(r'(\w)(\w)(\w)(\w)','alex')) # [('a', 'l', 'e', 'x')] 括號出現了4次,所以4個值都取到了
origin='hello alex sss hhh kkk'
print(re.split(r'a(\w+)',origin)) # ['hello ', 'lex', ' sss hhh kkk']
print(re.split(r'a\w+',origin)) # ['hello ', ' sss hhh kkk']
練習
檢測一個IP地址,比如說192.168.1.1,下面看下代碼怎么實現的
c=re.compile(r'((1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.){3}(1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)')
print(c.search('245.255.256.25asdsa10.11.244.10').group()) # 10.11.244.10 245.255.256.25不符合要求所以就沒有匹配出來
這里來解釋下上面的匹配規則,先看 (1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.),其中1\d\d表示匹配100-199的數字 | 代表或的意思,2[0-4]\d代表匹配100-249,25[0-5]代表匹配250-255,[1-9]\d|\d)代表匹配10-99和0-9,\.代表匹配一個點,{3}代表將前面的分組匹配3次,后面的一部分類似就不說明了。要匹配一個ip重要的是先理解ip每個字段的形式,然后再來寫匹配規則。
常用正則表達式:
IP:
^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
手機號:
^1[3|4|5|8][0-9]\d{8}$
郵箱:
[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+
