python 正則表示式詳解
正則表示式是一個很強大的字串處理工具,幾乎任何關於字串的操作都可以使用正則表示式來完成,作為一個爬蟲工作者,每天和字串打交道,正則表示式更是不可或缺的技能,正則表示式的在不同的語言中使用方式可能不一樣,不過只要學會了任意一門語言的正則表示式用法,其他語言中大部分也只是換了個函式的名稱而已,本質都是一樣的。下面,我來介紹一下python中的正則表示式是怎麼使用的。
首先,python中的正則表示式大致分為以下幾部分:
- 元字元
- 模式
- 函式
- re 內建物件用法
- 分組用法
- 環視用法
所有關於正則表示式的操作都使用 python 標準庫中的 re 模組。
一、元字元 (參見 python 模組 re 文件)
-
- . 匹配任意字元(不包括換行符)
- ^ 匹配開始位置,多行模式下匹配每一行的開始
- $ 匹配結束位置,多行模式下匹配每一行的結束
- * 匹配前一個元字元0到多次
- + 匹配前一個元字元1到多次
- ? 匹配前一個元字元0到1次
- {m,n} 匹配前一個元字元m到n次
- \\ 轉義字元,跟在其後的字元將失去作為特殊元字元的含義,例如\\.只能匹配.,不能再匹配任意字元
- [] 字符集,一個字元的集合,可匹配其中任意一個字元
- | 邏輯表示式 或 ,比如 a|b 代表可匹配 a 或者 b
- (...) 分組,預設為捕獲,即被分組的內容可以被單獨取出,預設每個分組有個索引,從 1 開始,按照"("的順序決定索引值
- (?iLmsux) 分組中可以設定模式,iLmsux之中的每個字元代表一個模式,用法參見 模式 I
- (?:...) 分組的不捕獲模式,計算索引時會跳過這個分組
- (?P<name>...) 分組的命名模式,取此分組中的內容時可以使用索引也可以使用name
- (?P=name) 分組的引用模式,可在同一個正則表示式用引用前面命名過的正則
- (?#...) 註釋,不影響正則表示式其它部分,用法參見 模式 I
- (?=...)順序肯定環視,表示所在位置右側能夠匹配括號內正則
- (?!...)順序否定環視,表示所在位置右側不能匹配括號內正則
- (?<=...)逆序肯定環視,表示所在位置左側能夠匹配括號內正則
- (?<!...)逆序否定環視,表示所在位置左側不能匹配括號內正則
- (?(id/name)yes|no) 若前面指定id或name的分割槽匹配成功則執行yes處的正則,否則執行no處的正則
- \number 匹配和前面索引為number的分組捕獲到的內容一樣的字串
- \A 匹配字串開始位置,忽略多行模式
- \Z 匹配字串結束位置,忽略多行模式
- \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_]
二、模式
-
- I IGNORECASE, 忽略大小寫的匹配模式, 樣例如下
s = 'hello World!' regex = re.compile("hello world!", re.I) print regex.match(s).group() #output> 'Hello World!' #在正則表示式中指定模式以及註釋 regex = re.compile("(?#註釋)(?i)hello world!") print regex.match(s).group() #output> 'Hello World!'
- L LOCALE, 字符集本地化。這個功能是為了支援多語言版本的字符集使用環境的,比如在轉義符\w,在英文環境下,它代表[a-zA-Z0-9_],即所以英文字元和數字。如果在一個法語環境下使用,預設設定下,不能匹配"é" 或 "ç"。加上這L選項和就可以匹配了。不過這個對於中文環境似乎沒有什麼用,它仍然不能匹配中文字元。
- M MULTILINE,多行模式, 改變 ^ 和 $ 的行為
s = '''first line second line third line''' # ^ regex_start = re.compile("^\w+") print regex_start.findall(s) # output> ['first'] regex_start_m = re.compile("^\w+", re.M) print regex_start_m.findall(s) # output> ['first', 'second', 'third'] #$ regex_end = re.compile("\w+$") print regex_end.findall(s) # output> ['line'] regex_end_m = re.compile("\w+$", re.M) print regex_end_m.findall(s) # output> ['line', 'line', 'line']
- S DOTALL,此模式下 '.' 的匹配不受限制,可匹配任何字元,包括換行符
s = '''first line second line third line''' # regex = re.compile(".+") print regex.findall(s) # output> ['first line', 'second line', 'third line'] # re.S regex_dotall = re.compile(".+", re.S) print regex_dotall.findall(s) # output> ['first line\nsecond line\nthird line']
- X VERBOSE,冗餘模式, 此模式忽略正則表示式中的空白和#號的註釋,例如寫一個匹配郵箱的正則表示式
email_regex = re.compile("[\w+\.]+@[a-zA-Z\d]+\.(com|cn)") email_regex = re.compile("""[\w+\.]+ # 匹配@符前的部分 @ # @符 [a-zA-Z\d]+ # 郵箱類別 \.(com|cn) # 郵箱字尾 """, re.X)
- I IGNORECASE, 忽略大小寫的匹配模式, 樣例如下
-
- U UNICODE,使用 \w, \W, \b, \B 這些元字元時將按照 UNICODE 定義的屬性.
正則表示式的模式是可以同時使用多個的,在 python 裡面使用按位或運算子 | 同時新增多個模式
如 re.compile('', re.I|re.M|re.S)
每個模式在 re 模組中其實就是不同的數字
print re.I # output> 2 print re.L # output> 4 print re.M # output> 8 print re.S # output> 16 print re.X # output> 64 print re.U # output> 32
三、函式 (參見 python 模組 re 文件)
python 的 re 模組提供了很多方便的函式使你可以使用正則表示式來操作字串,每種函式都有它自己的特性和使用場景,熟悉之後對你的工作會有很大幫助
-
- compile(pattern, flags=0)
給定一個正則表示式 pattern,指定使用的模式 flags 預設為0 即不使用任何模式,然後會返回一個 SRE_Pattern (參見第四小節 re 內建物件用法) 物件
regex = re.compile(".+") print regex # output> <_sre.SRE_Pattern object at 0x00000000026BB0B8>
這個物件可以呼叫其他函式來完成匹配,一般來說推薦使用 compile 函式預編譯出一個正則模式之後再去使用,這樣在後面的程式碼中可以很方便的複用它,當然大部分函式也可以不用 compile 直接使用,具體見 findall 函式
s = '''first line second line third line''' # regex = re.compile(".+") # 呼叫 findall 函式 print regex.findall(s) # output> ['first line', 'second line', 'third line'] # 呼叫 search 函式 print regex.search(s).group() # output> first lin
-
- escape(pattern)
轉義 如果你需要操作的文字中含有正則的元字元,你在寫正則的時候需要將元字元加上反斜扛 \ 去匹配自身, 而當這樣的字元很多時,寫出來的正則表示式就看起來很亂而且寫起來也挺麻煩的,這個時候你可以使用這個函式,用法如下
s = ".+\d123" # regex_str = re.escape(".+\d123") # 檢視轉義後的字元 print regex_str # output> \.\+\\d123 # 檢視匹配到的結果 for g in re.findall(regex_str, s): print g # output> .+\d123
-
- findall(pattern, string, flags=0)
引數 pattern 為正則表示式, string 為待操作字串, flags 為所用模式,函式作用為在待操作字串中尋找所有匹配正則表示式的字串,返回一個列表,如果沒有匹配到任何子串,返回一個空列表。
s = '''first line second line third line''' # compile 預編譯後使用 findall regex = re.compile("\w+") print regex.findall(s) # output> ['first', 'line', 'second', 'line', 'third', 'line'] # 不使用 compile 直接使用 findall print re.findall("\w+", s) # output> ['first', 'line', 'second', 'line', 'third', 'line']
-
- finditer(pattern, string, flags=0)
引數和作用與 findall 一樣,不同之處在於 findall 返回一個列表, finditer 返回一個迭代器(參見http://www.cnblogs.com/huxi/archive/2011/07/01/2095931.html), 而且迭代器每次返回的值並不是字串,而是一個SRE_Match(參見 第四小節 re 內建物件用法)物件,這個物件的具體用法見 match 函式。
s = '''first line second line third line''' regex = re.compile("\w+") print regex.finditer(s) # output> <callable-iterator object at 0x0000000001DF3B38> for i in regex.finditer(s): print i # output> <_sre.SRE_Match object at 0x0000000002B7A920> # <_sre.SRE_Match object at 0x0000000002B7A8B8> # <_sre.SRE_Match object at 0x0000000002B7A920> # <_sre.SRE_Match object at 0x0000000002B7A8B8> # <_sre.SRE_Match object at 0x0000000002B7A920> # <_sre.SRE_Match object at 0x0000000002B7A8B8>
-
- match(pattern, string, flags=0)
使用指定正則去待操作字串中尋找可以匹配的子串, 返回匹配上的第一個字串,並且不再繼續找,需要注意的是 match 函式是從字串開始處開始查詢的,如果開始處不匹配,則不再繼續尋找,返回值為 一個 SRE_Match(參見第四小節 re 內建物件用法)物件,找不到時返回 None
s = '''first line second line third line''' # compile regex = re.compile("\w+") m = regex.match(s) print m # output> <_sre.SRE_Match object at 0x0000000002BCA8B8> print m.group() # output> first # s 的開頭是 "f", 但正則中限制了開始為 i 所以找不到 regex = re.compile("^i\w+") print regex.match(s) # output> None
-
- purge()
當你在程式中使用 re 模組,無論是先使用 compile 還是直接使用比如 findall 來使用正則表示式操作文字,re 模組都會將正則表示式先編譯一下, 並且會將編譯過後的正則表示式放到快取中,這樣下次使用同樣的正則表示式的時候就不需要再次編譯, 因為編譯其實是很費時的,這樣可以提升效率,而預設快取的正則表示式的個數是 100, 當你需要頻繁使用少量正則表示式的時候,快取可以提升效率,而使用的正則表示式過多時,快取帶來的優勢就不明顯了 (參考 《python re.compile對效能的影響》http://blog.trytofix.com/article/detail/13/), 這個函式的作用是清除快取中的正則表示式,可能在你需要優化佔用記憶體的時候會用到。
-
- search(pattern, string, flags=0)
函式類似於 match,不同之處在於不限制正則表示式的開始匹配位置
s = '''first line second line third line''' # 需要從開始處匹配 所以匹配不到 print re.match('i\w+', s) # output> None # 沒有限制起始匹配位置 print re.search('i\w+', s) # output> <_sre.SRE_Match object at 0x0000000002C6A920> print re.search('i\w+', s).group() # output> irst
-
- split(pattern, string, maxsplit=0, flags=0)
引數 maxsplit 指定切分次數, 函式使用給定正則表示式尋找切分字串位置,返回包含切分後子串的列表,如果匹配不到,則返回包含原字串的一個列表
s = '''first 111 line second 222 line third 333 line''' # 按照數字切分 print re.split('\d+', s) # output> ['first ', ' line\nsecond ', ' line\nthird ', ' line'] # \.+ 匹配不到 返回包含自身的列表 print re.split('\.+', s, 1) # output> ['first 111 line\nsecond 222 line\nthird 333 line'] # maxsplit 引數 print re.split('\d+', s, 1) # output> ['first ', ' line\nsecond 222 line\nthird 333 line']
-
- sub(pattern, repl, string, count=0, flags=0)
替換函式,將正則表示式 pattern 匹配到的字串替換為 repl 指定的字串, 引數 count 用於指定最大替換次數
s = "the sum of 7 and 9 is [7+9]." # 基本用法 將目標替換為固定字串 print re.sub('\[7\+9\]', '16', s) # output> the sum of 7 and 9 is 16. # 高階用法 1 使用前面匹配的到的內容 \1 代表 pattern 中捕獲到的第一個分組的內容 print re.sub('\[(7)\+(9)\]', r'\2\1', s) # output> the sum of 7 and 9 is 97. # 高階用法 2 使用函式型 repl 引數, 處理匹配到的 SRE_Match 物件 def replacement(m): p_str = m.group() if p_str == '7': return '77' if p_str == '9': return '99' return '' print re.sub('\d', replacement, s) # output> the sum of 77 and 99 is [77+99]. # 高階用法 3 使用函式型 repl 引數, 處理匹配到的 SRE_Match 物件 增加作用域 自動計算 scope = {} example_string_1 = "the sum of 7 and 9 is [7+9]." example_string_2 = "[name = 'Mr.Gumby']Hello,[name]" def replacement(m): code = m.group(1) st = '' try: st = str(eval(code, scope)) except SyntaxError: exec code in scope return st # 解析: code='7+9' # str(eval(code, scope))='16' print re.sub('\[(.+?)\]', replacement, example_string_1) # output> the sum of 7 and 9 is 16.
# 兩次替換 # 解析1: code="name = 'Mr.Gumby'" # eval(code) # raise SyntaxError # exec code in scope # 在名稱空間 scope 中將 "Mr.Gumby" 賦給了變數 name # 解析2: code="name" # eval(name) 返回變數 name 的值 Mr.Gumby print re.sub('\[(.+?)\]', replacement, example_string_2) # output> Hello,Mr.Gumby
-
- subn(pattern, repl, string, count=0, flags=0)
作用與函式 sub 一樣, 唯一不同之處在於返回值為一個元組,第一個值為替換後的字串,第二個值為發生替換的次數
-
- template(pattern, flags=0)
這個吧,咋一看和 compile 差不多,不過不支援 +、?、*、{} 等這樣的元字元,只要是需要有重複功能的元字元,就不支援,查了查資料,貌似沒人知道這個函式到底是幹嘛的...
四、re 內建物件用法
-
- SRE_Pattern 這個物件是一個編譯後的正則表示式,編譯後不僅能夠複用和提升效率,同時也能夠獲得一些其他的關於正則表示式的資訊
屬性:
- flags 編譯時指定的模式
- groupindex 以正則表示式中有別名的組的別名為鍵、以該組對應的編號為值的字典,沒有別名的組不包含在內。
- groups 正則表示式中分組的數量
- pattern 編譯時用的正則表示式
s = 'Hello, Mr.Gumby : 2016/10/26' p = re.compile('''(?: # 構造一個不捕獲分組 用於使用 | (?P<name>\w+\.\w+) # 匹配 Mr.Gumby | # 或 (?P<no>\s+\.\w+) # 一個匹配不到的命名分組 ) .*? # 匹配 : (\d+) # 匹配 2016 ''', re.X) # print p.flags # output> 64 print p.groupindex # output> {'name': 1, 'no': 2} print p.groups # output> 3 print p.pattern # output> (?: # 構造一個不捕獲分組 用於使用 | # (?P<name>\w+\.\w+) # 匹配 Mr.Gumby # | # 或 # (?P<no>\s+\.\w+) # 一個匹配不到的命名分組 # ) # .*? # 匹配 : # (\d+) # 匹配 2016
函式:可使用 findall、finditer、match、search、split、sub、subn 等函式
-
- SRE_Match 這個物件會儲存本次匹配的結果,包含很多關於匹配過程以及匹配結果的資訊
屬性:
- endpos 本次搜尋結束位置索引
- lastgroup本次搜尋匹配到的最後一個分組的別名
- lastindex本次搜尋匹配到的最後一個分組的索引
- pos 本次搜尋開始位置索引
- re 本次搜尋使用的 SRE_Pattern 物件
- regs 列表,元素為元組,包含本次搜尋匹配到的所有分組的起止位置
- string 本次搜尋操作的字串
s = 'Hello, Mr.Gumby : 2016/10/26'
m = re.search(', (?P<name>\w+\.\w+).*?(\d+)', s)
# 本次搜尋的結束位置索引
print m.endpos
# output> 28
# 本次搜尋匹配到的最後一個分組的別名
# 本次匹配最後一個分組沒有別名
print m.lastgroup
# output> None
# 本次搜尋匹配到的最後一個分組的索引
print m.lastindex
# output> 2
# 本次搜尋開始位置索引
print m.pos
# output> 0
# 本次搜尋使用的 SRE_Pattern 物件
print m.re
# output> <_sre.SRE_Pattern object at 0x000000000277E158>
# 列表,元素為元組,包含本次搜尋匹配到的所有分組的起止位置 第一個元組為正則表示式匹配範圍
print m.regs
# output> ((7, 22), (7, 15), (18, 22))
# 本次搜尋操作的字串
print m.string
# output> Hello, Mr.Gumby : 2016/10/26
函式:
- end([group=0]) 返回指定分組的結束位置,預設返回正則表示式所匹配到的最後一個字元的索引
- expand(template) 根據模版返回相應的字串,類似與 sub 函式裡面的 repl, 可使用 \1 或者 \g<name> 來選擇分組
- group([group1, ...]) 根據提供的索引或名字返回響應分組的內容,預設返回 start() 到 end() 之間的字串, 提供多個引數將返回一個元組
- groupdict([default=None]) 返回 返回一個包含所有匹配到的命名分組的字典,沒有命名的分組不包含在內,key 為組名, value 為匹配到的內容,引數 default 為沒有參與本次匹配的命名分組提供預設值
- groups([default=None]) 以元組形式返回每一個分組匹配到的字串,包括沒有參與匹配的分組,其值為 default
- span([group]) 返回指定分組的起止位置組成的元組,預設返回由 start() 和 end() 組成的元組
- start([group]) 返回指定分組的開始位置,預設返回正則表示式所匹配到的第一個字元的索引
s = 'Hello, Mr.Gumby : 2016/10/26' m = re.search('''(?: # 構造一個不捕獲分組 用於使用 | (?P<name>\w+\.\w+) # 匹配 Mr.Gumby | # 或 (?P<no>\s+\.\w+) # 一個匹配不到的命名分組 ) .*? # 匹配 : (\d+) # 匹配 2016 ''', s, re.X) # 返回指定分組的結束位置,預設返回正則表示式所匹配到的最後一個字元的索引 print m.end() # output> 22 # 根據模版返回相應的字串,類似與 sub 函式裡面的 repl, 可使用 \1 或者 \g<name> 來選擇分組 print m.expand("my name is \\1") # output> my name is Mr.Gumby # 根據提供的索引或名字返回響應分組的內容,預設返回 start() 到 end() 之間的字串, 提供多個引數將返回一個元組 print m.group() # output> Mr.Gumby : 2016 print m.group(1,2) # output> ('Mr.Gumby', None) # 返回 返回一個包含所有匹配到的命名分組的字典,沒有命名的分組不包含在內,key 為組名, value 為匹配到的內容,引數 default 為沒有參與本次匹配的命名分組提供預設值 print m.groupdict('default_string') # output> {'name': 'Mr.Gumby', 'no': 'default_string'} # 以元組形式返回每一個分組匹配到的字串,包括沒有參與匹配的分組,其值為 default print m.groups('default_string') # output> ('Mr.Gumby', 'default_string', '2016') # 返回指定分組的起止未知組成的元組,預設返回由 start() 和 end() 組成的元組 print m.span(3) # output> (18, 22) # 返回指定分組的開始位置,預設返回正則表示式所匹配到的第一個字元的索引 print m.start(3) # output> 18
五、分組用法
python 的正則表示式中用小括號 "(" 表示分組,按照每個分組中前半部分出現的順序 "(" 判定分組的索引,索引從 1 開始,每個分組在訪問的時候可以使用索引,也可以使用別名
s = 'Hello, Mr.Gumby : 2016/10/26' p = re.compile("(?P<name>\w+\.\w+).*?(\d+)(?#comment)") m = p.search(s) # 使用別名訪問 print m.group('name') # output> Mr.Gumby # 使用分組訪問 print m.group(2) # output> 2016
有時候可能只是為了把正則表示式分組,而不需要捕獲其中的內容,這時候可以使用非捕獲分組
s = 'Hello, Mr.Gumby : 2016/10/26' p = re.compile(""" (?: # 非捕獲分組標誌 用於使用 | (?P<name>\w+\.\w+) | (\d+/) ) """, re.X) m = p.search(s) # 使用非捕獲分組 # 此分組將不計入 SRE_Pattern 的 分組計數 print p.groups # output> 2 # 不計入 SRE_Match 的分組 print m.groups() # output> ('Mr.Gumby', None)
如果你在寫正則的時候需要在正則裡面重複書寫某個表示式,那麼你可以使用正則的引用分組功能,需要注意的是引用的不是前面分組的正則表示式而是捕獲到的內容,並且引用的分組不算在分組總數中.
s = 'Hello, Mr.Gumby : 2016/2016/26' p = re.compile(""" (?: # 非捕獲分組標誌 用於使用 | (?P<name>\w+\.\w+) | (\d+/) ) .*?(?P<number>\d+)/(?P=number)/ """, re.X) m = p.search(s) # 使用引用分組 # 此分組將不計入 SRE_Pattern 的 分組計數 print p.groups # output> 3 # 不計入 SRE_Match 的分組 print m.groups() # output> ('Mr.Gumby', None, '2016') # 檢視匹配到的字串 print m.group() # output> Mr.Gumby : 2016/2016/
六、環視用法
環視還有其他的名字,例如 界定、斷言、預搜尋等,叫法不一。
環視是一種特殊的正則語法,它匹配的不是字串,而是位置,其實就是使用正則來說明這個位置的左右應該是什麼或者應該不是什麼,然後去尋找這個位置。
環視的語法有四種,見第一小節元字元,基本用法如下。
s = 'Hello, Mr.Gumby : 2016/10/26 Hello,r.Gumby : 2016/10/26' # 不加環視限定 print re.compile("(?P<name>\w+\.\w+)").findall(s) # output> ['Mr.Gumby', 'r.Gumby'] # 環視表示式所在位置 左邊為 "Hello, " print re.compile("(?<=Hello, )(?P<name>\w+\.\w+)").findall(s) # output> ['Mr.Gumby'] # 環視表示式所在位置 左邊不為 "," print re.compile("(?<!,)(?P<name>\w+\.\w+)").findall(s) # output> ['Mr.Gumby'] # 環視表示式所在位置 右邊為 "M" print re.compile("(?=M)(?P<name>\w+\.\w+)").findall(s) # output> ['Mr.Gumby'] # 環視表示式所在位置 右邊不為 r print re.compile("(?!r)(?P<name>\w+\.\w+)").findall(s) # output> ['Mr.Gumby']
from :https://www.cnblogs.com/misswangxing/p/10736310.html