鍍金池/ 教程/ Python/ Python 中的正則表達式教程
一個簡單的百度貼吧的小爬蟲
亮劍!爬蟲框架小抓抓 Scrapy 閃亮登場!
Opener 與 Handler 的介紹和實例應用
百度貼吧的網(wǎng)絡爬蟲(v0.4)源碼及解析
異常的處理和 HTTP 狀態(tài)碼的分類
利用 urllib2 通過指定的 URL 抓取網(wǎng)頁內(nèi)容
Python 中的正則表達式教程
爬蟲框架 Scrapy 的第一個爬蟲示例入門教程
抓取網(wǎng)頁的含義和 URL 基本構(gòu)成
urllib2 的使用細節(jié)與抓站技巧
一個爬蟲的誕生全過程(以山東大學績點運算為例)
糗事百科的網(wǎng)絡爬蟲(v0.3)源碼及解析(簡化更新)

Python 中的正則表達式教程

接下來準備用糗百做一個爬蟲的小例子。但是在這之前,先詳細的整理一下 Python 中的正則表達式的相關(guān)內(nèi)容。 正則表達式在 Python 爬蟲中的作用就像是老師點名時用的花名冊一樣,是必不可少的神兵利器。

正則表達式基礎(chǔ)

概念介紹

正則表達式是用于處理字符串的強大工具,它并不是 Python 的一部分。 其他編程語言中也有正則表達式的概念,區(qū)別只在于不同的編程語言實現(xiàn)支持的語法數(shù)量不同。 它擁有自己獨特的語法以及一個獨立的處理引擎,在提供了正則表達式的語言里,正則表達式的語法都是一樣的。

下圖展示了使用正則表達式進行匹配的流程:

http://wiki.jikexueyuan.com/project/python-crawler/images/6.png" alt="" />

正則表達式的大致匹配過程是:

  1. 依次拿出表達式和文本中的字符比較。
  2. 如果每一個字符都能匹配,則匹配成功;一旦有匹配不成功的字符則匹配失敗。
  3. 如果表達式中有量詞或邊界,這個過程會稍微有一些不同。

下圖列出了 Python 支持的正則表達式元字符和語法:

http://wiki.jikexueyuan.com/project/python-crawler/images/7.png" alt="" />

數(shù)量詞的貪婪模式與非貪婪模式

正則表達式通常用于在文本中查找匹配的字符串。 貪婪模式,總是嘗試匹配盡可能多的字符;非貪婪模式則相反,總是嘗試匹配盡可能少的字符。Python 里數(shù)量詞默認是貪婪的。

例如:正則表達式 ab\* 如果用于查找abbbc,將找到abbb。而如果使用非貪婪的數(shù)量詞 ab\*?,將找到"a"。

反斜杠的問題

與大多數(shù)編程語言相同,正則表達式里使用"\"作為轉(zhuǎn)義字符,這就可能造成反斜杠困擾。 假如你需要匹配文本中的字符"\",那么使用編程語言表示的正則表達式里將需要 4 個反斜杠"\\": 第一個和第三個用于在編程語言里將第二個和第四個轉(zhuǎn)義成反斜杠, 轉(zhuǎn)換成兩個反斜杠\后再在正則表達式里轉(zhuǎn)義成一個反斜杠用來匹配反斜杠\。 這樣顯然是非常麻煩的。

Python 里的原生字符串很好地解決了這個問題,這個例子中的正則表達式可以使用 r"\"表示。 同樣,匹配一個數(shù)字的"\d"可以寫成r"\d"。 有了原生字符串,媽媽再也不用擔心我的反斜杠問題~

介紹 re 模塊

Compile

Python 通過 re 模塊提供對正則表達式的支持。使用 re 的一般步驟是:

  • Step1:先將正則表達式的字符串形式編譯為Pattern實例。

  • Step2:然后使用Pattern實例處理文本并獲得匹配結(jié)果(一個Match實例)。

  • Step3:最后使用Match實例獲得信息,進行其他的操作。

我們新建一個 re01.py 來試驗一下 re 的應用:

# -*- coding: utf-8 -*-  
#一個簡單的re實例,匹配字符串中的hello字符串  

#導入re模塊  
import re  

# 將正則表達式編譯成Pattern對象,注意hello前面的r的意思是“原生字符串”  
pattern = re.compile(r'hello')  

# 使用Pattern匹配文本,獲得匹配結(jié)果,無法匹配時將返回None  
match1 = pattern.match('hello world!')  
match2 = pattern.match('helloo world!')  
match3 = pattern.match('helllo world!')  

#如果match1匹配成功  
if match1:  
    # 使用Match獲得分組信息  
    print match1.group()  
else:  
    print 'match1匹配失敗!'  

#如果match2匹配成功  
if match2:  
    # 使用Match獲得分組信息  
    print match2.group()  
else:  
    print 'match2匹配失??!'  

#如果match3匹配成功  
if match3:  
    # 使用Match獲得分組信息  
    print match3.group()  
else:  
    print 'match3匹配失?。?  

可以看到控制臺輸出了匹配的三個結(jié)果:

http://wiki.jikexueyuan.com/project/python-crawler/images/8.png" alt="" />

下面來具體看看代碼中的關(guān)鍵方法。

★ re.compile(strPattern[, flag]):

這個方法是 Pattern 類的工廠方法,用于將字符串形式的正則表達式編譯為 Pattern 對象。 第二個參數(shù) flag 是匹配模式,取值可以使用按位或運算符'|'表示同時生效,比如 re.I | re.M。 另外,你也可以在 regex 字符串中指定模式, 比如 re.compile('pattern', re.I | re.M)與re.compile('(?im)pattern')是等價的。 可選值有:

  • re.I(全拼:IGNORECASE): 忽略大小寫(括號內(nèi)是完整寫法,下同)
  • re.M(全拼:MULTILINE): 多行模式,改變'^'和'$'的行為(參見上圖)
  • re.S(全拼:DOTALL): 點任意匹配模式,改變'.'的行為
  • re.L(全拼:LOCALE): 使預定字符類 \w \W \b \B \s \S 取決于當前區(qū)域設定
  • re.U(全拼:UNICODE): 使預定字符類 \w \W \b \B \s \S \d \D 取決于 unicode 定義的字符屬性
  • re.X(全拼:VERBOSE): 詳細模式。這個模式下正則表達式可以是多行,忽略空白字符,并可以加入注釋。

以下兩個正則表達式是等價的:

# -*- coding: utf-8 -*-  
#兩個等價的re匹配,匹配一個小數(shù)  
import re  

a = re.compile(r"""\d +  # the integral part 
                   \.    # the decimal point 
                   \d *  # some fractional digits""", re.X)  

b = re.compile(r"\d+\.\d*")  

match11 = a.match('3.1415')  
match12 = a.match('33')  
match21 = b.match('3.1415')  
match22 = b.match('33')   

if match11:  
    # 使用Match獲得分組信息  
    print match11.group()  
else:  
    print u'match11不是小數(shù)'  

if match12:  
    # 使用Match獲得分組信息  
    print match12.group()  
else:  
    print u'match12不是小數(shù)'  

if match21:  
    # 使用Match獲得分組信息  
    print match21.group()  
else:  
    print u'match21不是小數(shù)'  

if match22:  
    # 使用Match獲得分組信息  
    print match22.group()  
else:  
    print u'match22不是小數(shù)'  

re 提供了眾多模塊方法用于完成正則表達式的功能。這些方法可以使用 Pattern 實例的相應方法替代,唯一的好處是少寫一行 re.compile()代碼,但同時也無法復用編譯后的 Pattern 對象。這些方法將在 Pattern 類的實例方法部分一起介紹。

如一開始的 hello 實例可以簡寫為:

# -*- coding: utf-8 -*-  
#一個簡單的re實例,匹配字符串中的hello字符串  
import re  

m = re.match(r'hello', 'hello world!')  
print m.group()  

re 模塊還提供了一個方法 escape(string),用于將 string 中的正則表達式元字符如 */+/?等之前加上轉(zhuǎn)義符再返回

Match

Match 對象是一次匹配的結(jié)果,包含了很多關(guān)于此次匹配的信息,可以使用 Match 提供的可讀屬性或方法來獲取這些信息。

屬性

  • string: 匹配時使用的文本。
  • re: 匹配時使用的 Pattern 對象。
  • pos: 文本中正則表達式開始搜索的索引。值與 Pattern.match() 和 Pattern.seach() 方法的同名參數(shù)相同。
  • endpos: 文本中正則表達式結(jié)束搜索的索引。值與 Pattern.match() 和Pattern.seach() 方法的同名參數(shù)相同。
  • lastindex: 最后一個被捕獲的分組在文本中的索引。如果沒有被捕獲的分組,將為 None
  • lastgroup: 最后一個被捕獲的分組的別名。如果這個分組沒有別名或者沒有被捕獲的分組,將為 None。

方法

  1. group([group1, …]):
    獲得一個或多個分組截獲的字符串;指定多個參數(shù)時將以元組形式返回。group1 可以使用編號也可以使用別名;編號 0 代表整個匹配的子串;不填寫參數(shù)時,返回 group(0);沒有截獲字符串的組返回 None;截獲了多次的組返回最后一次截獲的子串。
  2. groups([default]):
    以元組形式返回全部分組截獲的字符串。相當于調(diào)用 group(1,2,…last)。default 表示沒有截獲字符串的組以這個值替代,默認為 None。
  3. groupdict([default]):
    返回以有別名的組的別名為鍵、以該組截獲的子串為值的字典,沒有別名的組不包含在內(nèi)。default 含義同上。
  4. start([group]):
    返回指定的組截獲的子串在 string 中的起始索引(子串第一個字符的索引)。group 默認值為 0。
  5. end([group]):
    返回指定的組截獲的子串在 string 中的結(jié)束索引(子串最后一個字符的索引+1)。group 默認值為 0。
  6. span([group]):
    返回(start(group), end(group))。
  7. expand(template):
    將匹配到的分組代入 template 中然后返回。template 中可以使用\id或\g、\g引用分組,但不能使用編號 0。\id 與\g是等價的;但\10 將被認為是第 10 個分組,如果你想表達\1 之后是字符'0',只能使用\g<1>0。

下面來用一個 py 實例輸出所有的內(nèi)容加深理解:

# -*- coding: utf-8 -*-  
#一個簡單的match實例  

import re  
# 匹配如下內(nèi)容:單詞+空格+單詞+任意字符  
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')  

print "m.string:", m.string  
print "m.re:", m.re  
print "m.pos:", m.pos  
print "m.endpos:", m.endpos  
print "m.lastindex:", m.lastindex  
print "m.lastgroup:", m.lastgroup  

print "m.group():", m.group()  
print "m.group(1,2):", m.group(1, 2)  
print "m.groups():", m.groups()  
print "m.groupdict():", m.groupdict()  
print "m.start(2):", m.start(2)  
print "m.end(2):", m.end(2)  
print "m.span(2):", m.span(2)  
print r"m.expand(r'\g<2> \g<1>\g<3>'):", m.expand(r'\2 \1\3')  

### output ###  
# m.string: hello world!  
# m.re: <_sre.SRE_Pattern object at 0x016E1A38>  
# m.pos: 0  
# m.endpos: 12  
# m.lastindex: 3  
# m.lastgroup: sign  
# m.group(1,2): ('hello', 'world')  
# m.groups(): ('hello', 'world', '!')  
# m.groupdict(): {'sign': '!'}  
# m.start(2): 6  
# m.end(2): 11  
# m.span(2): (6, 11)  
# m.expand(r'\2 \1\3'): world hello!  

Pattern

Pattern 對象是一個編譯好的正則表達式,通過 Pattern 提供的一系列方法可以對文本進行匹配查找。 Pattern 不能直接實例化,必須使用 re.compile()進行構(gòu)造,也就是 re.compile()返回的對象。 Pattern 提供了幾個可讀屬性用于獲取表達式的相關(guān)信息:

  • pattern: 編譯時用的表達式字符串。
  • flags: 編譯時用的匹配模式。數(shù)字形式。
  • groups: 表達式中分組的數(shù)量。
  • groupindex: 以表達式中有別名的組的別名為鍵、以該組對應的編號為值的字典,沒有別名的組不包含在內(nèi)。

可以用下面這個例子查看 pattern 的屬性:

# -*- coding: utf-8 -*-  
#一個簡單的pattern實例  

import re  
p = re.compile(r'(\w+) (\w+)(?P<sign>.*)', re.DOTALL)  

print "p.pattern:", p.pattern  
print "p.flags:", p.flags  
print "p.groups:", p.groups  
print "p.groupindex:", p.groupindex  

### output ###  
# p.pattern: (\w+) (\w+)(?P<sign>.*)  
# p.flags: 16  
# p.groups: 3  
# p.groupindex: {'sign': 3}  

下面重點介紹一下 pattern 的實例方法及其使用。

match

match(string[, pos[, endpos]]) | re.match(pattern, string[, flags]):

這個方法將從 string 的 pos 下標處起嘗試匹配 pattern;如果 pattern 結(jié)束時仍可匹配,則返回一個 Match 對象;如果匹配過程中 pattern 無法匹配,或者匹配未結(jié)束就已到達 endpos,則返回 None。pos 和 endpos 的默認值分別為 0 和 len(string);re.match()無法指定這兩個參數(shù),參數(shù) flags 用于編譯 pattern時指定匹配模式。

注意:這個方法并不是完全匹配。當 pattern 結(jié)束時若 string 還有剩余字符,仍然視為成功。 想要完全匹配,可以在表達式末尾加上邊界匹配符'$'。

下面來看一個 Match 的簡單案例:

# encoding: UTF-8  
import re  

# 將正則表達式編譯成Pattern對象  
pattern = re.compile(r'hello')  

# 使用Pattern匹配文本,獲得匹配結(jié)果,無法匹配時將返回None  
match = pattern.match('hello world!')  

if match:  
    # 使用Match獲得分組信息  
    print match.group()  

### 輸出 ###  
# hello  

search

search(string[, pos[, endpos]]) | re.search(pattern, string[, flags]): 

這個方法用于查找字符串中可以匹配成功的子串。從 string 的 pos 下標處起嘗試匹配 pattern,如果 pattern 結(jié)束時仍可匹配,則返回一個 Match 對象;若無法匹配,則將 pos 加 1 后重新嘗試匹配;直到 pos=endpos 時仍無法匹配則返回 None。pos 和 endpos 的默認值分別為 0 和 len(string)); re.search()無法指定這兩個參數(shù),參數(shù) flags 用于編譯 pattern 時指定匹配模式。那么它和 match 有什么區(qū)別呢?match()函數(shù)只檢測 re 是不是在 string 的開始位置匹配,search()會掃描整個 string 查找匹配,

match()只有在 0 位置匹配成功的話才有返回,如果不是開始位置匹配成功的話,match() 就返回 none。

例如:

print(re.match(‘super’, ‘superstition’).span()) 

會返回(0, 5)

print(re.match(‘super’, ‘insuperable’))  

則返回 None

search()會掃描整個字符串并返回第一個成功的匹配

例如:

print(re.search(‘super’, ‘superstition’).span())

返回(0, 5)

print(re.search(‘super’, ‘insuperable’).span())  

返回(2, 7)

看一個 search 的實例:

# -*- coding: utf-8 -*-  
#一個簡單的search實例  

import re  

# 將正則表達式編譯成Pattern對象  
pattern = re.compile(r'world')  

# 使用search()查找匹配的子串,不存在能匹配的子串時將返回None  
# 這個例子中使用match()無法成功匹配  
match = pattern.search('hello world!')  

if match:  
    # 使用Match獲得分組信息  
    print match.group()  

### 輸出 ###  
# world  

split

split(string[, maxsplit]) | re.split(pattern, string[, maxsplit]):  

按照能夠匹配的子串將 string 分割后返回列表。maxsplit 用于指定最大分割次數(shù),不指定將全部分割。

import re  

p = re.compile(r'\d+')  
print p.split('one1two2three3four4')  

### output ###  
# ['one', 'two', 'three', 'four', '']  

findall

findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags]):

搜索 string,以列表形式返回全部能匹配的子串。

import re  

p = re.compile(r'\d+')  
print p.findall('one1two2three3four4')  

### output ###  
# ['1', '2', '3', '4']  

finditer

finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags]):  

搜索 string,返回一個順序訪問每一個匹配結(jié)果(Match 對象)的迭代器。

import re  

p = re.compile(r'\d+')  
for m in p.finditer('one1two2three3four4'):  
    print m.group(),  

### output ###  
# 1 2 3 4  

sub

sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]): 

使用 repl 替換 string 中每一個匹配的子串后返回替換后的字符串。 當 repl 是一個字符串時,可以使用\id 或\g、\g引用分組,但不能使用編號 0。 當 repl 是一個方法時,這個方法應當只接受一個參數(shù)(Match 對象),并返回一個字符串用于替換(返回的字符串中不能再引用分組)。count 用于指定最多替換次數(shù),不指定時全部替換。

import re  

p = re.compile(r'(\w+) (\w+)')  
s = 'i say, hello world!'  

print p.sub(r'\2 \1', s)  

def func(m):  
    return m.group(1).title() + ' ' + m.group(2).title()  

print p.sub(func, s)  

### output ###  
# say i, world hello!  
# I Say, Hello World!  

subn

subn(repl, string[, count]) |re.sub(pattern, repl, string[, count]):

返回 (sub(repl, string[, count]),替換次數(shù))。

import re  

p = re.compile(r'(\w+) (\w+)')  
s = 'i say, hello world!'  

print p.subn(r'\2 \1', s)  

def func(m):  
    return m.group(1).title() + ' ' + m.group(2).title()  

print p.subn(func, s)  

### output ###  
# ('say i, world hello!', 2)  
# ('I Say, Hello World!', 2)