(一) 爬蟲教程 |正規表示式

古娜娜啦黑暗之神發表於2020-12-18

一、概述:

正規表示式(regular expression)描述了一種字串匹配的模式(pattern),可以用來檢查一個串是否含有某種子串、將匹配的子串替換或者從某個串中取出符合某個條件的子串等。

二、常用的正則匹配規則

在這裡插入圖片描述

三、match()

  • match()方法會嘗試從字串的起始位置匹配正規表示式,如果匹配,就返回匹配成功的結果;如果不匹配,就返回None
  • 在 match()方法中,第一個引數傳入了正規表示式,第二個引數傳入了要匹配的字串
  • 列印輸出結果,可以看到結果是 SRE_Match 物件,這證明成功匹配 該物件有兩個方法: group()
    方法可以輸出匹配到的內容,結果是 Hello 123 456 my mine,這恰好是正規表示式規則所匹配的
    內容;span()方法可以輸出匹配的範圍,結果是span=(0, 21),,這就是匹配到的結果字串在原字串中的
    位置範圍
import re
content = 'Hello 123 456 my mine'
print(len(content))
# ^用它來匹配這個長字串 開頭的 是匹配字串的開頭,也就是以 Hello 開頭
result = re.match('^Hello\s\w+\s\w+\s\w+\s\w+',content)

print(result)
print(result.group())
print(result.span())

輸出:
21
<_sre.SRE_Match object; span=(0, 21), match='Hello 123 456 my mine'>
Hello 123 456 my mine
(0, 21)

3.1、匹配目標

如果想從字串中提 一部分內容,該怎麼辦呢?就像最前面的例項一樣,從一段文字中提取陽郵件或電話號碼等內容

  • 1.這裡可以使用()括號將想提取的子字串括起來 ()實際上標記了一個子表示式的開始和結束位置
  • 2.被標記的每個子表示式會依次對應每一個分組,呼叫 group()方法傳入分組的索引即可獲取提取
  • 3.可以看到,我們成功得到了123這裡用的是 group(1),它與 group()有所不同,後者會輸出
    完整的匹配結果,而前者會輸出第一個被()包圍的匹配結果 假如正規表示式後面還有()包括的內容,那麼可以依次用group(2)、group(3)等來獲取
import re
content = 'Hello 123 456 my mine'
result = re.match('^Hello\s(\d+)\s(\d+)',content)
print(result)
print(result.group())
print(result.group(1))
print(result.span())

輸出:
<_sre.SRE_Match object; span=(0, 13), match='Hello 123 456'>
Hello 123 456
123
(0, 13)

3.2、通用匹配

剛才寫的正規表示式其實較複雜,出現空白字元我們就寫\ 匹配,出現數字我們就用匹配,這樣的工作量非常大 其實完全沒必要這麼做,因為還有一個萬能匹配可以用,那就是 .*(點星) 其中 .(點)可以匹配任意字元(除換行符),(星)代表匹配前面的字元無限次,所以它們組合在一起就可以匹配任意字元了 有了它,我們就不用挨個字元地匹配了

import re
content = 'Hello 123 456 my mine'
# result = re.match('^Hello\s(\d+)\s(\d+)',content)
result = re.match('^Hello.*mine$', content)
print(result)
print(result.group())
print(result.span())

輸出:
<_sre.SRE_Match object; span=(0, 21), match='Hello 123 456 my mine'>
Hello 123 456 my mine
(0, 21)

3.3、貪婪與非貪婪

import re
content = 'Hello 123 456 my mine'
# result = re.match('^Hello\s(\d+)\s(\d+)',content)
result = re.match('^He.*(\d+).*mine$', content)
print(result)
print(result.group(1))

輸出:
<_sre.SRE_Match object; span=(0, 21), match='Hello 123 456 my mine'>
6
  • 我們只得到7個數字是怎麼回事呢?
    這裡就涉及一個貪婪匹配與非貪婪匹配的問題了在貪婪匹配下 * 會匹配儘可能多的字元,則表示式中.*後面是\d+,也就是至少一個數字,並沒有指定具體多少個數字,因此, 就儘可能匹配多的字元,這裡就把 123 456 匹配了,給\d+留下一個可滿足條件的數字 ,最後得到的內容就只有數字
  • 但這很明顯會給我們帶來很大的不便 有時候,匹配結果會莫名其妙少了一部分內容 其實,這
    裡只需要使用非貪婪匹配就好了 非貪婪匹配的寫法是.叫,多了一個飛那麼它可以達到怎樣的效果?
    我們再用例項看一下
import re
content = 'Hello 123 456 my mine'
# result = re.match('^Hello\s(\d+)\s(\d+)',content)
result = re.match('^He.*?(\d+).*mine$', content)
print(result)
print(result.group(1))

# 此時就可以獲取123了,原因可想而知,貪婪匹配
輸出:
<_sre.SRE_Match object; span=(0, 21), match='Hello 123 456 my mine'>
123

  • 在做匹配的時候,字串中間儘量使用非貪婪匹配,也就是用 . * ?來替代. *,以免出現匹配結果缺失的情況
  • 但是需要注意的是匹配結果在字串結尾.*?就有可能匹配不到任何內容了,因為他會盡可能匹配少的字元
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('result1', result2.group(1))

# 可以看到.*?沒有匹配任何結果,而.*則匹配多的內容
輸出:
result1 
result1 kEraCN

3.4、修飾符

在這裡插入圖片描述

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

# 執行報錯,,也就是說正規表示式沒有匹配到這個字串,返回結果為None,又呼叫了group()導致AttrubuteError
輸出:
AttributeError: 'NoneType' object has no attribute 'group'

上面加了一個換行符,則匹配不到。匹配的是除換行符之外的任意字元,當遇到換行符時,.*?就不能匹配,所有導致匹配失敗,這裡只需要新增修飾符re.S,則可以修正這個錯誤

import re

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

3.5、轉義匹配

  • 我們知道正規表示式定義了許多匹配模式,如 匹配除換行符以外的任意字元,但是如果目標字串裡面就包含.的呢?
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www.baidu\.com',content)
print(result)

# 當遇到用於正則匹配的特殊字元時,在前面加反斜線轉義一下即可。例如.就可以用\.來匹配
輸出:
<_sre.SRE_Match object; span=(0, 17), match='(百度)www.baidu.com'>

四、search()

  • 前面提到過, match()方法是從字串的開頭開始匹配的,一旦開頭不匹配,那麼整個匹配就失敗,我們看下面的例子:
content = 'Extra stings Hello 1234567 World This is a Regex Demo Extra stings'
result = re.match('Hello.*?(\d+)',content)
print(result)

# 這裡的字串以 Extra 開頭,但是正規表示式以 ello 開頭,整個正 表示式是字串的一部分,
但是這樣匹配是失敗的 執行結果如下:
輸出:
None
  • 這裡有另外一個方法search(),它在匹配時會掃描整個字串,然後返回第一個成功匹配的結果。在匹配時,serach()方法會依次掃描字串,知道找到第一個符合的字串,然後返回匹配的內容,如果搜尋完了還沒找到,就返回None
# 將上面程式碼中的match換成search
content = 'Extra stings Hello 1234567 World This is a Regex Demo Extra stings'
result = re.search('Hello.*?(\d+)',content)
print(result)

輸出:
<_sre.SRE_Match object; span=(13, 26), match='Hello 1234567'>

五、findall()

  • 前面我們介紹了 search ()方法的用法,它可以返回匹配正規表示式的第 個內容,但是如果想要獲取匹配正規表示式的所有內容,那該怎麼辦呢?這時就要藉助 findall()方法,該方法會搜尋整個字串,然後返回匹配正規表示式的所有內容
html = """< div id="songs-list" >
<h2 class ="title">經典老歌</h2>
<p class=”introduction”>
經典老歌列表
</p>
<ul id=”list” class=”list-group”><li data-view="2"〉一路上有你</li>
<li data-view="7">

<a href="/2.mp3" singer="任賢齊">滄海一聲笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齊秦">往事隨風</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光輝歲月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陳慧琳">記事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="鄧麗君">但願人長久</a></li>
</ul>
</div>"""

result = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html,re.S)
print(result)
print(type(result))


輸出:
[('/2.mp3', '任賢齊', '滄海一聲笑'), ('/3.mp3', '齊秦', '往事隨風'), ('/4.mp3', 'beyond', '光輝歲月'), ('/5.mp3', '陳慧琳', '記事本'), ('/6.mp3', '鄧麗君', '但願人長久')]
<class 'list'>

如果只獲取第一個內容,則使用search()

result = re.search('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html,re.S)
print(result)
print(type(result))

輸出:
<_sre.SRE_Match object; span=(123, 211), match='<li data-view="2"〉一路上有你</li>\n<li data-view="7">\>
<class '_sre.SRE_Match'>

六、sub()

  • 除了使用正規表示式提取資訊外,有時候還需要藉助它來修改文字 比如,想要把 串文字中的所有數字都去掉,如果只用字串的 replace()方法,那就太煩瑣了,這時可以藉助 sub()方法
    如下:
content = '422rfdsa34grvfdsa'
content = re.sub('\d+','',content)
print(content)

輸出:
rfdsagrvfdsa

想獲取所有 li 節點的歌名,如下:

html = re.sub('<a.*?>|</a>', '', html)
results = re.findall('<li.*?>(.*?)</li>',html,re.S)
print(html)

print('--')


for result in results:
    print(result.strip())

輸出:
< div id="songs-list" >
<h2 class ="title">經典老歌</h2>
<p class=”introduction”>
經典老歌列表
</p>
<ul id=”list” class=”list-group”><li data-view="2"〉一路上有你</li>
<li data-view="7">

滄海一聲笑
</li>
<li data-view="4" class="active">
往事隨風
</li>
<li data-view="6">光輝歲月</li>
<li data-view="5">記事本</li>
<li data-view="5">
但願人長久</li>
</ul>
</div>
--
<li data-view="7">

滄海一聲笑
往事隨風
光輝歲月
記事本
但願人長久

七、compile()

compile 函式用於編譯正規表示式,生成一個 Pattern 物件

content1 = '2016-12-15 12:00'
content2 = '2016-12-17 12:55'
content3 = '2016-12-17 13:21'
partern = re.compile('\d{2}:\d{2}')
result1 = re.sub(partern, '', content1)
result2 = re.sub(partern, '', content2)
result3 = re.sub(partern, '', content3)
print(result1, result2, result3)

輸出:
2016-12-15  2016-12-17  2016-12-17 

相關文章