這篇博客是自己《數據挖掘與分析》課程講到正則表達式爬蟲的相關內容,主要簡單介紹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方法得到。
簡單舉個實例,使用正則表達式獲取字符串中的數字內容,如下所示:
- >>> import re
- >>> string="A1.45,b5,6.45,8.82"
- >>> regex = re.compile(r"\d+\.?\d*")
- >>> print regex.findall(string)
- ['1.45', '5', '6.45', '8.82']
- >>>
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>',爬取百度標題代碼如下:
- # coding=utf-8
- import re
- import urllib
- url = "http://www.baidu.com/"
- content = urllib.urlopen(url).read()
- title = re.findall(r'<title>(.*?)</title>', content)
- print title[0]
- # 百度一下,你就知道
代碼調用urllib庫的urlopen()函數打開超鏈接,並借用正則表達式庫中的findall()函數尋找title標籤間的內容,由於findall()函數獲取所有滿足該正則表達式的文本,故輸出第一個值title[0]即可。下面是獲取標籤的另一種方法。
- pat = r'(?<=<title>).*?(?=</title>)'
- ex = re.compile(pat, re.M|re.S)
- obj = re.search(ex, content)
- title = obj.group()
- print title
- # 百度一下,你就知道
(2) 抓取超鏈接標籤間的內容
在HTML中,<a href=URL></a>用於標識超鏈接,test03_08.py文件用於獲取完整的超鏈接和超鏈接<a>和</a>之間的內容。
- # coding=utf-8
- import re
- import urllib
- url = "http://www.baidu.com/"
- content = urllib.urlopen(url).read()
-
- #獲取完整超鏈接
- res = r"<a.*?href=.*?<\/a>"
- urls = re.findall(res, content)
- for u in urls:
- print unicode(u,'utf-8')
-
- #獲取超鏈接<a>和</a>之間內容
- res = r'<a .*?>(.*?)</a>'
- texts = re.findall(res, content, re.S|re.M)
- for t in texts:
- print unicode(t,'utf-8')
輸出結果部分內容如下所示,這裏如果直接輸出print u或print t可能會亂碼,需要調用函數unicode(u,'utf-8')進行轉碼。
- #獲取完整超鏈接
- <a href="http://news.baidu.com" name="tj_trnews" class="mnav">新聞</a>
- <a href="http://www.hao123.com" name="tj_trhao123" class="mnav">hao123</a>
- <a href="http://map.baidu.com" name="tj_trmap" class="mnav">地圖</a>
- <a href="http://v.baidu.com" name="tj_trvideo" class="mnav">視頻</a>
- ...
- #獲取超鏈接<a>和</a>之間內容
- 新聞
- hao123
- 地圖
- 視頻
- ...
(3) 抓取tr\td標籤間的內容
網頁中常用的佈局包括table佈局或div佈局,其中table表格佈局中常見的標籤包括tr、th和td,表格行爲tr(table row),表格數據爲td(table data),表格表頭th(table heading)。那麼如何抓取這些標籤之間的內容呢?下面代碼是獲取它們之間內容。
假設存在HTML代碼如下所示:
- <html>
- <head><title>表格</title></head>
- <body>
- <table border=1>
- <tr><th>學號</th><th>姓名</th></tr>
- <tr><td>1001</td><td>楊秀璋</td></tr>
- <tr><td>1002</td><td>嚴娜</td></tr>
- </table>
- </body>
- </html>
則爬取對應值的Python代碼如下:
- # coding=utf-8
- import re
- import urllib
- content = urllib.urlopen("test.html").read() #打開本地文件
-
- #獲取<tr></tr>間內容
- res = r'<tr>(.*?)</tr>'
- texts = re.findall(res, content, re.S|re.M)
- for m in texts:
- print m
-
- #獲取<th></th>間內容
- for m in texts:
- res_th = r'<th>(.*?)</th>'
- m_th = re.findall(res_th, m, re.S|re.M)
- for t in m_th:
- print t
-
- #直接獲取<td></td>間內容
- res = r'<td>(.*?)</td><td>(.*?)</td>'
- texts = re.findall(res, content, re.S|re.M)
- for m in texts:
- print m[0],m[1]
輸出結果如下,首先獲取tr之間的內容,然後再在tr之間內容中獲取<th>和</th>之間值,即“學號”、“姓名”,最後講述直接獲取兩個<td>之間的內容方法。
- >>>
- <th>學號</th><th>姓名</th>
- <td>1001</td><td>楊秀璋</td>
- <td>1002</td><td>嚴娜</td>
-
- 學號
- 姓名
-
- 1001 楊秀璋
- 1002 嚴娜
- >>>
(1) 抓取超鏈接標籤的URL
HTML超鏈接的基本格式爲“<a href=URL>鏈接內容</a>”,現在需要獲取其中的URL鏈接地址,方法如下:
- # coding=utf-8
- import re
-
- content = '''
- <a href="http://news.baidu.com" name="tj_trnews" class="mnav">新聞</a>
- <a href="http://www.hao123.com" name="tj_trhao123" class="mnav">hao123</a>
- <a href="http://map.baidu.com" name="tj_trmap" class="mnav">地圖</a>
- <a href="http://v.baidu.com" name="tj_trvideo" class="mnav">視頻</a>
- '''
-
- res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
- urls = re.findall(res, content, re.I|re.S|re.M)
- for url in urls:
- print url
輸出內容如下:
- >>>
- http://news.baidu.com
- http://www.hao123.com
- http://map.baidu.com
- http://v.baidu.com
- >>>
(2) 抓取圖片超鏈接標籤的URL
HTML插入圖片使用標籤的基本格式爲“<img src=圖片地址 />”,則需要獲取圖片URL鏈接地址的方法如下:
- content = '''<img alt="Python" src="http://www..csdn.net/eastmount.jpg" />'''
- urls = re.findall('src="(.*?)"', content, re.I|re.S|re.M)
- print urls
- # ['http://www..csdn.net/eastmount.jpg']
其中圖片對應的超鏈接爲“http://www..csdn.net/eastmount.jpg”,這些資源通常存儲在服務器端,最後一個“/”後面的字段即爲資源的名稱,該圖片名稱爲“eastmount.jpg”。那麼如何獲取URL中最後一個參數呢?
(3) 獲取URL中最後一個參數
通常在使用Python爬取圖片過程中,會遇到圖片對應的URL最後一個字段通常用於命名圖片,如前面的“eastmount.jpg”,需要通過URL“/”後面的參數獲取圖片。
- content = '''<img alt="Python" src="http://www..csdn.net/eastmount.jpg" />'''
- urls = 'http://www..csdn.net/eastmount.jpg'
- name = urls.split('/')[-1]
- print name
- # eastmount.jpg
該段代碼表示採用字符“/”分割字符串,並且獲取最後一個獲取的值,即爲圖片名稱。
3.字符串處理及替換
在使用正則表達式爬取網頁文本時,通常需要調用find()函數找到指定的位置,再進行進一步爬取,比如獲取class屬性爲“infobox”的表格table,再進行定位爬取。
- start = content.find(r'<table class="infobox"') #起點位置
- end = content.find(r'</table>') #重點點位置
- infobox = text[start:end]
- print infobox
同時爬取過程中可能會爬取到無關變量,此時需要對無關內容進行過濾,這裏推薦使用replace函數和正則表達式進行處理。比如,爬取內容如下:
- # coding=utf-8
- import re
-
- content = '''
- <tr><td>1001</td><td>楊秀璋<br /></td></tr>
- <tr><td>1002</td><td>顏 娜</td></tr>
- <tr><td>1003</td><td><B>Python</B></td></tr>
- '''
-
- res = r'<td>(.*?)</td><td>(.*?)</td>'
- texts = re.findall(res, content, re.S|re.M)
- for m in texts:
- print m[0],m[1]
輸出如下所示:
- >>>
- 1001 楊秀璋<br />
- 1002 顏 娜
- 1003 <B>Python</B>
- >>>
此時需要過濾多餘字符串,如換行(<br />)、空格( )、加粗(<B></B>)。
過濾代碼如下:
- # coding=utf-8
- import re
-
- content = '''
- <tr><td>1001</td><td>楊秀璋<br /></td></tr>
- <tr><td>1002</td><td>顏 娜</td></tr>
- <tr><td>1003</td><td><B>Python</B></td></tr>
- '''
-
- res = r'<td>(.*?)</td><td>(.*?)</td>'
- texts = re.findall(res, content, re.S|re.M)
- for m in texts:
- value0 = m[0].replace('<br />', '').replace(' ', '')
- value1 = m[1].replace('<br />', '').replace(' ', '')
- if '<B>' in value1:
- m_value = re.findall(r'<B>(.*?)</B>', value1, re.S|re.M)
- print value0, m_value[0]
- else:
- print value0, value1
採用replace將字符串“<br />”或“' ”替換成空白,實現過濾,而加粗(<B></B>)需要使用正則表達式過濾,輸出結果如下:
- >>>
- 1001 楊秀璋
- 1002 顏娜
- 1003 Python
- >>>
三.實戰爬取個人博客實例
在講述了正則表達式、常用網絡數據爬取模塊、正則表達式爬取數據常見方法等內容之後,我們將講述一個簡單的正則表達式爬取網站的實例。這裏作者用正則表達式爬取作者的個人博客網站的簡單示例,獲取所需內容。
作者的個人網址“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>記錄摘要信息,即:
- <div class="essay">
- <h1 style="text-align:center">
- <a href="http://blog.csdn.net/eastmount/.../52201984">
- 再見北理工:憶北京研究生的編程時光
- </a>
- </h1>
- <p style="text-indent: 2em;">
- 兩年前,我本科畢業寫了這樣一篇文章:《 回憶自己的大學四年得與失 》,感慨了自己在北理軟院四年的所得所失;兩年後,我離開了帝都,回到了貴州家鄉,準備開啓一段新的教師生涯,在此也寫一篇文章紀念下吧!
- 還是那句話:這篇文章是寫給自己的,希望很多年之後,回想起自己北京的六年時光,也是美好的回憶。文章可能有點長,但希望大家像讀小說一樣耐心品讀,....
- </p>
- </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=”開頭,以雙引號結尾的內容即可。
- import re
- import urllib
-
- url = "http://www.eastmountyxz.com/"
- content = urllib.urlopen(url).read()
- urls = re.findall(r'src="(.*?)"', content)
- for url in urls:
- 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”,而name或id可以用來標識其唯一值。
這裏使用find()函數定位<div class=”essay”>開頭,</div>結尾,獲取它們之間的值。比如獲取第一篇文章的標題和超鏈接代碼如下:
- import re
- import urllib
- url = "http://www.eastmountyxz.com/"
- content = urllib.urlopen(url).read()
- start = content.find(r'<div class="essay">')
- end = content.find(r'<div class="essay1">')
- print content[start:end]
該部分代碼分爲三步驟:
(1) 調用urllib庫的urlopen()函數打開博客地址,並讀取內容賦值給content變量。
(2) 調用find()函數查找特定的內容,比如class屬性爲“essay”的div標籤,依次定位獲取開始和結束的位置。
(3) 進行下一步分析,獲取源碼中的超鏈接和標題等內容。
定位這段內容之後,再通過正則表達式獲取具體內容,代碼如下:
- import re
- import urllib
-
- url = "http://www.eastmountyxz.com/"
- content = urllib.urlopen(url).read()
- start = content.find(r'<div class="essay">')
- end = content.find(r'<div class="essay1">')
- page = content[start:end]
-
- res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
- t1 = re.findall(res, page) #超鏈接
- print t1[0]
- t2 = re.findall(r'<a .*?>(.*?)</a>', page) #標題
- print t2[0]
- t3 = re.findall('<p style=.*?>(.*?)</p>', page, re.M|re.S) #摘要(
- print t3[0]
調用正則表達式分別獲取內容,由於爬取的段落(P)存在換行內容,所以需要加入re.M和re.S支持換行查找,最後輸出結果如下:
- >>>
- http://blog.csdn.net/eastmount/article/details/52201984
- 再見北理工:憶北京研究生的編程時光
- 兩年前,我本科畢業寫了這樣一篇文章:《 回憶自己的大學四年得與失 》,感慨了自己在北理軟院四年的所得所失;兩年後,我離開了帝都,回到了貴州家鄉,準備開啓一段新的教師生涯,在此也寫一篇文章紀念下吧!
-
- 還是那句話:這篇文章是寫給自己的,希望很多年之後,回想起自己北京的六年時光,也是美好的回憶。文章可能有點長,但希望大家像讀小說一樣耐心品讀,....
- >>>
2.代碼實現
完整代碼參考test03_10.py文件,代碼如下所示。
- #coding:utf-8
- import re
- import urllib
-
- url = "http://www.eastmountyxz.com/"
- content = urllib.urlopen(url).read()
-
- #爬取標題
- title = re.findall(r'<title>(.*?)</title>', content)
- print title[0]
-
- #爬取圖片地址
- urls = re.findall(r'src="(.*?)"', content)
- for url in urls:
- print url
-
- #爬取內容
- start = content.find(r'<div class="essay">')
- end = content.find(r'<div class="essay1">')
- page = content[start:end]
- res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
- t1 = re.findall(res, page) #超鏈接
- print t1[0]
- t2 = re.findall(r'<a .*?>(.*?)</a>', page) #標題
- print t2[0]
- t3 = re.findall('<p style=.*?>(.*?)</p>', page, re.M|re.S) #摘要(
- print t3[0]
- print ''
-
- start = content.find(r'<div class="essay1">')
- end = content.find(r'<div class="essay2">')
- page = content[start:end]
- res = r"(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"
- t1 = re.findall(res, page) #超鏈接
- print t1[0]
- t2 = re.findall(r'<a .*?>(.*?)</a>', page) #標題
- print t2[0]
- t3 = re.findall('<p style=.*?>(.*?)</p>', page, re.M|re.S) #摘要(
- print t3[0]
輸出結果如圖所示。
通過上面的代碼,讀者會發現使用正則表達式爬取網站還是比較繁瑣,尤其是定位網頁節點時,後面將講述Python提供的常用第三方擴展包,利用這些包的函數進行定向爬取。
希望這篇文字對你有所幫助,尤其是剛接觸爬蟲的同學或是遇到類似問題的同學,更推薦大家使用BeautifulSoup、Selenium、Scrapy等庫來爬取數據。
總結:
貴州縱美路迢迢,
未負勞心此一遭。
搜得破書三四本,
也堪將去教爾曹。
這是我大學畢業離開北京時寫的詩,回想依然感慨萬分,當時放棄了互聯網月薪過萬的工資,親朋好友的勸阻,選擇回貴州任教,子承父志。剛來財大領了兩個月2800的工資,但內心始終都是愉悅的。
轉眼已工作一年多,自己也有了新的感悟。知道了有一些事情比事業重要得多,即使是最喜歡的教書育人,也可以放棄,似乎卻總讓你操心,確實不該。人,一方面需要牢記自己的初心,能堅持做一輩子喜歡的事真的很難,所以教書的我是幸運的; 另一方面也要學會say no,那時的秀璋才真正成長。
人生得一知己足矣,教育和工作都是根植於愛中。最近對不住很多人,加班太多了,都是深夜一兩點,中午和坐公交的短暫時間都用來學習了,但很多編程問題都還來不及解答,房子也沒關心,博客也來不及撰寫。唉,但想想她、親人、朋友和學生,我接着擦乾眼淚繼續前行,還有有你。這就是生活嗎?忙碌的你們也要注意休息哈。附一張上課圖片,忙取了。
(By:Eastmount 2017-10-19 清早9點 http://blog.csdn.net/eastmount/)