Python爬蟲學習(4): python中re模塊中的向后引用以及零寬斷言


轉自:http://www.cnblogs.com/linux-wangkun/p/5978462.html

使用小括號的時候,還有很多特定用途的語法。下面列出了最常用的一些:

表4.常用分組語法
分類 代碼/語法 說明
捕獲 (exp) 匹配exp,並捕獲文本到自動命名的組里

(?<name>exp) 

{ python:  (?P<name>exp) }

匹配exp,並捕獲文本到名稱為name的組里,也可以寫成(?'name'exp)
(?:exp) 匹配exp,不捕獲匹配的文本,也不給此分組分配組號
零寬斷言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注釋 (?#comment) 這種類型的分組不對正則表達式的處理產生任何影響,用於提供注釋讓人閱讀

 

1. 后向引用

  使用小括號指定一個子表達式后,匹配這個子表達式的文本(也就是此分組捕獲的內容)可以在表達式或其它程序中作進一步的處理。默認情況下,每個分組會自動擁有一個組號,規則是:從左向右,以分組的左括號為標志,第一個出現的分組的組號為1,第二個為2,以此類推。

呃……其實,組號分配還不像我剛說得那么簡單:

  • 分組0對應整個正則表達式
  • 實際上組號分配過程是要從左向右掃描兩遍的:第一遍只給未命名組分配,第二遍只給命名組分配--因此所有命名組的組號都大於未命名的組號
  • 你可以使用(?:exp)這樣的語法來剝奪一個分組對組號分配的參與權.

  后向引用用於重復搜索前面某個分組匹配的文本。例如,\1代表分組1匹配的文本。難以理解?請看示例:

  \b(\w+)\b\s+\1\b可以用來匹配重復的單詞,像go go, 或者kitty kitty。這個表達式首先是一個單詞,也就是單詞開始處和結束處之間的多於一個的字母或數字(\b(\w+)\b),這個單詞會被捕獲到編號為1的分組中,然后是1個或幾個空白符(\s+),最后是分組1中捕獲的內容(也就是前面匹配的那個單詞)(\1)。

In [65]: pattern = re.compile(r"\b(\w+)\b\s+\1\b")
In [66]: str = "hello go go hello"
In [67]: re.findall(pattern,str)
Out[67]: ['go']

  你也可以自己指定子表達式的組名。要指定一個子表達式的組名,請使用這樣的語法:(?<Word>\w+)(或者把尖括號換成'也行:(?'Word'\w+)),這樣就把\w+的組名指定為Word了。要反向引用這個分組捕獲的內容,你可以使用\k<Word>,所以上一個例子也可以寫成這樣:\b(?<Word>\w+)\b\s+\k<Word>\b。

  在python中指定子表示式的格式為: (?P<name>...),向后引用的格式為: (?P=name),所以上述所說的正則表達式在ptyon中正確表達為: r"\b(?P<word>\w+)\b\s+(?P=word)\b"。實例如下:

In [86]: str = "hello go go hello"
In [87]: pattern = re.compile(r"\b(?P<word>\w+)\b\s+(?P=word)\b")
In [88]: re.findall(pattern,str)
Out[88]: ['go']

 

2. 零寬斷言

  接下來的四個用於查找在某些內容(但並不包括這些內容)之前或之后的東西,也就是說它們像\b,^,$那樣用於指定一個位置,這個位置應該滿足一定的條件(即斷言),因此它們也被稱為零寬斷言。最好還是拿例子來說明吧:

  斷言用來聲明一個應該為真的事實。正則表達式中只有當斷言為真時才會繼續進行匹配。

  (?=exp)也叫零寬度正預測先行斷言,它斷言自身出現的位置的后面能匹配表達式exp。比如\b\w+(?=ing\b),匹配以ing結尾的單詞的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.時,它會匹配sing和danc。

In [100]: str = "I'm singing while you're dancing."
In [101]: pattern = re.compile(r"\b\w+(?=ing\b)")
In [102]: re.findall(pattern,str)
Out[102]: ['sing', 'danc']

  (?<=exp)也叫零寬度正回顧后發斷言(- _ - positive lookbehind assertion),它斷言自身出現的位置的前面能匹配表達式exp。比如(?<=\bre)\w+\b會匹配以re開頭的單詞的后半部分(除了re以外的部分),例如在查找reading a book時,它匹配ading。

In [103]: str = "reading a book"
In [104]: pattern = re.compile(r"(?<=\bre)\w+\b")
In [105]: re.findall(pattern,str)
Out[105]: ['ading']

  下面這個例子同時使用了這兩種斷言:(?<=\s)\d+(?=\s)匹配以逗號間隔的數字(再次強調,不包括這些逗號)。

In [122]: str = "123,456,789"
In [123]: pattern = re.compile(r"(?<=,)?(\d+)(?=,)?")
In [124]: re.findall(pattern,str)
Out[124]: ['123', '456', '789']

3. 負向零寬斷言

  前面我們提到過怎么查找不是某個字符或不在某個字符類里的字符的方法(反義)。但是如果我們只是想要確保某個字符沒有出現,但並不想去匹配它時怎么辦?例如,如果我們想查找這樣的單詞--它里面出現了字母q,但是q后面跟的不是字母u,我們可以嘗試這樣: \b\w*q[^u]\w*\b,匹配包含后面不是字母u的字母q的單詞。但是如果多做測試(或者你思維足夠敏銳,直接就觀察出來了),你會發現,如果q出現在單詞的結尾的話,像Iraq,Benq,這個表達式就會出錯。這是因為[^u]總要匹配一個字符,所以如果q是單詞的最后一個字符的話,后面的[^u]將會匹配q后面的單詞分隔符(可能是空格,或者是句號或其它的什么),后面的\w*\b將會匹配下一個單詞,於是\b\w*q[^u]\w*\b就能匹配整個Iraq fighting。負向零寬斷言能解決這樣的問題,因為它只匹配一個位置,並不消費任何字符。現在,我們可以這樣來解決這個問題:\b\w*q(?!u)\w*\b。

  零寬度負預測先行斷言(?!exp),斷言此位置的后面不能匹配表達式exp。例如:\d{3}(?!\d)匹配三位數字,而且這三位數字的后面不能是數字;\b((?!abc)\w)+\b匹配不包含連續字符串abc的單詞。

同理,我們可以用(?<!exp),零寬度負回顧后發斷言來斷言此位置的前面不能匹配表達式exp:(?<![a-z])\d{7}匹配前面不是小寫字母的七位數字。

  請詳細分析表達式(?<=<(\w+)>).*(?=<\/\1>),這個表達式最能表現零寬斷言的真正用途。

  一個更復雜的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含屬性的簡單HTML標簽內里的內容。(?<=<(\w+)>)指定了這樣的前綴:被尖括號括起來的單詞(比如可能是<span>),然后是.*(任意的字符串),最后是一個后綴(?=<\/\1>)。注意后綴里的\/,它用到了前面提過的字符轉義;\1則是一個反向引用,引用的正是捕獲的第一組,前面的(\w+)匹配的內容,這樣如果前綴實際上是<span>的話,后綴就是</span>了。整個表達式匹配的是<span>和</span>之間的內容(再次提醒,不包括前綴和后綴本身)。

  但是在python中這樣做會報 look-behind requires fixed-width pattern 的錯誤,以也就是在負向零寬斷言中不能有不確定長度的表達式:

復制代碼
In [128]: str = "<span> hello world </span>"

In [129]: pattern = re.compile(r"(?<=(<\w+>))(.*)(?=<\/\1>)")
---------------------------------------------------------------------------
error                                     Traceback (most recent call last)
......
error: look-behind requires fixed-width pattern
復制代碼

  為了能夠正確匹配需要將\w+改為\w{4}

In [130]: pattern = re.compile(r"(?<=<(\w{4})>)(.*)(?=<\/\1>)")
In [131]: re.findall(pattern,str)
Out[131]: [('span', ' hello world ')]

 

參考學習地址: http://deerchao.net/tutorials/regex/regex.htm  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM