正則表達式是一個很強大的字符串處理工具,幾乎任何關於字符串的操做均可以使用正則表達式來完成,做爲一個爬蟲工做者,天天和字符串打交道,正則表達式更是不可或缺的技能,正則表達式的在不一樣的語言中使用方式可能不同,不過只要學會了任意一門語言的正則表達式用法,其餘語言中大部分也只是換了個函數的名稱而已,本質都是同樣的。下面,我來介紹一下python中的正則表達式是怎麼使用的。html
首先,python中的正則表達式大體分爲如下幾部分:python
全部關於正則表達式的操做都使用 python 標準庫中的 re 模塊。正則表達式
1、元字符 (參見 python 模塊 re 文檔)緩存
2、模式函數
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!'
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 = '''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']
email_regex = re.compile("[\w+\.]+@[a-zA-Z\d]+\.(com|cn)") email_regex = re.compile("""[\w+\.]+ # 匹配@符前的部分 @ # @符 [a-zA-Z\d]+ # 郵箱類別 \.(com|cn) # 郵箱後綴 """, re.X)
正則表達式的模式是能夠同時使用多個的,在 python 裏面使用按位或運算符 | 同時添加多個模式工具
如 re.compile('', re.I|re.M|re.S)post
每一個模式在 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 模塊提供了不少方便的函數使你可使用正則表達式來操做字符串,每種函數都有它本身的特性和使用場景,熟悉以後對你的工做會有很大幫助優化
給定一個正則表達式 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
轉義 若是你須要操做的文本中含有正則的元字符,你在寫正則的時候須要將元字符加上反斜扛 \ 去匹配自身, 而當這樣的字符不少時,寫出來的正則表達式就看起來很亂並且寫起來也挺麻煩的,這個時候你可使用這個函數,用法以下
s = ".+\d123"
# regex_str = re.escape(".+\d123") # 查看轉義後的字符
print regex_str # output> \.\+\\d123
# 查看匹配到的結果
for g in re.findall(regex_str, s): print g # output> .+\d123
參數 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']
參數和做用與 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 函數是從字符串開始處開始查找的,若是開始處不匹配,則再也不繼續尋找,返回值爲 一個 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
當你在程序中使用 re 模塊,不管是先使用 compile 仍是直接使用好比 findall 來使用正則表達式操做文本,re 模塊都會將正則表達式先編譯一下, 而且會將編譯事後的正則表達式放到緩存中,這樣下次使用一樣的正則表達式的時候就不須要再次編譯, 由於編譯實際上是很費時的,這樣能夠提高效率,而默認緩存的正則表達式的個數是 100, 當你須要頻繁使用少許正則表達式的時候,緩存能夠提高效率,而使用的正則表達式過多時,緩存帶來的優點就不明顯了 (參考 《python re.compile對性能的影響》http://blog.trytofix.com/article/detail/13/), 這個函數的做用是清除緩存中的正則表達式,可能在你須要優化佔用內存的時候會用到。
函數相似於 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
參數 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']
替換函數,將正則表達式 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
做用與函數 sub 同樣, 惟一不一樣之處在於返回值爲一個元組,第一個值爲替換後的字符串,第二個值爲發生替換的次數
這個吧,咋一看和 compile 差很少,不過不支持 +、?、*、{} 等這樣的元字符,只要是須要有重複功能的元字符,就不支持,查了查資料,貌似沒人知道這個函數究竟是幹嗎的...
4、re 內置對象用法
屬性:
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 等函數
屬性:
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
函數:
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
5、分組用法
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/
6、環視用法
環視還有其餘的名字,例如 界定、斷言、預搜索等,叫法不一。
環視是一種特殊的正則語法,它匹配的不是字符串,而是 位置,其實就是使用正則來講明這個位置的左右應該是什麼或者應該不是什麼,而後去尋找這個位置。
環視的語法有四種,見第一小節元字符,基本用法以下。
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']
高級一些的例子參見《正則基礎之——環視(Lookaround)》(http://www.cnblogs.com/kernel0815/p/3375249.html)
參考文章:
《Python正則表達式指南》(http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html)
《Python 正則式學習筆記 》(http://blog.csdn.net/whycadi/article/details/2011046)