python 正則表達式使用方法及爬蟲案例

這篇博客是自己《數據挖掘與分析》課程講到正則表達式爬蟲的相關內容,主要簡單介紹Python正則表達式爬蟲,同時講述常見的正則表達式分析方法,最後通過實例爬取作者的個人博客網站。希望這篇基礎文章對您有所幫助,如果文章中存在錯誤或不足之處,還請海涵。真的太忙了,太長時間沒有寫博客了,抱歉~

一.正則表達式

正則表達式(Regular Expression,簡稱Regex或RE)又稱爲正規表示法或常規表示法,常常用來檢索、替換那些符合某個模式的文本,它首先設定好了一些特殊的字及字符組合,通過組合的“規則字符串”來對表達式進行過濾,從而獲取或匹配我們想要的特定內容。它具有靈活、邏輯性和功能性非常的強,能迅速地通過表達式從字符串中找到所需信息的優點,但對於剛接觸的人來說,比較晦澀難懂。

1.re模塊

Python通過re模塊提供對正則表達式的支持,使用正則表達式之前需要導入該庫。

import re

其基本步驟是先將正則表達式的字符串形式編譯爲Pattern實例,然後使用Pattern實例處理文本並獲得一個匹配(Match)實例,再使用Match實例獲得所需信息。常用的函數是findall,原型如下:

findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags])
該函數表示搜索字符串string,以列表形式返回全部能匹配的子串。
其中參數re包括三個常見值:
  (1)re.I(re.IGNORECASE):忽略大小寫(括號內是完整寫法)
  (2)re.M(re.MULTILINE):允許多行模式
  (3)re.S(re.DOTALL):支持點任意匹配模式

Pattern
對象是一個編譯好的正則表達式,通過Pattern提供的一系列方法可以對文本進行匹配查找。Pattern不能直接實例化,必須使用re.compile()進行構造。

2.complie方法

re正則表達式模塊包括一些常用的操作函數,比如complie()函數。其原型如下:

compile(pattern[,flags] ) 

該函數根據包含正則表達式的字符串創建模式對象,返回一個pattern對象。參數flags是匹配模式,可以使用按位或“|”表示同時生效,也可以在正則表達式字符串中指定。Pattern對象是不能直接實例化的,只能通過compile方法得到。

簡單舉個實例,使用正則表達式獲取字符串中的數字內容,如下所示:
  1. >>> import re
  2. >>> string="A1.45,b5,6.45,8.82"
  3. >>> regex = re.compile(r"\d+\.?\d*")
  4. >>> print regex.findall(string)
  5. ['1.45', '5', '6.45', '8.82']
  6. >>>

3.match方法

match方法是從字符串的pos下標處起開始匹配pattern,如果pattern結束時已經匹配,則返回一個Match對象;如果匹配過程中pattern無法匹配,或者匹配未結束就已到達endpos,則返回None。該方法原型如下:

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

參數string表示字符串;pos表示下標,pos和endpos的默認值分別爲0和len(string);參數flags用於編譯pattern時指定匹配模式。 


4.search方法

search方法用於查找字符串中可以匹配成功的子串。從字符串的pos下標處起嘗試匹配pattern,如果pattern結束時仍可匹配,則返回一個Match對象;若無法匹配,則將pos加1後重新嘗試匹配;直到pos=endpos時仍無法匹配則返回None。 函數原型如下:

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

參數string表示字符串;pos表示下標,pos和endpos的默認值分別爲0和len(string));參數flags用於編譯pattern時指定匹配模式。 

5.group和groups方法

group([group1, …])方法用於獲得一個或多個分組截獲的字符串,當它指定多個參數時將以元組形式返回。groups([default])方法以元組形式返回全部分組截獲的字符串,相當於調用group(1,2,…last)。default表示沒有截獲字符串的組以這個值替代,默認爲None。



二.正則表達式抓取網絡數據常見方法


在第三小節作者將介紹常用的正則表達式抓取網絡數據的一些技巧,這些技巧都是作者自然語言處理和數據抓取實際編程中的總結,可能不是很系統,但是也能給讀者提供一些抓取數據的思路以及解決實際的一些問題。

1.抓取標籤間的內容

HTML語言是採用標籤對的形式來編寫網站的,包括起始標籤和結束標籤,比如<head></head>、<tr></tr>、<script><script>等。下面講解抓取標籤對之間的文本內容。

(1) 抓取title標籤間的內容

首先爬取網頁的標題,採用的正則表達式爲'<title>(.*?)</title>',爬取百度標題代碼如下:

  1. # coding=utf-8
  2. import re
  3. import urllib
  4. url = "http://www.baidu.com/"
  5. content = urllib.urlopen(url).read()
  6. title = re.findall(r'<title>(.*?)</title>', content)
  7. print title[0]
  8. # 百度一下,你就知道

代碼調用urllib庫的urlopen()函數打開超鏈接,並借用正則表達式庫中的findall()函數尋找title標籤間的內容,由於findall()函數獲取所有滿足該正則表達式的文本,故輸出第一個值title[0]即可。下面是獲取標籤的另一種方法。

  1. pat = r'(?<=<title>).*?(?=</title>)'
  2. ex = re.compile(pat, re.M|re.S)
  3. obj = re.search(ex, content)
  4. title = obj.group()
  5. print title
  6. # 百度一下,你就知道


(2) 抓取超鏈接標籤間的內容
HTML中,<a href=URL></a>用於標識超鏈接,test03_08.py文件用於獲取完整的超鏈接和超鏈接<a>和</a>之間的內容。

  1. # coding=utf-8
  2. import re
  3. import urllib
  4. url = "http://www.baidu.com/"
  5. content = urllib.urlopen(url).read()
  6. #獲取完整超鏈接
  7. res = r"<a.*?href=.*?<\/a>"
  8. urls = re.findall(res, content)
  9. for u in urls:
  10. print unicode(u,'utf-8')
  11. #獲取超鏈接<a>和</a>之間內容
  12. res = r'<a .*?>(.*?)</a>'
  13. texts = re.findall(res, content, re.S|re.M)
  14. for t in texts:
  15. print unicode(t,'utf-8')

輸出結果部分內容如下所示,這裏如果直接輸出print u或print t可能會亂碼,需要調用函數unicode(u,'utf-8')進行轉碼。

  1. #獲取完整超鏈接
  2. <a href="http://news.baidu.com" name="tj_trnews" class="mnav">新聞</a>
  3. <a href="http://www.hao123.com" name="tj_trhao123" class="mnav">hao123</a>
  4. <a href="http://map.baidu.com" name="tj_trmap" class="mnav">地圖</a>
  5. <a href="http://v.baidu.com" name="tj_trvideo" class="mnav">視頻</a>
  6. ...
  7. #獲取超鏈接<a>和</a>之間內容
  8. 新聞
  9. hao123
  10. 地圖
  11. 視頻
  12. ...


(3) 抓取tr\td標籤間的內容
網頁中常用的佈局包括table佈局或div佈局,其中table表格佈局中常見的標籤包括tr、th和td,表格行爲tr(table row),表格數據爲td(table data),表格表頭th(table heading)。那麼如何抓取這些標籤之間的內容呢?下面代碼是獲取它們之間內容。

假設存在HTML代碼如下所示:

  1. <html>
  2. <head><title>表格</title></head>
  3. <body>
  4. <table border=1>
  5. <tr><th>學號</th><th>姓名</th></tr>
  6. <tr><td>1001</td><td>楊秀璋</td></tr>
  7. <tr><td>1002</td><td>嚴娜</td></tr>
  8. </table>
  9. </body>
  10. </html>

則爬取對應值的Python代碼如下:

  1. # coding=utf-8
  2. import re
  3. import urllib
  4. content = urllib.urlopen("test.html").read() #打開本地文件
  5. #獲取<tr></tr>間內容
  6. res = r'<tr>(.*?)</tr>'
  7. texts = re.findall(res, content, re.S|re.M)
  8. for m in texts:
  9. print m
  10. #獲取<th></th>間內容
  11. for m in texts:
  12. res_th = r'<th>(.*?)</th>'
  13. m_th = re.findall(res_th, m, re.S|re.M)
  14. for t in m_th:
  15. print t
  16. #直接獲取<td></td>間內容
  17. res = r'<td>(.*?)</td><td>(.*?)</td>'
  18. texts = re.findall(res, content, re.S|re.M)
  19. for m in texts:
  20. print m[0],m[1]

輸出結果如下,首先獲取tr之間的內容,然後再在tr之間內容中獲取<th>和</th>之間值,即“學號”、“姓名”,最後講述直接獲取兩個<td>之間的內容方法。

  1. >>>
  2. <th>學號</th><th>姓名</th>
  3. <td>1001</td><td>楊秀璋</td>
  4. <td>1002</td><td>嚴娜</td>
  5. 學號
  6. 姓名
  7. 1001 楊秀璋
  8. 1002 嚴娜
  9. >>>


2.抓取標籤中的參數

(1) 抓取超鏈接標籤的URL

HTML超鏈接的基本格式爲“<a href=URL>鏈接內容</a>”,現在需要獲取其中的URL鏈接地址,方法如下:

  1. # coding=utf-8
  2. import re
  3. content = '''
  4. <a href="http://news.baidu.com" name="tj_trnews" class="mnav">新聞</a>
  5. <a href="http://www.hao123.com" name="tj_trhao123" class="mnav">hao123</a>
  6. <a href="http://map.baidu.com" name="tj_trmap" class="mnav">地圖</a>
  7. <a href="http://v.baidu.com" name="tj_trvideo" class="mnav">視頻</a>
  8. '''
  9. res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
  10. urls = re.findall(res, content, re.I|re.S|re.M)
  11. for url in urls:
  12. print url

輸出內容如下:

  1. >>>
  2. http://news.baidu.com
  3. http://www.hao123.com
  4. http://map.baidu.com
  5. http://v.baidu.com
  6. >>>


(2) 抓取圖片超鏈接標籤的URL

HTML插入圖片使用標籤的基本格式爲“<img src=圖片地址 />”,則需要獲取圖片URL鏈接地址的方法如下:

  1. content = '''<img alt="Python" src="http://www..csdn.net/eastmount.jpg" />'''
  2. urls = re.findall('src="(.*?)"', content, re.I|re.S|re.M)
  3. print urls
  4. # ['http://www..csdn.net/eastmount.jpg']

其中圖片對應的超鏈接爲“http://www..csdn.net/eastmount.jpg”,這些資源通常存儲在服務器端,最後一個“/”後面的字段即爲資源的名稱,該圖片名稱爲“eastmount.jpg”。那麼如何獲取URL中最後一個參數呢?


(3) 獲取URL中最後一個參數

通常在使用Python爬取圖片過程中,會遇到圖片對應的URL最後一個字段通常用於命名圖片,如前面的“eastmount.jpg”,需要通過URL“/”後面的參數獲取圖片。

  1. content = '''<img alt="Python" src="http://www..csdn.net/eastmount.jpg" />'''
  2. urls = 'http://www..csdn.net/eastmount.jpg'
  3. name = urls.split('/')[-1]
  4. print name
  5. # eastmount.jpg

該段代碼表示採用字符“/”分割字符串,並且獲取最後一個獲取的值,即爲圖片名稱。


3.字符串處理及替換

在使用正則表達式爬取網頁文本時,通常需要調用find()函數找到指定的位置,再進行進一步爬取,比如獲取class屬性爲“infobox”的表格table,再進行定位爬取。

  1. start = content.find(r'<table class="infobox"') #起點位置
  2. end = content.find(r'</table>') #重點點位置
  3. infobox = text[start:end]
  4. print infobox

同時爬取過程中可能會爬取到無關變量,此時需要對無關內容進行過濾,這裏推薦使用replace函數和正則表達式進行處理。比如,爬取內容如下:

  1. # coding=utf-8
  2. import re
  3. content = '''
  4. <tr><td>1001</td><td>楊秀璋<br /></td></tr>
  5. <tr><td>1002</td><td>顏 娜</td></tr>
  6. <tr><td>1003</td><td><B>Python</B></td></tr>
  7. '''
  8. res = r'<td>(.*?)</td><td>(.*?)</td>'
  9. texts = re.findall(res, content, re.S|re.M)
  10. for m in texts:
  11. print m[0],m[1]

輸出如下所示:

  1. >>>
  2. 1001 楊秀璋<br />
  3. 1002 顏 娜
  4. 1003 <B>Python</B>
  5. >>>

此時需要過濾多餘字符串,如換行(<br />)、空格( )、加粗(<B></B>)。
過濾代碼如下:

  1. # coding=utf-8
  2. import re
  3. content = '''
  4. <tr><td>1001</td><td>楊秀璋<br /></td></tr>
  5. <tr><td>1002</td><td>顏 娜</td></tr>
  6. <tr><td>1003</td><td><B>Python</B></td></tr>
  7. '''
  8. res = r'<td>(.*?)</td><td>(.*?)</td>'
  9. texts = re.findall(res, content, re.S|re.M)
  10. for m in texts:
  11. value0 = m[0].replace('<br />', '').replace(' ', '')
  12. value1 = m[1].replace('<br />', '').replace(' ', '')
  13. if '<B>' in value1:
  14. m_value = re.findall(r'<B>(.*?)</B>', value1, re.S|re.M)
  15. print value0, m_value[0]
  16. else:
  17. print value0, value1

採用replace將字符串“<br />”或“' ”替換成空白,實現過濾,而加粗(<B></B>)需要使用正則表達式過濾,輸出結果如下:

  1. >>>
  2. 1001 楊秀璋
  3. 1002 顏娜
  4. 1003 Python
  5. >>>



三.實戰爬取個人博客實例

在講述了正則表達式、常用網絡數據爬取模塊、正則表達式爬取數據常見方法等內容之後,我們將講述一個簡單的正則表達式爬取網站的實例。這裏作者用正則表達式爬取作者的個人博客網站的簡單示例,獲取所需內容。
作者的個人網址http://www.eastmountyxz.com/”打開如下圖所示。


假設現在需要爬取的內容如下:
1.博客網址的標題(title)內容。
2.爬取所有圖片的超鏈接,比如爬取<img src=”xxx.jpg” />中的“xxx.jpg”。
3.分別爬取博客首頁中的四篇文章的標題、超鏈接及摘要內容,比如標題爲“再見北理工:憶北京研究生的編程時光”


1.分析過程

第一步 瀏覽器源碼定位

首先通過瀏覽器定位這些元素源代碼,發現它們之間的規律,這稱爲DOM樹文檔節點樹分析,找到所需爬取節點對應的屬性和屬性值,如圖3.6所示。


標題“再見北理工:憶北京研究生的編程時光”位於<div class=”essay”></div>節點下,它包括一個<h1></h1>記錄標題,一個<p></p>記錄摘要信息,即:

  1. <div class="essay">
  2. <h1 style="text-align:center">
  3. <a href="http://blog.csdn.net/eastmount/.../52201984">
  4. 再見北理工:憶北京研究生的編程時光
  5. </a>
  6. </h1>
  7. <p style="text-indent: 2em;">
  8. 兩年前,我本科畢業寫了這樣一篇文章:《 回憶自己的大學四年得與失 》,感慨了自己在北理軟院四年的所得所失;兩年後,我離開了帝都,回到了貴州家鄉,準備開啓一段新的教師生涯,在此也寫一篇文章紀念下吧!
  9. 還是那句話:這篇文章是寫給自己的,希望很多年之後,回想起自己北京的六年時光,也是美好的回憶。文章可能有點長,但希望大家像讀小說一樣耐心品讀,....
  10. </p>
  11. </div>

其餘三篇文章同樣爲<div class=”essay1”></div>、
<div class=”essay2”></div>和<div class=”essay3”></div>。


第二步 正則表達式爬取標題

網站的標題通常位於<head><title>...</title></head>之間,爬取博客網站的標題“秀璋學習天地”的方法是通過正則表達式“<title>(.*?)</title>”實現,代碼如下,首先通過urlopen()函數訪問博客網址,然後定義正則表達式爬取。如下圖所示:


第三步 正則表達式爬取所有圖片地址

由於HTML插入圖片標籤格式爲“<img src=圖片地址 />”,則使用正則表達式獲取圖片URL鏈接地址的方法如下,獲取以“src=”開頭,以雙引號結尾的內容即可。

  1. import re
  2. import urllib
  3. url = "http://www.eastmountyxz.com/"
  4. content = urllib.urlopen(url).read()
  5. urls = re.findall(r'src="(.*?)"', content)
  6. for url in urls:
  7. print url

輸出共顯示了6張圖片,但每張圖片省略了博客地址“http://www.eastmountyxz.com/”,增加相關地址則可以通過瀏覽器訪問,如“http://www.eastmountyxz.com/images/11.gif”。


第四步 正則表達式爬取博客內容

前面第一步講述瞭如何定位四篇文章的標題,第一篇文章位於<div class=”essay”></div>標籤之間,第二篇位於<div class=”essay1”></div>,依次類推。但是該HTML代碼存在一個錯誤:class屬性通常表示一類標籤,它們的值都應該是相同的,所以這四篇文章的class屬性都應該是“essay”,而nameid可以用來標識其唯一值。

這裏使用find()函數定位<div class=”essay”>開頭,</div>結尾,獲取它們之間的值。比如獲取第一篇文章的標題和超鏈接代碼如下:

  1. import re
  2. import urllib
  3. url = "http://www.eastmountyxz.com/"
  4. content = urllib.urlopen(url).read()
  5. start = content.find(r'<div class="essay">')
  6. end = content.find(r'<div class="essay1">')
  7. print content[start:end]

該部分代碼分爲三步驟:
  (1) 調用urllib庫的urlopen()函數打開博客地址,並讀取內容賦值給content變量。
  (2) 調用find()函數查找特定的內容,比如class屬性爲“essay”的div標籤,依次定位獲取開始和結束的位置。
  (3) 進行下一步分析,獲取源碼中的超鏈接和標題等內容。
定位這段內容之後,再通過正則表達式獲取具體內容,代碼如下:

  1. import re
  2. import urllib
  3. url = "http://www.eastmountyxz.com/"
  4. content = urllib.urlopen(url).read()
  5. start = content.find(r'<div class="essay">')
  6. end = content.find(r'<div class="essay1">')
  7. page = content[start:end]
  8. res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
  9. t1 = re.findall(res, page) #超鏈接
  10. print t1[0]
  11. t2 = re.findall(r'<a .*?>(.*?)</a>', page) #標題
  12. print t2[0]
  13. t3 = re.findall('<p style=.*?>(.*?)</p>', page, re.M|re.S) #摘要(
  14. print t3[0]

調用正則表達式分別獲取內容,由於爬取的段落(P)存在換行內容,所以需要加入re.M和re.S支持換行查找,最後輸出結果如下:

  1. >>>
  2. http://blog.csdn.net/eastmount/article/details/52201984
  3. 再見北理工:憶北京研究生的編程時光
  4. 兩年前,我本科畢業寫了這樣一篇文章:《 回憶自己的大學四年得與失 》,感慨了自己在北理軟院四年的所得所失;兩年後,我離開了帝都,回到了貴州家鄉,準備開啓一段新的教師生涯,在此也寫一篇文章紀念下吧!
  5. 還是那句話:這篇文章是寫給自己的,希望很多年之後,回想起自己北京的六年時光,也是美好的回憶。文章可能有點長,但希望大家像讀小說一樣耐心品讀,....
  6. >>>

2.代碼實現

完整代碼參考test03_10.py文件,代碼如下所示。

  1. #coding:utf-8
  2. import re
  3. import urllib
  4. url = "http://www.eastmountyxz.com/"
  5. content = urllib.urlopen(url).read()
  6. #爬取標題
  7. title = re.findall(r'<title>(.*?)</title>', content)
  8. print title[0]
  9. #爬取圖片地址
  10. urls = re.findall(r'src="(.*?)"', content)
  11. for url in urls:
  12. print url
  13. #爬取內容
  14. start = content.find(r'<div class="essay">')
  15. end = content.find(r'<div class="essay1">')
  16. page = content[start:end]
  17. res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
  18. t1 = re.findall(res, page) #超鏈接
  19. print t1[0]
  20. t2 = re.findall(r'<a .*?>(.*?)</a>', page) #標題
  21. print t2[0]
  22. t3 = re.findall('<p style=.*?>(.*?)</p>', page, re.M|re.S) #摘要(
  23. print t3[0]
  24. print ''
  25. start = content.find(r'<div class="essay1">')
  26. end = content.find(r'<div class="essay2">')
  27. page = content[start:end]
  28. res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
  29. t1 = re.findall(res, page) #超鏈接
  30. print t1[0]
  31. t2 = re.findall(r'<a .*?>(.*?)</a>', page) #標題
  32. print t2[0]
  33. t3 = re.findall('<p style=.*?>(.*?)</p>', page, re.M|re.S) #摘要(
  34. print t3[0]

輸出結果如圖所示。



通過上面的代碼,讀者會發現使用正則表達式爬取網站還是比較繁瑣,尤其是定位網頁節點時,後面將講述Python提供的常用第三方擴展包,利用這些包的函數進行定向爬取。
希望這篇文字對你有所幫助,尤其是剛接觸爬蟲的同學或是遇到類似問題的同學,更推薦大家使用BeautifulSoup、Selenium、Scrapy等庫來爬取數據。




總結:

貴州縱美路迢迢,

未負勞心此一遭。
搜得破書三四本,
也堪將去教爾曹。


這是我大學畢業離開北京時寫的詩,回想依然感慨萬分,當時放棄了互聯網月薪過萬的工資,親朋好友的勸阻,選擇回貴州任教,子承父志。剛來財大領了兩個月2800的工資,但內心始終都是愉悅的。


轉眼已工作一年多,自己也有了新的感悟。知道了有一些事情比事業重要得多,即使是最喜歡的教書育人,也可以放棄,似乎卻總讓你操心,確實不該。人,一方面需要牢記自己的初心,能堅持做一輩子喜歡的事真的很難,所以教書的我是幸運的; 另一方面也要學會say no,那時的秀璋才真正成長。

人生得一知己足矣,教育和工作都是根植於愛中。最近對不住很多人,加班太多了,都是深夜一兩點,中午和坐公交的短暫時間都用來學習了,但很多編程問題都還來不及解答,房子也沒關心,博客也來不及撰寫。唉,但想想她、親人、朋友和學生,我接着擦乾眼淚繼續前行,還有有你。這就是生活嗎?忙碌的你們也要注意休息哈。附一張上課圖片,忙取了。



(By:Eastmount 2017-10-19 清早9點  http://blog.csdn.net/eastmount/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章