1. 程式人生 > >【Python3 爬蟲學習筆記】基本庫的使用 9—— 正則表示式 2

【Python3 爬蟲學習筆記】基本庫的使用 9—— 正則表示式 2

1.1 匹配目標

如果想從字串中提取出一部分內容,可以用()括號將想提取的子字串括起來。()實際上標記了一個子表示式的開始和結束位置,被標記的每個子表示式會一次對應一個分組,呼叫group()方法傳入分組的索引即可獲取提取的結果。示例如下:

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result)
print(result.group())
print(result.group(
1)) print(result.span())

我們相把字串中的1234567提取出來,此時可買將數字部分的正則表示式用()括起來,然後呼叫了group(1)獲取匹配結果。
執行結果如下:

<_sre.SRE_Match object; span=(0, 19), match='Hello 1234567 World'>
Hello 1234567 World
1234567
(0, 19)

可以看到,我們成功得到了1234567。這裡用的是group(1),它與group()有所不同,後者會輸出完整的匹配結果,而前者會輸出第一個被()包圍的匹配結果。假如正則表示式後面還有()包括的內容,那麼可以依次用group(2)、group(3)等來獲取。

1.2 通用匹配

.(點)可以匹配任意字元(除換行符),*(星)代表匹配前面的字元無限次,所以它們組合在一起可以匹配任意字元了。

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello.*Demo$', content)
print(result)
print(result.group())
print(result.span())

這裡我們將中間部分直接省略,全部用.*來代替,最後加一個結尾字串就好了。

<_sre.SRE_Match object
; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'> Hello 1234567 World_This is a Regex Demo (0, 40)

可以看到,group()方法輸出了匹配的全部字串,也就是我們寫的正則表示式匹配到了目標字串的全部內容;span()方法輸出(0,41),這是整個字串的長度。

1.3 貪婪與非貪婪

在貪婪匹配下,(點星) 會匹配儘可能多的字元。正則表示式中(點星)後面是\d+,也就是至少一個數字,並沒有指定具體多少個數字,因此,.*就儘可能匹配多的字元,這裡就把123456匹配了,給\d+留下一個可滿足條件的數字7,最後得到的內容就只有數字7了。
非貪婪的寫法是(點星),多了一個?,那麼它可以達到怎樣的效果?

import re

content = 'Hello 1234567 World_This is a Regex Demo'
result = re.match('^Hello.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))

這裡我們只是將第一個(點星)改成(點星?),轉變為非貪婪匹配。結果如下:

<_sre.SRE_Match object; span=(0, 40), match='Hello 1234567 World_This is a Regex Demo'>
1234567

此時就可以成功獲取1234567了。貪婪匹配是儘可能匹配多的字元,非貪婪匹配就是儘可能匹配少的字元。當(點星?)匹配到Hello後面的空白字元時,再往後的字元就是數字了,而\d+恰好可以匹配,那麼這裡(點星?)就不再進行匹配,交給\d+去匹配後面的數字。所以這樣(點星?)匹配了儘可能少的額字元,\d+的結果就是1234567。
所以說,在做匹配的時候,字串中間儘可能使用非貪婪匹配,也就是用(點星?)來代替(點星),以免出現匹配確實的情況。
但這裡需要注意,如果匹配的結果在字串,(點星?)就有可能匹配不到任何內容,因為它會匹配儘可能少的字元。

import re

content = 'http://weibo.com/comment/kEraCN'
result1 = re.match('http.*?comment/(.*?)',content)
result2 = re.match('http.*?comment/(.*)',content)
print('result1', result1.group(1))
print('result2', result2.group(1))

執行結果如下:

result1
result2 kEraCN

可以觀察到,.*?沒有匹配到任何結果,而(點星)則儘可能匹配多的內容,成功得到了匹配結果。

1.4 修飾符

正則表示式可以包含一些可選標誌修飾符來控制匹配的模式。修飾符被指定為一個可選的標誌。

import re

content = '''Hello 1234567 World_This
is a Regex Demo
'''
result = re.match('^He.*?(\d+).*?Demo$',content)
print(result.group(1))

我們在字串中加了換行符,正則表示式還是一樣的,用來匹配其中的數字。執行結果如下:

AttributeError                            Traceback (most recent call last)
<ipython-input-6-379f50fcebe7> in <module>()
      5 '''
      6 result = re.match('^He.*?(\d+).*?Demo$',content)
----> 7 print(result.group(1))

AttributeError: 'NoneType' object has no attribute 'group'

執行直接報錯,也就是說正則表示式沒有匹配到這個字串,返回結果為None,而我們又呼叫了group()方法導致AttributeError。
. 匹配的是除換行符之外的任意字元,當遇到換行符是,.*?就不能匹配了,所以導致匹配失敗。這裡只需要一個修飾符re.S,即可修正這個錯誤:

result = re.match('^He.*?(\d+).*?Demo$',content,re.S)

這個修飾符的作用使.匹配包括換行符在內的所有字元。便能匹配到數字:1234567。
這個re.S在網頁匹配中經常用到。因為HTML節點經常會有換行,加上它,就可以匹配節點與節點之間的換行了。
另外,還有一些修飾符,在必要的情況下也可以使用:

修飾符 描述
re.I 使匹配對大小寫不敏感
re.L 使本地化識別(locale-aware)匹配
re.M 多行匹配,影響^和$
re.S 使.匹配包括換行在內的所有字元
re.U 根據Unicode字符集解析字元。這個標誌影響\w、\W、\b和\B
re.X 該標誌通過給予你更靈活的格式以便你將正則表示式寫得更易於理解

在網頁匹配中,較為常用的有re.S和re.I。

1.5 轉義匹配

當遇到用於正則匹配模式的特殊字元時,在前面加反斜線轉義一下即可。例如.就可以用.來匹配。

import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com',content)
print(result)

執行結果:

<_sre.SRE_Match object; span=(0, 17), match='(百度)www.baidu.com'>