2020年9月12日星期六

60 《零基礎入門學習Python》筆記 第060講:論一隻爬蟲的自我修養8:正則表達式4

《零基礎入門學習Python》筆記  第060講:論一隻爬蟲的自我修養8:正則表達式4

有了前面幾節課的準備,我們這一次終於可以真刀真槍的干一場大的了,但是呢,在進行實戰之前,我們還要講講正則表達式的實用方法和擴展語法,然後再來實戰,大家多把持一會啊。
我們先來翻一下文檔:
首先,我們要舉的例子是講得最多的search() 方法,search() 方法既有模塊級別的,就是直接調用re.search() 來實現,另外,編譯後的正則表達式模式對像也同樣擁有search() 方法,我問問大家,它們之間有區別嗎?
如果你的回答僅僅是模塊級別的search() 方法比模式級別的search() 方法要多一個正則表達式的參數,那你肯定沒有去翻文檔。
re.searchpatternstringflags = 0
掃描字符串以查找正則表達式模式產生匹配項的第一個位置,然後返回相應的match對象None如果字符串中沒有位置與模式匹配,則返回;否則返回false。請注意,這不同於在字符串中的某個點找到零長度匹配。
這是模塊級別的search() 方法,大家注意它的參數,它有一個flags 參數, flags 參數就我們上節課講得編譯標誌位,作為一個模塊級別的,它沒辦法複印,它直接在這裡使用它的標誌位就可以了。
pattern 是正則表達式的模式
string 是要搜索的字符串
我們再來看一下如果是編譯後的模式對象,它的search() 方法又有哪些參數:
regex.search字符串 [,pos [,endpos ]])
掃描字符串以查找此正則表達式產生匹配項的第一個位置,然後返回相應的match對象None如果字符串中沒有位置與模式匹配,則返回;否則返回false。請注意,這不同於在字符串中的某個點找到零長度匹配。
可選的第二個參數pos在開始搜索的字符串中給出一個索引;默認為0這並不完全等同於切片字符串。'^'模式字符在字符串的真正開始,並在僅僅一個換行符後的位置相匹配,但不一定,其中搜索是啟動索引。
可選參數endpos限制將搜索字符串的距離;就像字符串是endpos字符長一樣,因此僅搜索pos的字符endpos - 1進行匹配。如果endpos小於pos,則不會找到匹配項;否則,如果rx是已編譯的正則表達式對象,rx.search(string, 0, 50)則等效於rx.search(string[:50], 0)
前面的pattern, 模式對象的參數,就不需要了。
string 第一個參數就是待搜索的字符串
後面有兩個可選參數是我們模塊級別的search() 方法沒有的,它分別代表需要搜索的起始位置(pos)和結束位置(endpos)
你就可以像 rx.search(string, 0, 50) 或者 rx.search(string[:50], 0) 这样子去匹配它的搜索位置了。
還有一點可能被忽略的就是,search() 方法並不會立刻返回你所需要的字符串,取而代之,它是返回一個匹配對象。我們來舉個例子:
  1. >>> import re
  2. >>> result = re.search(r" (\w+) (\w+)", "I love Python.com")
  3. >>> result
  4. <_sre.SRE_Match object; span=(1, 13), match=' love Python'>
我們看到,這個result是一個匹配對象(match object .),而不是一個字符串。它這個匹配對像有一些方法,你使用這些方法才能夠獲得你所需要的匹配的字符串:
例如:group()方法:
  1. >>> result.group()
  2. ' love Python'
我們就把匹配的內容打印出來了。首先是一個空格,然後是\w+ ,就是任何字符,這裡就是love,然後又是一個空格,然後又是\w+,這裡就是Python。
說到這個group()方法,值的一提的是,如果正則表達式中存在著子組,子組會將匹配的內容進行捕獲,通過這個group()方法中設置序號,可以提取到對應的子組(序號從1開始) 捕獲的字符串。例如:
  1. >>> result.group(1)
  2. 'love'
  3. >>> result.group(2)
  4. 'Python'
除了 group()方法之外,它還有start()方法 、end()方法、 span() 方法,分別返回它匹配的開始位置、結束位置、範圍。
match.start([  ])
match.end([  ])
返回匹配的子字符串的開始和結束的索引默認為零(表示整個匹配的子字符串)。返回-1如果存在,但無助於比賽。對於匹配對象,和一組這並有助於匹配,則子組由匹配(相當於m.group(g))是
m.string [m.start(g):m.end(g)]
請注意,如果group匹配一個空字符串m.start(group)則等於例如,after之後為1,為2,並且均為2,並引發異常。m.end(group)m = re.search('b(c?)', 'cba')m.start(0)m.end(0)m.start(1)m.end(1)m.start(2)IndexError
從電子郵件地址中刪除remove_this的示例
>>>電子郵件=“ tony@tiremove_thisger.net”
>>> m = re.search(“ remove_this”,電子郵件)
>>>電子郵件[:m.start()] +電子郵件[m.end():]
'tony@tiger.net'
match.span([  ])
對於匹配m,返回2元組(m.start(group), m.end(group))請注意,如果沒有為比賽做出貢獻,則為(-1, -1)默認為零,即整個匹配。
  1. >>> result.start()
  2. 1
  3. >>> result.end()
  4. 13
  5. >>> result.span()
  6. (1, 13)

 接下來講講findall() 方法:

re.findallpatternstringflags = 0
返回所有非重疊的匹配模式字符串,如字符串列表。字符串被掃描的左到右,而比賽的順序返回找到。如果模式中存在一個或多個組,則返回一個組列表;否則,返回一個列表。如果模式包含多個組,則這將是一個元組列表。空匹配項將包括在結果中,除非它們碰到另一個匹配項的開頭。
有人可能會覺得,findall() 方法很容易,不就是找到所有匹配的內容,然後把它們組織成列表的形式返回嗎。
沒錯,這是在正則表達式裡沒有子組的情況下所做的事,如果正則表達式裡包含了子組,那麼,findall() 會變得很聰明。
我們來舉個例子吧,上貼吧爬圖:
例如我們想下載這個頁面的所有圖片:https://tieba.baidu.com/p/4863860271
我們先來踩點,看到圖片格式的標籤:
我們就來直接寫代碼啦:
首先,我們寫下下面的代碼,來爬取圖片地址:
  1. import re
  2. p = r'<img class="BDE_Image" src="[^"]+\.jpg"'
  3. imglist = re.findall(p, html)
  4. for each in imglist:
  5. print(each)
打印的結果為:
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\tieba.py ==============
  2. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=65ac7c3d9e0a304e5222a0f2e1c9a7c3/4056053b5bb5c9ea8d7d0bdadc39b6003bf3b34e.jpg"
  3. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=d887aa03394e251fe2f7e4f09787c9c2/77f65db5c9ea15ceaf60e830bf003af33b87b24e.jpg"
  4. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=0db90d472c1f95caa6f592bef9167fc5/2f78cfea15ce36d34f8a8b0933f33a87e850b14e.jpg"
  5. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=abfd18169ccad1c8d0bbfc2f4f3f67c4/bd2713ce36d3d5392db307fa3387e950342ab04e.jpg"
很顯然,這不是我們需要的地址,我們需要的只是後面的部分。我們接下來要解決的問題就是如何將裡面的地址提取出來,不少人聽到這裡,可能就已經開始動手了。但是,別急,我這裡有更好的方法。
只需要把圖片地址用小括號括起來,即將:
 p = r'<img class =“ BDE_Image” src =“ [^”] + \。jpg“'替換p = r'<img class =” BDE_Image“ src =”([^“] + \。jpg) “',
大家再來看一下運行後的結果:
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\tieba.py ==============
  2. https://imgsa.baidu.com/forum/w%3D580/sign=65ac7c3d9e0a304e5222a0f2e1c9a7c3/4056053b5bb5c9ea8d7d0bdadc39b6003bf3b34e.jpg
  3. https://imgsa.baidu.com/forum/w%3D580/sign=d887aa03394e251fe2f7e4f09787c9c2/77f65db5c9ea15ceaf60e830bf003af33b87b24e.jpg
  4. https://imgsa.baidu.com/forum/w%3D580/sign=0db90d472c1f95caa6f592bef9167fc5/2f78cfea15ce36d34f8a8b0933f33a87e850b14e.jpg
  5. https://imgsa.baidu.com/forum/w%3D580/sign=abfd18169ccad1c8d0bbfc2f4f3f67c4/bd2713ce36d3d5392db307fa3387e950342ab04e.jpg
是不是很興奮,是不是很驚訝,先別急,我先把代碼敲完,再給大家講解。
  1. import urllib.request
  2. import re
  3. def open_url(url):
  4. req = urllib.request.Request(url)
  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')
  6. response = urllib.request.urlopen(url)
  7. html = response.read()
  8. return html
  9. def get_img(url):
  10. html = open_url(url).decode('utf-8')
  11. p = r'<img class="BDE_Image" src="([^"]+\.jpg)"'
  12. imglist = re.findall(p, html)
  13. '''
  14. for each in imglist:
  15. print(each)
  16. '''
  17. for each in imglist:
  18. filename = each.split('/')[-1]
  19. urllib.request.urlretrieve(each, filename, None)
  20. if __name__ == '__main__':
  21. url = "https://tieba.baidu.com/p/4863860271"
  22. get_img(url)
運行結果,就是很多美眉圖片出現在桌面了(前提是這個程序在桌面運行,圖片自動下載到程序所在文件夾。)
接下來就來解決大家的困惑了:為什麼加個小括號會如此方便呢?

這是因為在findall() 方法中,如果給出的正則表達式是包含著子組的話,那麼就會把子組的內容單獨給返回回來。然而,如果存在多個子組,那麼它還會將匹配的內容組合成元組的形式再返回。

我們還是舉個例子:
因為有時候findall() 如果使用的不好,很多同學就會感覺很疑惑,很迷茫……
拿前面匹配ip地址的正則表達式來講解,我們使用findall()來嘗試自動從https://www.xicidaili.com/wt/獲取ip地址:
初代碼如下:
  1. import urllib.request
  2. import re
  3. def open_url(url):
  4. req = urllib.request.Request(url)
  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')
  6. reponse = urllib.request.urlopen(req)
  7. html = reponse.read()
  8. return html
  9. def get_ip(url):
  10. html = open_url(url).decode('utf-8')
  11. p = r'(([0,1]?\d?\d|2[0-4]\d|25[0-5])\.){3}([0,1]?\d?\d|2[0-4]\d|25[0-5])'
  12. iplist = re.findall(p, html)
  13. for each in iplist:
  14. print(each)
  15. if __name__ == "__main__":
  16. url = "https://www.xicidaili.com/wt/"
  17. get_ip(url)
運行結果如下:
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\getIP.py ==============
  2. ('180.', '180', '122')
  3. ('248.', '248', '79')
  4. ('129.', '129', '198')
  5. ('217.', '217', '7')
  6. ('40.', '40', '35')
  7. ('128.', '128', '21')
  8. ('118.', '118', '106')
  9. ('101.', '101', '46')
  10. ('3.', '3', '4')
得到的結果讓我們很迷茫,為什麼會這樣呢?這明顯不是我們想要的結果,這是因為我們在正則表達式裡面使用了3 個子組,所以,findall() 會自作聰明的把我們的結果做了分類,然後用元組的形式返回給我們。
那有沒有解決的方法呢?

要解決這個問題,我們可以讓子組不捕獲內容。

我們查看->  Python3正則表達式特殊符號及用法(詳細列表) ,尋求擴展語法。
讓子組不捕獲內容,擴展語法就是非捕獲組:
所以我們的初代碼修改如下:
  1. import urllib.request
  2. import re
  3. def open_url(url):
  4. req = urllib.request.Request(url)
  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')
  6. reponse = urllib.request.urlopen(req)
  7. html = reponse.read()
  8. return html
  9. def get_ip(url):
  10. html = open_url(url).decode('utf-8')
  11. p = r'(?:(?:[0,1]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[0,1]?\d?\d|2[0-4]\d|25[0-5])'
  12. iplist = re.findall(p, html)
  13. for each in iplist:
  14. print(each)
  15. if __name__ == "__main__":
  16. url = "https://www.xicidaili.com/wt/"
  17. get_ip(url)
運行得到的結果也是我們想要的ip 地址了,如下:
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\getIP.py ==============
  2. 183.47.40.35
  3. 61.135.217.7
  4. 221.214.180.122
  5. 101.76.248.79
  6. 182.88.129.198
  7. 175.165.128.21
  8. 42.48.118.106
  9. 60.216.101.46
  10. 219.245.3.4
  11. 117.85.221.45
接下來我們又回到文檔:
另外還有一些使用的方法,例如:
finditer() ,是將結果返回一個迭代器,方便以迭代方式獲取數據。
sub() ,是實現替換的操作。
Python3正則表達式特殊符號及用法(詳細列表)中也還有一些特殊的語法,例如:
(?=...):前向肯定斷言。
(?!...):前向否定斷言。
(?<=...):後向肯定斷言。
(?<!...):後向肯定斷言。
這些都是非常有用的,但是呢,這些內容有點多了,如果說全部都講正則表達式的話,那我們就是喧賓奪主了,我們主要講的是網絡爬蟲哦。

所以,大家還是要自主學習一下,多看,多學,多操作。



0 留言:

發佈留言