該筆記主要參考 cqc 大佬的 blog。
一、綜述
二、爬蟲基礎(chǔ)了解
三、urllib庫(kù)的基本使用
1、簡(jiǎn)單的爬取一個(gè)靜態(tài)網(wǎng)頁(yè)
response = urllib.request.urlopen("http://www.baidu.com")
print(response.read())
首先,注意,Python3中把 urllib 和 urllib2 合并了,這樣其實(shí)邏輯更清楚。
其次,我們調(diào)用了urlopen這個(gè)方法,一般接受三個(gè)參數(shù)
urlopen(url, data, timeout)
url即為URL
data是訪問URL時(shí)要傳送的數(shù)據(jù),默認(rèn)為None
timeout是設(shè)置超時(shí)時(shí)間,默認(rèn)為socket._GLOBAL_DEFAULT_TIMEOUT
返回值是一個(gè)response對(duì)象,read方法可以返回獲取到的網(wǎng)頁(yè)內(nèi)容.
2、POST 和 GET 數(shù)據(jù)傳送
(1)POST方式不會(huì)在鏈接上顯示所有參數(shù)
values = {"username":"XX@qq.com", "password":"XX"}
data = urllib.parse.urlencode(values).encode('utf-8')
url = "https://passport.csdn.net/account/login?from=http://my.csdn.net/my/mycsdn"
request = urllib.request.Request(url, data)
response = urllib.request.urlopen(request)
print(response.read())
??但是上述代碼可能登錄不進(jìn)去,因?yàn)镃SDN還有個(gè)流水號(hào)的字段,沒有設(shè)置全,比較復(fù)雜,這里只是為了說明登錄的原理。
(2)GET方式會(huì)在連接上顯示所有參數(shù),不安全
values = {"username":"XX@qq.com", "password":"XX"}
data = urllib.parse.urlencode(values).encode('utf-8')
url = "https://passport.csdn.net/account/login"
getUrl = url + "?" + data
request = urllib.request.Request(url, data)
response = urllib.request.urlopen(request)
print(response.read())
四、urllib的高級(jí)用法
這些東西在Chrome瀏覽器的“檢查/F12”中都可以看到。
1、設(shè)置 Headers
(1)有些網(wǎng)站不會(huì)同意程序直接用上面的方式進(jìn)行訪問,如果識(shí)別有問題,那么站點(diǎn)根本不會(huì)響應(yīng),所以為了完全模擬瀏覽器的工作,我們需要設(shè)置一些 Headers 的屬性。
import urllib
url = "http://www.server.com/login"
user_agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
values = {"username":"XXX", "password":"XXX"}
headers = {"User-Agent":user_agent}
data = urllib.parse.urlencode(values)
request = urllib.request.Request(url, data, headers)
response = urllib.request.urlopen(request)
page = response.read()
(2)另外,我們還有對(duì)付“反盜鏈”的方式。對(duì)付防盜鏈,服務(wù)器會(huì)識(shí)別 headers 中的 referer 是不是它自己,如果不是,有的服務(wù)器不會(huì)響應(yīng),所以我們還可以在 headers 中加入 referer。
headers = {"User-Agent":user_agent, "Referer":"https://www.zhihu.com/question/35120624"}
同上面的方法,在傳送請(qǐng)求時(shí)把 headers 傳入 Request 參數(shù)里,這樣就能應(yīng)付防盜鏈了。
(3)headers 的其他一些屬性,下面的需要特別注意一下:
User-Agent : 有些服務(wù)器或 Proxy 會(huì)通過該值來判斷是否是瀏覽器發(fā)出的請(qǐng)求
Content-Type : 在使用 REST 接口時(shí),服務(wù)器會(huì)檢查該值,用來確定 HTTP Body 中的內(nèi)容該怎樣解析。
application/xml : 在 XML RPC,如 RESTful/SOAP 調(diào)用時(shí)使用
application/json : 在 JSON RPC 調(diào)用時(shí)使用
application/x-www-form-urlencoded : 瀏覽器提交 Web 表單時(shí)使用
在使用服務(wù)器提供的RESTful或SOAP服務(wù)時(shí),Content-Type設(shè)置錯(cuò)誤會(huì)導(dǎo)致服務(wù)器拒絕服務(wù)。
其他的有必要的可以審查瀏覽器的headers內(nèi)容,在構(gòu)建時(shí)寫入同樣的數(shù)據(jù)即可。
2、Proxy(代理)的設(shè)置
import urllib
enable_proxy = True
proxy_handler = urllib.request.ProxyHandler({'sock5': 'localhost:1080'})
#靜覓的做法,運(yùn)行錯(cuò)誤
#proxy_handler = urllib.request.ProxyHandler({"http":"http://some-proxy.com:8080"})
null_proxy_handler = urllib.request.ProxyHandler({})
if enable_proxy:
opener = urllib.request.build_opener(proxy_handler)
else:
opener = urllib.request.build_opener(null_proxy_handler)
urllib.request.install_opener(opener)
page = urllib.request.urlopen("https://www.baidu.com").read().decode("utf8")
print(page)
3、Timeout設(shè)置
timeout的設(shè)置,可以設(shè)置等待多久超時(shí),為了解決一些網(wǎng)站實(shí)在響應(yīng)過慢而造成的影響。
response = urllib.request.urlopen('http://www.baidu.com', timeout=10)
response = urllib.request.urlopen('http://www.baidu.com',data, 10)
4、使用 HTTP 的 PUT 和 DELETE 方法
http協(xié)議有六種請(qǐng)求方法,get,head,put,delete,post,options,我們有時(shí)候需要用到PUT方式或者DELETE方式請(qǐng)求。
PUT:這個(gè)方法比較少見。HTML表單也不支持這個(gè)。本質(zhì)上來講, PUT和POST極為相似,都是向服務(wù)器發(fā)送數(shù)據(jù),但它們之間有一個(gè)重要區(qū)別,PUT通常指定了資源的存放位置,而POST則沒有,POST的數(shù)據(jù)存放位置由服務(wù)器自己決定。
DELETE:刪除某一個(gè)資源?;旧线@個(gè)也很少見,不過還是有一些地方比如amazon的S3云服務(wù)里面就用的這個(gè)方法來刪除資源。
如果要使用HTTP PUT和DELETE,只能使用比較低層的 httplib 庫(kù)。雖然如此,我們還是能通過下面的方式,使 urllib 能夠發(fā)出 PUT 或DELETE 的請(qǐng)求,不過用的次數(shù)的確是少,在這里提一下。
import urllib
request = urllib.request.Request(uri, data=data)
request.get_method = lambda: 'PUT' # or 'DELETE'
response = urllib.request.urlopen(request)
5、使用DebugLog
可以通過下面的方法把 Debug Log 打開,這樣收發(fā)包的內(nèi)容就會(huì)在屏幕上打印出來,方便調(diào)試,這個(gè)也不太常用,僅提一下。
import urllib
httpHandler = urllib.request.HTTPHandler(debuglevel=1)
httpsHandler = urllib.request.HTTPSHandler(debuglevel=1)
opener = urllib.request.build_opener(httpHandler, httpsHandler)
urllib.request.install_opener(opener)
response = urllib.request.urlopen('http://www.baidu.com')
以上便是一部分高級(jí)特性,前三個(gè)是重要內(nèi)容。后面還有cookies的設(shè)置及異常的處理。
五、URLError的異常處理
1、URLError
URLError可能產(chǎn)生的原因:
網(wǎng)絡(luò)無連接,即本機(jī)無法上網(wǎng)
連接不到特定的服務(wù)器
服務(wù)器不存在
在代碼中,我們需要用try-except語(yǔ)句來包圍并捕獲相應(yīng)的異常。
import urllib
request = urllib.request.Request("https://www.baibai.com")
try:
urllib.request.urlopen(request)
except urllib.request.URLError as e:
print(e.reason)
我們利用了 urlopen方法訪問了一個(gè)不存在的網(wǎng)址,運(yùn)行結(jié)果如下:
[Errno 11004] getaddrinfo failed
它說明了錯(cuò)誤代號(hào)是11004,錯(cuò)誤原因是 getaddrinfo failed。
2、HTTPError
HTTPError 是 URLError 的子類,在你利用 urlopen 方法發(fā)出一個(gè)請(qǐng)求時(shí),服務(wù)器上都會(huì)對(duì)應(yīng)一個(gè)應(yīng)答對(duì)象 response,其中它包含一個(gè)數(shù)字”狀態(tài)碼”。舉個(gè)例子,假如 response 是一個(gè)”重定向”,需定位到別的地址獲取文檔,urllib 將對(duì)此進(jìn)行處理。
其他不能處理的,urlopen 會(huì)產(chǎn)生一個(gè) HTTPError,對(duì)應(yīng)相應(yīng)的狀態(tài)嗎,HTTP 狀態(tài)碼表示 HTTP 協(xié)議所返回的響應(yīng)的狀態(tài)。下面將狀態(tài)碼歸結(jié)如下:
100:繼續(xù) 客戶端應(yīng)當(dāng)繼續(xù)發(fā)送請(qǐng)求。客戶端應(yīng)當(dāng)繼續(xù)發(fā)送請(qǐng)求的剩余部分,或者如果請(qǐng)求已經(jīng)完成,忽略這個(gè)響應(yīng)。
101: 轉(zhuǎn)換協(xié)議 在發(fā)送完這個(gè)響應(yīng)最后的空行后,服務(wù)器將會(huì)切換到在Upgrade 消息頭中定義的那些協(xié)議。只有在切換新的協(xié)議更有好處的時(shí)候才應(yīng)該采取類似措施。
102:繼續(xù)處理 由WebDAV(RFC 2518)擴(kuò)展的狀態(tài)碼,代表處理將被繼續(xù)執(zhí)行。
200:請(qǐng)求成功 處理方式:獲得響應(yīng)的內(nèi)容,進(jìn)行處理
201:請(qǐng)求完成,結(jié)果是創(chuàng)建了新資源。新創(chuàng)建資源的URI可在響應(yīng)的實(shí)體中得到 處理方式:爬蟲中不會(huì)遇到
202:請(qǐng)求被接受,但處理尚未完成 處理方式:阻塞等待
204:服務(wù)器端已經(jīng)實(shí)現(xiàn)了請(qǐng)求,但是沒有返回新的信 息。如果客戶是用戶代理,則無須為此更新自身的文檔視圖。 處理方式:丟棄
300:該狀態(tài)碼不被HTTP/1.0的應(yīng)用程序直接使用, 只是作為3XX類型回應(yīng)的默認(rèn)解釋。存在多個(gè)可用的被請(qǐng)求資源。 處理方式:若程序中能夠處理,則進(jìn)行進(jìn)一步處理,如果程序中不能處理,則丟棄
301:請(qǐng)求到的資源都會(huì)分配一個(gè)永久的URL,這樣就可以在將來通過該URL來訪問此資源 處理方式:重定向到分配的URL
302:請(qǐng)求到的資源在一個(gè)不同的URL處臨時(shí)保存 處理方式:重定向到臨時(shí)的URL
304:請(qǐng)求的資源未更新 處理方式:丟棄
400:非法請(qǐng)求 處理方式:丟棄
401:未授權(quán) 處理方式:丟棄
403:禁止 處理方式:丟棄
404:沒有找到 處理方式:丟棄
500:服務(wù)器內(nèi)部錯(cuò)誤 服務(wù)器遇到了一個(gè)未曾預(yù)料的狀況,導(dǎo)致了它無法完成對(duì)請(qǐng)求的處理。一般來說,這個(gè)問題都會(huì)在服務(wù)器端的源代碼出現(xiàn)錯(cuò)誤時(shí)出現(xiàn)。
501:服務(wù)器無法識(shí)別 服務(wù)器不支持當(dāng)前請(qǐng)求所需要的某個(gè)功能。當(dāng)服務(wù)器無法識(shí)別請(qǐng)求的方法,并且無法支持其對(duì)任何資源的請(qǐng)求。
502:錯(cuò)誤網(wǎng)關(guān) 作為網(wǎng)關(guān)或者代理工作的服務(wù)器嘗試執(zhí)行請(qǐng)求時(shí),從上游服務(wù)器接收到無效的響應(yīng)。
503:服務(wù)出錯(cuò) 由于臨時(shí)的服務(wù)器維護(hù)或者過載,服務(wù)器當(dāng)前無法處理請(qǐng)求。這個(gè)狀況是臨時(shí)的,并且將在一段時(shí)間以后恢復(fù)。
HTTPError 實(shí)例產(chǎn)生后會(huì)有一個(gè) code 屬性,這就是是服務(wù)器發(fā)送的相關(guān)錯(cuò)誤號(hào)。
因?yàn)?urllib 可以為你處理重定向,也就是 3 開頭的代號(hào)可以被處理,并且 100-299 范圍的號(hào)碼指示成功,所以你只能看到 400-599 的錯(cuò)誤號(hào)碼。
下面我們寫一個(gè)例子來感受一下,捕獲的異常是 HTTPError,它會(huì)帶有一個(gè) code 屬性,就是錯(cuò)誤代號(hào),另外我們又打印了 reason 屬性,這是它的父類 URLError 的屬性:
import urllib
request = urllib.request.Request('http://blog.csdn.net/cqc')
try:
urllib.request.urlopen(request)
except urllib.request.HTTPError as e:
print(e.code)
print(e.reason)
運(yùn)行結(jié)果:錯(cuò)誤代號(hào)是404,錯(cuò)誤原因是Not Found。
我們知道,HTTPError的父類是URLError,根據(jù)編程經(jīng)驗(yàn),父類的異常應(yīng)當(dāng)寫到子類異常的后面,如果子類捕獲不到,那么可以捕獲父類的異常,所以上述的代碼可以這么改寫:
import urllib
request = urllib.request.Request('http://blog.csdn.net/ccc')
try:
urllib.request.urlopen(request)
except urllib.error.HTTPError as e:
print("HTTPError")
print(e.code, e.reason)
except urllib.error.URLError as e:
print("URLError")
print(e.code, e.reason)
else:
print("OK")
另外,還可以加入 hasattr屬性提前對(duì)屬性進(jìn)行判斷,代碼改寫如下:
import urllib
request = urllib.request.Request('[https://www.baibai.com](https://www.baibai.com/)')
try:
urllib.request.urlopen(request)
except urllib.error.URLError as e:
if hasattr(e, "reason"):
print(e.reason)
else:
print("OK")
首先對(duì)異常的屬性進(jìn)行判斷,以免出現(xiàn)屬性輸出報(bào)錯(cuò)的現(xiàn)象。
六、Cookie 的使用
??Cookie,指某些網(wǎng)站為了辨別用戶身份、進(jìn)行session跟蹤而儲(chǔ)存在用戶本地終端上的數(shù)據(jù)(通常經(jīng)過加密)。
??比如說有些網(wǎng)站需要登錄后才能訪問某個(gè)頁(yè)面,在登錄前,你想抓取某個(gè)頁(yè)面內(nèi)容是不允許的。那么我們可以利用Urllib庫(kù)保存我們登錄的Cookie,然后再抓取其他頁(yè)面就達(dá)到目的了。
1、Opener
??當(dāng)你獲取一個(gè)URL你使用一個(gè)opener(一個(gè)urllib.OpenerDirector的實(shí)例)。在前面,我們都是使用的默認(rèn)的opener,也就是urlopen。它是一個(gè)特殊的opener,可以理解成opener的一個(gè)特殊實(shí)例,傳入的參數(shù)僅僅是url,data,timeout。
??如果我們需要用到Cookie,只用這個(gè)opener是不能達(dá)到目的的,所以我們需要?jiǎng)?chuàng)建更一般的opener來實(shí)現(xiàn)對(duì)Cookie的設(shè)置。
2、Cookielib
??cookielib模塊的主要作用是提供可存儲(chǔ) cookie 的對(duì)象,以便于與 urllib 模塊配合使用來訪問 Internet 資源。Cookielib 模塊非常強(qiáng)大,我們可以利用本模塊的 CookieJar 類的對(duì)象來捕獲 cookie 并在后續(xù)連接請(qǐng)求時(shí)重新發(fā)送,比如可以實(shí)現(xiàn)模擬登錄功能。該模塊主要的對(duì)象有 CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。
它們的關(guān)系:CookieJar --派生--> FileCookieJar --派生--> MozillaCookieJar和LWPCookieJar
(1)獲取 Cookie 保存到變量
首先,我們先利用 CookieJar 對(duì)象實(shí)現(xiàn)獲取 cookie的功能,存儲(chǔ)到變量中:
import urllib
import http.cookiejar as hc
#聲明一個(gè)CookieJar對(duì)象實(shí)例來保存cookie
cookie = hc.CookieJar()
#利用urllib庫(kù)的HTTPCookieProcessor對(duì)象來創(chuàng)建cookie處理器
handler=urllib.request.HTTPCookieProcessor(cookie)
#通過handler來構(gòu)建opener
opener = urllib.request.build_opener(handler)
#此處的open方法同urllib的urlopen方法,也可以傳入request
response = opener.open('http://www.baidu.com')
for item in cookie:
print("name = " +item.name)
print("value= " +item.value)
每次的運(yùn)行結(jié)果中的 value 都不同。某次的運(yùn)行結(jié)果為:
name = BAIDUID
value= 5A3083717227D67BF0691E0B37F6E68F:FG=1
name = BIDUPSID
value= 5A3083717227D67BF0691E0B37F6E68F
name = H_PS_PSSID
value= 26524_1425_27212_21120_18559_27244_22158
name = PSTM
value= 1543115615
name = delPer
value= 0
name = BDSVRTM
value= 0
name = BD_HOME
value= 0
(2)保存 Cookie 到文件
??如果想要將 cookie 保存到文件中,就要用到 FileCookieJar 這個(gè)對(duì)象了,在這里我們使用它的子類MozillaCookieJar來實(shí)現(xiàn) Cookie 的保存。
import urllib
import http.cookiejar as hc
#設(shè)置保存cookie的文件,同級(jí)目錄下的cookie.txt
filename = 'cookie.txt'
cookie = hc.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response= opener.open("http://www.baidu.com")
#保存cookie到文件
cookie.save(ignore_discard = True, ignore_expires = True)
每次運(yùn)行結(jié)果都不一樣。某次運(yùn)行后,文件中的內(nèi)容是:
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This is a generated file! Do not edit.
.baidu.com TRUE / FALSE 3690601416 BAIDUID D7FFD9F3ECD41F5CF83AC79018247617:FG=1
.baidu.com TRUE / FALSE 3690601416 BIDUPSID D7FFD9F3ECD41F5CF83AC79018247617
.baidu.com TRUE / FALSE H_PS_PSSID 1422_21106_26350_27244_27508
.baidu.com TRUE / FALSE 3690601416 PSTM 1543117761
.baidu.com TRUE / FALSE delPer 0
www.baidu.com FALSE / FALSE BDSVRTM 0
www.baidu.com FALSE / FALSE BD_HOME 0
關(guān)于最后save方法的兩個(gè)參數(shù)在此說明一下。官方解釋如下:
ignore_discard: save even cookies set to be discarded.
ignore_expires: save even cookies that have expiredThe file is overwritten if it already exists
??由此可見,ignore_discard的意思是即使 cookies 將被丟棄也將它保存下來,ignore_expires的意思是如果在該文件中 cookies 已經(jīng)存在,則覆蓋原文件寫入,在這里,我們將這兩個(gè)全部設(shè)置為 True。
(3)從文件中獲取 Cookie 并訪問
??我們已經(jīng)做到把Cookie保存到文件中了,如果以后想使用,可以利用下面的方法來讀取cookie并訪問網(wǎng)站:
import urllib
import http.cookiejar as hc
cookie = hc.MozillaCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
#創(chuàng)建請(qǐng)求的request
request = urllib.request.Request("http://www.baidu.com")
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open(request)
print(response.read())
??設(shè)想,如果我們的 cookie.txt 文件中保存的是某人登錄百度的cookie,那么我們提取出這個(gè)cookie文件內(nèi)容,就可以用以上方法模擬這個(gè)人的賬號(hào)登錄百度。
(4)利用 cookie 模擬網(wǎng)站登錄
??原理是:創(chuàng)建一個(gè)帶有 cookie 的 opener,在訪問登錄的 URL 時(shí),將登錄后的 cookie 保存下來,然后利用這個(gè) cookie 來訪問其他網(wǎng)址。如登錄之后才能查看的成績(jī)查詢呀,本校的課堂在線啊等等網(wǎng)址,模擬登錄就這么實(shí)現(xiàn)啦!
以我們學(xué)校的教育系統(tǒng)為例,利用 cookie 實(shí)現(xiàn)模擬登錄,并將 cookie 信息保存到文本文件中,來感受一下 cookie 大法吧:
import urllib
import http.cookiejar as hc
filename = 'cookie.txt'
cookie = hc.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
postdata = urllib.parse.urlencode({'username':'XXX',
'password':'XXX'}).encode('utf8')
#登錄教務(wù)系統(tǒng)的 URL
loginURL = "http://my.hhu.edu.cn/login.portal"
#模擬登錄,并把 cookie 保存到變量
result = opener.open(loginURL, postdata)
#保存 cookie 到 cookie.txt 中
cookie.save(ignore_discard=True, ignore_expires = True)
#利用 cookie 請(qǐng)求訪問另一個(gè)網(wǎng)址,此網(wǎng)址是本校課堂在線
ktzxURL = "http://ktzx.hhu.edu.cn/portal/"
#請(qǐng)求訪問該網(wǎng)址,并將讀取到的內(nèi)容保存至文件中
result2 = opener.open(ktzxURL)
with open("read.txt", "wb") as f:
f.write(result2.read())
現(xiàn)在可以順利獲取網(wǎng)站信息了,接下來就是把網(wǎng)站里面有效內(nèi)容提取出來,下一節(jié)會(huì)會(huì)正則表達(dá)式吧!
七、正則表達(dá)式
1、了解正則表達(dá)式
??正則表達(dá)式是對(duì)字符串操作的一種邏輯公式,就是用事先定義好的一些特定字符、及這些特定字符的組合,組成一個(gè)規(guī)則字符串,這個(gè)規(guī)則字符串用來表達(dá)對(duì)字符串的一種過濾邏輯。
正則表達(dá)式的大致匹配過程是:
- 依次拿出表達(dá)式和文本中的字符比較,
- 如果每一個(gè)字符都能匹配,則匹配成功;一旦有匹配不成功的字符則匹配失敗。
- 如果表達(dá)式中有量詞或邊界,這個(gè)過程會(huì)稍微有一些不同。
2、正則表達(dá)式的語(yǔ)法規(guī)則

3、正則表達(dá)式相關(guān)注解
(1)數(shù)量詞的貪婪模式與非貪婪模式
??正則表達(dá)式通常用于在文本中查找匹配的字符串。Python里數(shù)量詞默認(rèn)是貪婪的(在少數(shù)語(yǔ)言里也可能是默認(rèn)非貪婪),總是嘗試匹配盡可能多的字符;非貪婪的則相反,總是嘗試匹配盡可能少的字符。例如:正則表達(dá)式 ab* 如果用于查找 abbbc,將找到 abbb。而如果使用非貪婪的數(shù)量詞 ab*?,將找到 a。
注:我們一般使用非貪婪模式來提取。
(2)反斜杠問題
??與大多數(shù)編程語(yǔ)言相同,正則表達(dá)式里使用\作為轉(zhuǎn)義字符,這就可能造成反斜杠困擾。假如你需要匹配文本中的字符\,那么使用編程語(yǔ)言表示的正則表達(dá)式里將需要 4 個(gè)反斜杠\\\\:前兩個(gè)和后兩個(gè)分別用于在編程語(yǔ)言里轉(zhuǎn)義成反斜杠,轉(zhuǎn)換成兩個(gè)反斜杠后再在正則表達(dá)式里轉(zhuǎn)義成一個(gè)反斜杠。
Python里的原生字符串很好地解決了這個(gè)問題,這個(gè)例子中的正則表達(dá)式可以使用r"\\"表示。同樣,匹配一個(gè)數(shù)字的"\d"可以寫成r"\d"。有了原生字符串,媽媽也不用擔(dān)心是不是漏寫了反斜杠,寫出來的表達(dá)式也更直觀勒。
4、Python Re模塊
(1) re.compile(string[,flag])
#返回pattern對(duì)象
re.compile(string[,flag])
其中,flag是匹配模式,取值可以使用按位或運(yùn)算符|表示同時(shí)生效,比如 re.I | re.M??蛇x值有:
? re.I(全拼:IGNORECASE): 忽略大小寫(括號(hào)內(nèi)是完整寫法,下同)
? re.M(全拼:MULTILINE): 多行模式,改變'^'和'$'的行為(參見上圖)
? re.S(全拼:DOTALL): 點(diǎn)任意匹配模式,改變'.'的行為
? re.L(全拼:LOCALE): 使預(yù)定字符類 \w \W \b \B \s \S 取決于當(dāng)前區(qū)域設(shè)定
? re.U(全拼:UNICODE): 使預(yù)定字符類 \w \W \b \B \s \S \d \D 取決于unicode定義的字符屬性
? re.X(全拼:VERBOSE): 詳細(xì)模式。這個(gè)模式下正則表達(dá)式可以是多行,忽略空白字符,并可以加入注釋。
注:以下七個(gè)方法中的 flags 同樣是代表匹配模式的意思,如果在 pattern 生成時(shí)已經(jīng)指明了 flags,那么在下面的方法中就不需要傳入這個(gè)參數(shù)了。
(2)re.match(pattern, string[, flags])
??這個(gè)方法將會(huì)從 string(我們要匹配的字符串)的開頭開始,嘗試匹配 pattern,一直向后匹配。如果遇到無法匹配的字符,或者匹配未結(jié)束已經(jīng)到達(dá) string 的末尾,都會(huì)返回 None,表示匹配失敗。否則匹配 pattern 成功,同時(shí)匹配終止,不再對(duì) string 向后匹配。
import re
# 將正則表達(dá)式編譯成Pattern對(duì)象,注意hello前面的r的意思是“原生字符串”
pattern = re.compile(r'hello')
# 使用re.match匹配文本,獲得匹配結(jié)果,無法匹配時(shí)將返回None
res1 = re.match(pattern, 'hello')
res2 = re.match(pattern, 'helloo world')
res3 = re.match(pattern, 'helo world')
res4 = re.match(pattern, 'hello world')
if res1:
print(res1.group())
else:
print("1匹配失敗")
if res2:
print(res2.group())
else:
print("2匹配失敗")
if res3:
print(res3.group())
else:
print("3匹配失敗")
if res4:
print(res4.group())
else:
print("4匹配失敗")
運(yùn)行結(jié)果為:
hello
hello
3匹配失敗
hello
??我們還看到最后打印出了 result.group(),這個(gè)是什么意思呢?下面我們說一下關(guān)于 match 對(duì)象的的屬性和方法。
??Match 對(duì)象是一次匹配的結(jié)果,包含了很多關(guān)于此次匹配的信息,可以使用 Match 提供的可讀屬性或方法來獲取這些信息。
屬性:
1.string: 匹配時(shí)使用的文本。
2.re: 匹配時(shí)使用的Pattern對(duì)象。
3.pos: 文本中正則表達(dá)式開始搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數(shù)相同。
4.endpos: 文本中正則表達(dá)式結(jié)束搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數(shù)相同。
5.lastindex: 最后一個(gè)被捕獲的分組在文本中的索引。如果沒有被捕獲的分組,將為None。
6.lastgroup: 最后一個(gè)被捕獲的分組的別名。如果這個(gè)分組沒有別名或者沒有被捕獲的分組,將為None。
方法:
1.group([group1, …]):
獲得一個(gè)或多個(gè)分組截獲的字符串;指定多個(gè)參數(shù)時(shí)將以元組形式返回。group1可以使用編號(hào)也可以使用別名;編號(hào)0代表整個(gè)匹配的子串;不填寫參數(shù)時(shí),返回group(0);沒有截獲字符串的組返回None;截獲了多次的組返回最后一次截獲的子串。
2.groups([default]):
以元組形式返回全部分組截獲的字符串。相當(dāng)于調(diào)用group(1,2,…last)。default表示沒有截獲字符串的組以這個(gè)值替代,默認(rèn)為None。
3.groupdict([default]):
返回以有別名的組的別名為鍵、以該組截獲的子串為值的字典,沒有別名的組不包含在內(nèi)。default含義同上。
4.start([group]):
返回指定的組截獲的子串在string中的起始索引(子串第一個(gè)字符的索引)。group默認(rèn)值為0。
5.end([group]):
返回指定的組截獲的子串在string中的結(jié)束索引(子串最后一個(gè)字符的索引+1)。group默認(rèn)值為0。
6.span([group]):
返回(start(group), end(group))。
7.expand(template):
將匹配到的分組代入template中然后返回。template中可以使用\id或\g、\g引用分組,但不能使用編號(hào)0。\id與\g是等價(jià)的;但\10將被認(rèn)為是第10個(gè)分組,如果你想表達(dá)\1之后是字符’0’,只能使用\g0。
下面我們用一個(gè)例子來體會(huì)一下:
import re
# 匹配如下內(nèi)容:?jiǎn)卧~+空格+單詞+任意字符
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')
print("m.string: ", m.string)
print("m.re : ", m.re)
print("m.pos : ", m.pos)
print("m.endpos: ", m.endpos)
print("m.lastindex : ", m.lastindex)
print("m.lastgroup : ", m.lastgroup)
print("m.group() : ", m.group())
print("m.group(1,2) : ", m.group(1,2))
print("m.groups() : ", m.groups())
print("m.groupdict(): ", m.groupdict())
print("m.start(2) : ", m.start(2))
print("m.end(2) : ", m.end(2))
print("m.span(2) : ", m.span(2))
print(r"m.expand(r'\g \g\g'): ", m.expand(r'\2 \1\3'))
運(yùn)行結(jié)果:
m.string: hello world!
m.re : re.compile('(\\w+) (\\w+)(?P<sign>.*)')
m.pos : 0
m.endpos: 12
m.lastindex : 3
m.lastgroup : sign
m.group() : hello world!
m.group(1,2) : ('hello', 'world')
m.groups() : ('hello', 'world', '!')
m.groupdict(): {'sign': '!'}
m.start(2) : 6
m.end(2) : 11
m.span(2) : (6, 11)
m.expand(r'\g \g\g'): world hello!
(3)re.search(pattern, string[, flags])
??search 方法與 match 方法極其類似,區(qū)別在于match()只檢測(cè) re 是不是在 string 的開始位置匹配,search()會(huì)掃描整個(gè) string 查找匹配。search()的返回對(duì)象和 match()返回對(duì)象的方法和屬性一樣。我們用一個(gè)例子感受一下:
import re
pattern = re.compile(r'world')
match = re.match(pattern, "hello world!")
search = re.search(pattern, "hello world!")
if match:
print("match 成功:" + match.group())
if search:
print("search 成功:" + search.group())
#運(yùn)行結(jié)果:search 成功:world
(4)re.split(pattern, string[, maxsplit])
??按照能夠匹配的子串將string分割后返回列表。maxsplit 用于指定最大分割次數(shù),不指定將全部分割。
import re
pattern = re.compile(r'\d+')
print(re.split(pattern, 'ont1two2three3four4'))
#運(yùn)行結(jié)果:['ont', 'two', 'three', 'four', '']
(5)re.findall(pattern, string[, flags])
import re
pattern = re.compile(r'\d+')
print(re.findall(pattern,'one1two2three3four4'))
#運(yùn)行結(jié)果:['1', '2', '3', '4']
(6)re.finditer(pattern, string[, flags])
??搜索string,返回一個(gè)順序訪問每一個(gè)匹配結(jié)果(Match對(duì)象)的迭代器。
import re
pattern = re.compile(r'\d+')
for m in re.finditer(pattern, 'one1two2three3four4'):
print(m.group(), end = ' ')
運(yùn)行結(jié)果:1 2 3 4
(7)re.sub(pattern, repl, string[, count])
使用 repl 替換 string 中每一個(gè)匹配的子串后返回替換后的字符串。
- 當(dāng) repl 是一個(gè)字符串時(shí),可以使用\id或\g、\g引用分組,但不能使用編號(hào)0。
- 當(dāng) repl 是一個(gè)方法時(shí),這個(gè)方法應(yīng)當(dāng)只接受一個(gè)參數(shù)(Match對(duì)象),并返回一個(gè)字符串用于替換(返回的字符串中不能再引用分組)。
使用 count 用于指定最多替換次數(shù),不指定時(shí)全部替換。
import re
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
print(re.sub(pattern,r'\2 \1', s))
def func(m):
return m.group(1).title() + ' ' + m.group(2).title()
print(re.sub(pattern,func, s))
#運(yùn)行結(jié)果:
#say i, world hello!
#I Say, Hello World!
(8)re.subn(pattern, repl, string[, count])
返回 (sub(repl, string[, count]), 替換次數(shù))。
import re
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
print(re.sub(pattern,r'\2 \1', s))
def func(m):
return m.group(1).title() + ' ' + m.group(2).title()
print(re.sub(pattern,func, s))
#運(yùn)行結(jié)果:
#('say i, world hello!', 2)
#('I Say, Hello World!', 2)
5、Python re 模塊的另一種使用方式
??在上面我們介紹了 7 個(gè)工具方法(第一個(gè)可看做生成 pattern 對(duì)象的方法),例如 match、search 等等,不過調(diào)用方式都是re.match,re.search的方式,其實(shí)還有另外一種調(diào)用方式,可以通過pattern.match,pattern.search調(diào)用,這樣調(diào)用便不用將pattern 作為第一個(gè)參數(shù)傳入了,大家想怎樣調(diào)用皆可。