[python爬蟲] 正規表示式使用技巧及爬取個人部落格例項

Eastmount發表於2017-10-18

這篇部落格是自己《資料探勘與分析》課程講到正規表示式爬蟲的相關內容,主要簡單介紹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 嚴娜
>>>


2.抓取標籤中的引數

(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”,而nameid可以用來標識其唯一值。

這裡使用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/

相關文章