- Python全棧開發(fā):數(shù)據(jù)分析
- 夏正東編著
- 7722字
- 2023-07-17 20:52:42
1.3.1 urllib庫(kù)
urllib庫(kù)是Python內(nèi)置的網(wǎng)絡(luò)請(qǐng)求庫(kù),無(wú)須安裝即可使用,urllib庫(kù)包含了4個(gè)模塊,即parse模塊、request模塊、error模塊和robotparser模塊。
1.parse模塊
該模塊為工具模塊,定義了處理URL的標(biāo)準(zhǔn)接口,如實(shí)現(xiàn)URL各部分的抽取、合并及鏈接轉(zhuǎn)換等,其常用的函數(shù)如下。
1)urlparse()函數(shù)
該函數(shù)用于URL的識(shí)別,并返回一個(gè)ParseResult對(duì)象,其語(yǔ)法格式如下:
urlparse(url)
其中,參數(shù)url表示URL,示例代碼如下:
#資源包\Code\chapter1\1.3\0101.py import urllib.parse url='http://www.oldxia.com/xzd/upload/forum.php?mod=forumdisplay&fid=2' result=urllib.parse.urlparse(url) #輸出結(jié)果為ParseResult(scheme='http',netloc='www.oldxia.com',path='/xzd/upload/forum. php',params='',query='mod=forumdisplay&fid=2',fragment='') print(result) #可以通過(guò)ParseResult對(duì)象的下標(biāo)或?qū)傩悦M(jìn)行訪問(wèn) #輸出結(jié)果為http www.oldxia.com print(result[0],result.netloc)
這里需要注意的是,ParseResult對(duì)象包含6個(gè)屬性,分別為屬性scheme(協(xié)議)、屬性netloc(域名)、屬性path(訪問(wèn)路徑)、屬性params(參數(shù))、屬性query(查詢條件)和屬性fragment(錨點(diǎn))。
2)urlunparse()函數(shù)
該函數(shù)用于將協(xié)議、域名、訪問(wèn)路徑、參數(shù)、查詢條件及錨點(diǎn)拼接成URL,其語(yǔ)法格式如下:
urlunparse(components)
其中,參數(shù)components表示協(xié)議、域名、訪問(wèn)路徑、參數(shù)、查詢條件及錨點(diǎn)所組成的序列,示例代碼如下:
#資源包\Code\chapter1\1.3\0102.py import urllib.parse data=['http','www.oldxia.com','/xzd/upload/forum.php','oldxia','wd=python','python'] result=urllib.parse.urlunparse(data) #輸出結(jié)果為 http://www.oldxia.com/xzd/upload/forum.php;oldxia?wd=python#python print(result)
3)urlsplit()函數(shù)
該函數(shù)用于URL的識(shí)別,并返回一個(gè)SplitResult對(duì)象,其語(yǔ)法格式如下:
urlsplit(url)
其中,參數(shù)url表示URL,示例代碼如下:
#資源包\Code\chapter1\1.3\0103.py import urllib.parse url='http://www.oldxia.com/xzd/upload/forum.php?mod=forumdisplay&fid=2' result=urllib.parse.urlsplit(url) #輸出結(jié)果為SplitResult(scheme='http',netloc='www.oldxia.com',path='/xzd/upload/forum. php',query='mod=forumdisplay&fid=2',fragment='') print(result) #可以通過(guò)ParseResult對(duì)象的下標(biāo)或?qū)傩悦M(jìn)行訪 #輸出結(jié)果為http www.oldxia.com print(result[0],result.netloc)
這里需要注意的是,SplitResult對(duì)象包含5個(gè)屬性,分別為屬性scheme(協(xié)議)、屬性netloc(域名)、屬性path(訪問(wèn)路徑和參數(shù))、屬性query(查詢條件)和屬性fragment(錨點(diǎn))。
4)urlunsplit()函數(shù)
該函數(shù)用于對(duì)協(xié)議、域名、訪問(wèn)路徑和參數(shù)、查詢條件及錨點(diǎn)拼接成URL,其語(yǔ)法格式如下:
urlunsplit(components)
其中,參數(shù)components表示協(xié)議、域名、訪問(wèn)路徑和參數(shù)、查詢條件及錨點(diǎn)所組成的序列,示例代碼如下:
#資源包\Code\chapter1\1.3\0104.py import urllib.parse data=['http','www.oldxia.com','/xzd/upload/forum.php;oldxia','wd=python','python'] result=urllib.parse.urlunsplit(data) #輸出結(jié)果為 http://www.oldxia.com/xzd/upload/forum.php;oldxia?wd=python#python print(result)
5)urljoin()函數(shù)
該函數(shù)用于將基本路徑和相對(duì)路徑連接成絕對(duì)路徑,其語(yǔ)法格式如下:
urljoin(base,url)
其中,參數(shù)base表示基本路徑;參數(shù)url表示相對(duì)路徑,示例代碼如下:
#資源包\Code\chapter1\1.3\0105.py import urllib.parse result=urllib.parse.urljoin("http://www.oldxia.com","/index.html") #輸出結(jié)果為http://www.oldxia.com/index.html print(result)
6)urlencode()函數(shù)
該函數(shù)用于對(duì)字典類型的請(qǐng)求參數(shù)進(jìn)行編碼,其語(yǔ)法格式如下:
urlencode(query)
其中,參數(shù)query表示字典類型的請(qǐng)求參數(shù),示例代碼如下:
#資源包\Code\chapter1\1.3\0106.py import urllib.parse data={'name':'夏正東','age':35} url_parse=urllib.parse.urlencode(data) #輸出結(jié)果為name=%E5%A4%8F%E6%AD%A3%E4%B8%9C&age=35 print(url_parse)
7)parse_qs()函數(shù)
該函數(shù)用于將字符串類型的請(qǐng)求參數(shù)轉(zhuǎn)換為字典類型的請(qǐng)求參數(shù),其語(yǔ)法格式如下:
parse_qs(qs)
其中,參數(shù)qs表示字符串類型的請(qǐng)求參數(shù),示例代碼如下:
#資源包\Code\chapter1\1.3\0107.py import urllib.parse qs='name=夏正東&age=35' result=urllib.parse.parse_qs(qs) #輸出結(jié)果為{'name':['夏正東'],'age':['35']} print(result)
8)parse_qsl()函數(shù)
該函數(shù)用于將字符串類型的請(qǐng)求參數(shù)轉(zhuǎn)換為元組組成的列表類型的請(qǐng)求參數(shù),其語(yǔ)法格式如下:
parse_qsl(qs)
其中,參數(shù)qs表示字符串類型的請(qǐng)求參數(shù),示例代碼如下:
#資源包\Code\chapter1\1.3\0108.py import urllib.parse qs='name=夏正東&age=35' result=urllib.parse.parse_qsl(qs) #輸出結(jié)果為[('name','夏正東'),('age','35')] print(result)
9)quote()函數(shù)
該函數(shù)用于對(duì)字符串進(jìn)行編碼,其語(yǔ)法格式如下:
quote(string)
其中,參數(shù)string表示字符串,示例代碼如下:
#資源包\Code\chapter1\1.3\0109.py import urllib.parse keyword='夏正東' result='http://www.oldxia.com/'+urllib.parse.quote(keyword) #輸出結(jié)果為http://www.oldxia.com/%E5%A4%8F%E6%AD%A3%E4%B8%9C print(result)
10)unquote()函數(shù)
該函數(shù)用于對(duì)編碼后的字符串進(jìn)行解碼,其語(yǔ)法格式如下:
unquote(string)
其中,參數(shù)string表示編碼后的字符串,示例代碼如下:
#資源包\Code\chapter1\1.3\0110.py import urllib.parse url='http://www.oldxia.com/%E5%A4%8F%E6%AD%A3%E4%B8%9C' result=urllib.parse.unquote(url) #輸出結(jié)果為http://www.oldxia.com/夏正東 print(result)
2.request模塊
該模塊是最基本的HTTP請(qǐng)求模塊,用來(lái)模擬發(fā)送HTTP請(qǐng)求。
1)發(fā)送HTTP請(qǐng)求
可以通過(guò)urlopen()函數(shù)發(fā)送HTTP請(qǐng)求,并返回HTTPResponse對(duì)象,其語(yǔ)法格式如下:
urlopen(url,data,timeout)
其中,參數(shù)url表示字符串類型的URL網(wǎng)址,也可以是一個(gè)Request對(duì)象(該內(nèi)容將在后續(xù)為讀者詳細(xì)講解);參數(shù)data為可選參數(shù),表示Bytes類型的數(shù)據(jù),如果省略該參數(shù),則表示使用GET方法發(fā)送HTTP請(qǐng)求,否則表示使用POST方法發(fā)送HTTP請(qǐng)求;參數(shù)timeout為可選參數(shù),表示網(wǎng)站的訪問(wèn)超時(shí)時(shí)間。
此外,通過(guò)HTTPResponse對(duì)象的相關(guān)屬性和方法可以獲取HTTP請(qǐng)求過(guò)程中的相關(guān)信息,具體如表1-8所示。
表1-8 HTTPResponse對(duì)象的相關(guān)屬性和方法

以下為發(fā)送HTTP請(qǐng)求的相關(guān)示例代碼。
(1)爬取百度首頁(yè)的相關(guān)數(shù)據(jù),示例代碼如下:
#資源包\Code\chapter1\1.3\0111.py import urllib.request url="http://www.baidu.com/" #使用GET方法發(fā)送請(qǐng)求 response=urllib.request.urlopen(url) print(response.getheader('Server')) print('===================') print(response.getheaders()) print('===================') print(response.fileno()) print('===================') print(response.geturl()) print('===================') print(response.info()) print('===================') print(response.getcode()) print('===================') print(response.status) print('===================') print(response.version) print('===================') print(response.msg) print('===================') print(response.reason) print('===================') print(response.Debuglevel) print('===================') response.close() print('===================') res=response.read().decode("utf-8") with open("baidu.html","w",encoding="utf-8")as f: f.write(res)
(2)訪問(wèn)老夏學(xué)院的HTTP請(qǐng)求測(cè)試頁(yè)面,示例代碼如下:
#資源包\Code\chapter1\1.3\0112.py import urllib.request import urllib.parse #構(gòu)造POST方法的請(qǐng)求參數(shù) data=Bytes(urllib.parse.urlencode({'name':'夏正東','age':35}),encoding='utf-8') #使用POST方法發(fā)送請(qǐng)求 response=urllib.request.urlopen('http://www.oldxia.com/http_test/post.php',data=data) print(response.read().decode('utf-8'))
2)構(gòu)造HTTP請(qǐng)求對(duì)象
為了防止反爬蟲技術(shù)阻止網(wǎng)絡(luò)爬蟲爬取數(shù)據(jù),通常在使用網(wǎng)絡(luò)爬蟲向服務(wù)器發(fā)送HTTP請(qǐng)求時(shí),需要構(gòu)造HTTP請(qǐng)求對(duì)象,以便于在其內(nèi)部設(shè)置請(qǐng)求頭等相關(guān)信息,達(dá)到模擬瀏覽器進(jìn)行HTTP請(qǐng)求的目的。
可以通過(guò)Request()函數(shù)構(gòu)造請(qǐng)求對(duì)象,其語(yǔ)法格式如下:
Request(url,data,headers)
其中,參數(shù)url表示URL;參數(shù)data為可選參數(shù),表示Bytes類型的數(shù)據(jù),如果省略該參數(shù),則表示使用GET方法發(fā)送HTTP請(qǐng)求,否則表示使用POST方法發(fā)送HTTP請(qǐng)求;參數(shù)headers表示傳遞的請(qǐng)求頭數(shù)據(jù),其類型為字典。
以下為構(gòu)造HTTP請(qǐng)求對(duì)象的相關(guān)示例代碼。
(1)訪問(wèn)老夏學(xué)院的HTTP請(qǐng)求測(cè)試頁(yè)面,示例代碼如下:
#資源包\Code\chapter1\1.3\0113.py import urllib.request import urllib.parse url='http://www.oldxia.com/http_test/get.php?name=xzd&age=35' headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3868.400 QQBrowser/10.8.4394.400"} #使用GET方法發(fā)送請(qǐng)求 req=urllib.request.Request(url=url,headers=headers) response=urllib.request.urlopen(req) print(response.read().decode('utf-8'))
(2)爬取百度首頁(yè)的源代碼,示例代碼如下:
#資源包\Code\chapter1\1.3\0114.py import urllib.request url="http://www.baidu.com/" headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3868.400 QQBrowser/10.8.4394.400"} #使用GET方法發(fā)送請(qǐng)求 req=urllib.request.Request(url=url,headers=headers) response=urllib.request.urlopen(req) res=response.read().decode("utf-8") with open("baidu.html","w",encoding="utf-8")as f: f.write(res)
(3)爬取豆瓣電影分類排行榜中動(dòng)作片的封面圖片,并將相關(guān)信息保存至JSON文件中,示例代碼如下:
#資源包\Code\chapter1\1.3\0115.py from urllib import request,parse import json #請(qǐng)求URL,如圖1-4所示 url="https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&" headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400"} start=input("請(qǐng)輸入開始采集的位置:") limit=input("請(qǐng)輸入要采集的數(shù)量:") #GET方法的請(qǐng)求參數(shù),如圖1-5所示 form={"start":start,"limit":limit} url_parse=parse.urlencode(form) #HTTP請(qǐng)求的URL full_url=url+url_parse #使用GET方法發(fā)送請(qǐng)求 req=request.Request(url=full_url,headers=headers) response=request.urlopen(req) res=response.read().decode("utf-8") results=json.loads(res) items=[] for result in results: dict={} #獲取電影名稱 title=result["title"] #獲取電影評(píng)分 score=result["score"] #獲取電影封面圖片URL img_link=result["cover_url"] dict["title"]=title dict["score"]=score dict["img_link"]=img_link items.append(dict) print(f"正在下載:{title}...") request.urlretrieve(img_link,f"data/{title}.jpg") print(f"{title}下載完畢!") json.dump(items,open("douban.json","w",encoding="utf-8"),ensure_ascii=False,indent=4)

圖1-4 請(qǐng)求URL

圖1-5 GET方法的請(qǐng)求參數(shù)
(4)訪問(wèn)老夏學(xué)院的HTTP請(qǐng)求測(cè)試頁(yè)面,示例代碼如下:
#資源包\Code\chapter1\1.3\0116.py import urllib.request import urllib.parse import json url='http://www.oldxia.com/http_test/post.php' headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3868.400 QQBrowser/10.8.4394.400"} data=Bytes(urllib.parse.urlencode({'name':'夏正東','age':35}),encoding='utf-8') #使用POST方法發(fā)送請(qǐng)求 req=urllib.request.Request(url=url,data=data,headers=headers) response=urllib.request.urlopen(req) print(response.read().decode('utf-8')) print('=========================') #JSON數(shù)據(jù) json_str={'name':'于萍','age':65} #使用POST方法發(fā)送請(qǐng)求 req=urllib.request.Request(url=url,data=Bytes(json.dumps(json_str),'utf8'),headers= headers) response=urllib.request.urlopen(req) print(response.read().decode('utf-8'))
(5)百度翻譯API,示例代碼如下:
#資源包\Code\chapter1\1.3\0117.py import urllib.request import urllib.parse import json #請(qǐng)求URL,如圖1-6所示 url="https://fanyi.baidu.com/sug" word=input("請(qǐng)輸入要翻譯的單詞:") headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3868.400 QQBrowser/10.8.4394.400"} #POST方法的請(qǐng)求參數(shù),如圖1-7所示 data={"kw":word} #使用POST方法發(fā)送請(qǐng)求 data=Bytes(urllib.parse.urlencode(data),encoding='utf-8') req=urllib.request.Request(url=url,headers=headers,data=data) response=urllib.request.urlopen(req) res=response.read().decode("utf-8") #百度翻譯返回的數(shù)據(jù)格式為JSON格式,需要進(jìn)行轉(zhuǎn)換 result=json.loads(res) print(result["data"][0]['v'])

圖1-6 請(qǐng)求URL

圖1-7 POST方法的請(qǐng)求參數(shù)
3)模擬登錄
在訪問(wèn)網(wǎng)站的過(guò)程中,經(jīng)常會(huì)遇到需要登錄的情況,這就給編寫網(wǎng)絡(luò)爬蟲增加了一定的難度,即某些需要爬取的頁(yè)面必須在登錄網(wǎng)站后才能正常爬取,而此時(shí),就可以使用模擬登錄技術(shù),進(jìn)而獲取指定頁(yè)面的內(nèi)容。
在學(xué)習(xí)模擬登錄的相關(guān)技術(shù)之前,首先了解一下什么是會(huì)話。
會(huì)話指的是客戶端與服務(wù)器之間的一系列交互動(dòng)作。理論上,一個(gè)用戶的所有請(qǐng)求操作都應(yīng)該屬于同一個(gè)會(huì)話,而另一個(gè)用戶的所有請(qǐng)求操作則應(yīng)該屬于另一個(gè)會(huì)話,二者不能混淆。例如,用戶A在超市購(gòu)買的任何商品都應(yīng)該放在用戶A的購(gòu)物車內(nèi),并且無(wú)論用戶A在何時(shí)購(gòu)買的商品,也都應(yīng)該放入用戶A的購(gòu)物車內(nèi),而不能放入其他用戶的購(gòu)物車內(nèi),因?yàn)檫@都屬于用戶A的購(gòu)物行為。
那么,如何使多個(gè)會(huì)話不發(fā)生混淆呢?這就需要進(jìn)行會(huì)話跟蹤。
但是,通過(guò)之前的學(xué)習(xí)得知HTTP是一種無(wú)狀態(tài)協(xié)議,一旦數(shù)據(jù)交換完畢,客戶端與服務(wù)器的連接就會(huì)關(guān)閉,再次交換數(shù)據(jù)則需要建立新的連接,也就是說(shuō)服務(wù)器無(wú)法保存客戶端的狀態(tài),這就意味著服務(wù)器無(wú)法從連接上進(jìn)行會(huì)話跟蹤。例如,用戶A在超市購(gòu)買了1件商品并放入購(gòu)物車內(nèi)后,當(dāng)用戶A再次購(gòu)買商品時(shí),超市就已經(jīng)無(wú)法判斷該購(gòu)物行為是否屬于用戶A。
所以進(jìn)行會(huì)話跟蹤必須引入一種特殊的機(jī)制,而Cookie和Session就是用來(lái)進(jìn)行會(huì)話跟蹤的特殊機(jī)制。
(1)Cookie是一種客戶端會(huì)話跟蹤的機(jī)制。是由服務(wù)器發(fā)給客戶端的特殊信息,而這些信息以文本文件的方式存放在客戶端,然后客戶端每次向服務(wù)器發(fā)送請(qǐng)求時(shí)都會(huì)帶上這些特殊的信息,即當(dāng)用戶使用瀏覽器訪問(wèn)一個(gè)支持Cookie的網(wǎng)站時(shí),用戶會(huì)提供包括用戶名在內(nèi)的相關(guān)信息,并提交至服務(wù)器,然后,如果服務(wù)器需要記錄該用戶的狀態(tài),則在向客戶端返回相應(yīng)數(shù)據(jù)的同時(shí)也會(huì)返回這些信息,但是這些信息并不是存放在響應(yīng)體中,而是存放于響應(yīng)頭的首部字段Set-Cookie中,表示在客戶端建立一個(gè)Cookie;最后,當(dāng)客戶端接收到來(lái)自服務(wù)器的響應(yīng)后,客戶端會(huì)將這些信息存放于一個(gè)統(tǒng)一的位置;自此,客戶端再向服務(wù)器發(fā)送請(qǐng)求時(shí),都會(huì)將相應(yīng)的Cookie再次發(fā)送至服務(wù)器,而此次的Cookie信息則存放于請(qǐng)求頭的首部字段Cookie中。
有了Cookie這樣的技術(shù),服務(wù)器在接收到來(lái)自客戶端的請(qǐng)求后,就能通過(guò)分析存放于請(qǐng)求頭中的Cookie獲得客戶端的特有信息,從而動(dòng)態(tài)地生成與該客戶端相對(duì)應(yīng)的內(nèi)容,以此來(lái)辨認(rèn)用戶的狀態(tài),進(jìn)而可以輕松實(shí)現(xiàn)登錄等需要會(huì)話跟蹤的頁(yè)面。
(2)隨著網(wǎng)絡(luò)技術(shù)的不斷發(fā)展,Session應(yīng)運(yùn)而生。Session是一種服務(wù)器會(huì)話跟蹤機(jī)制,其在使用上比Cookie更簡(jiǎn)單,并且安全性較Cookie也有很大的提升,但是相應(yīng)地也增加了服務(wù)器的存儲(chǔ)壓力。
Session是服務(wù)器為客戶端所開辟的內(nèi)存空間,其中保存的信息用于會(huì)話跟蹤。當(dāng)客戶端訪問(wèn)服務(wù)器時(shí),會(huì)在服務(wù)器開辟一塊內(nèi)存,這塊內(nèi)存就叫作Session;與此同時(shí),服務(wù)器會(huì)為該Session生成唯一的sessionID,而這個(gè)sessionID將在本次響應(yīng)中返回客戶端,并可以通過(guò)3種方式進(jìn)行保存,一是借助Cookie機(jī)制,即把sessionID放在Cookie中,其名稱類似于SESSIONID,二是URL重寫,三是表單隱藏字段;最后,當(dāng)客戶端再次發(fā)送請(qǐng)求時(shí),會(huì)將sessionID一并發(fā)送,服務(wù)器在接收到該請(qǐng)求后,會(huì)根據(jù)sessionID找到相應(yīng)的Session,從而再次使用。
下面通過(guò)一個(gè)例子,以使讀者深入了解Cookie和Session之間的區(qū)別與聯(lián)系。
當(dāng)顧客去超市購(gòu)物時(shí),一般情況下,超市會(huì)提供會(huì)員卡,即當(dāng)顧客購(gòu)買商品時(shí)出示會(huì)員卡就會(huì)獲得相應(yīng)的優(yōu)惠待遇,接下來(lái)就以此為例具體分析一下:
一是該超市的收銀員很厲害,能記住每位辦過(guò)會(huì)員卡的顧客,每當(dāng)顧客結(jié)賬時(shí),收銀員憑借其記憶就可以知道該顧客是否為會(huì)員,進(jìn)而判斷該顧客是否可以享受相應(yīng)的優(yōu)惠待遇。這種方式就是協(xié)議本身支持會(huì)話跟蹤。
二是該超市發(fā)給顧客一張實(shí)體的會(huì)員卡,并且該實(shí)體會(huì)員卡一般還會(huì)具有有效期限,而超市并不掌握所有發(fā)放的實(shí)體會(huì)員卡的明細(xì),所以顧客在每次結(jié)賬時(shí),必須攜帶該實(shí)體會(huì)員卡,并需要出示實(shí)體會(huì)員卡,這樣才可以享受相應(yīng)的優(yōu)惠待遇。這種方式就是客戶端會(huì)話跟蹤的機(jī)制,即Cookie。
三是該超市發(fā)給顧客一張實(shí)體的會(huì)員卡,并且超市掌握所有發(fā)放的實(shí)體會(huì)員卡的明細(xì),所以顧客在每次結(jié)賬時(shí),不必?cái)y帶該實(shí)體會(huì)員卡,而是只需告知收銀員實(shí)體會(huì)員卡的卡號(hào),便可以享受相應(yīng)的優(yōu)惠待遇。這種做法就是服務(wù)器會(huì)話跟蹤的機(jī)制,即Session。
此外,關(guān)于Session還有一種誤解,即“只要關(guān)閉瀏覽器,Session就會(huì)消失”。其實(shí)經(jīng)過(guò)之前的學(xué)習(xí),這個(gè)誤解已經(jīng)很好解釋了,讀者完全可以從上述的例子中得出答案,即除非顧客主動(dòng)向超市提出注銷實(shí)體會(huì)員卡,否則超市絕對(duì)不會(huì)輕易刪除顧客的實(shí)體會(huì)員卡信息。對(duì)于Session來(lái)講也是一樣的,除非客戶端通知服務(wù)器刪除一個(gè)Session,否則服務(wù)器會(huì)一直保留該Session,然而,客戶端的瀏覽器從不會(huì)主動(dòng)地在關(guān)閉之前通知服務(wù)器其將要關(guān)閉,因此服務(wù)器根本沒有機(jī)會(huì)得知客戶端的瀏覽器已經(jīng)關(guān)閉,所以服務(wù)器也就不會(huì)刪除Session,但之所以會(huì)有這種誤解,是因?yàn)榇蟛糠諷ession是借助Cookie機(jī)制來(lái)保存sessionID的,即關(guān)閉客戶端的瀏覽器后,隨著Cookie的消失,導(dǎo)致sessionID被刪除,所以當(dāng)再次連接服務(wù)器時(shí),也就無(wú)法找到原來(lái)的Session,而如果服務(wù)器在客戶端所建立的Cookie被保存到硬盤上,或者使用某種方式改寫客戶端發(fā)出的請(qǐng)求頭,將原來(lái)的sessionID發(fā)送給服務(wù)器,則再次打開客戶端的瀏覽器時(shí),仍然可以找到原來(lái)的Session。此外,由于關(guān)閉客戶端的瀏覽器并不會(huì)導(dǎo)致服務(wù)器的Session被刪除,所以服務(wù)器會(huì)為Session設(shè)置一個(gè)失效時(shí)間,即當(dāng)距離客戶端上次使用Session的時(shí)間超過(guò)該失效時(shí)間時(shí),服務(wù)器就可以認(rèn)為客戶端已經(jīng)停止了活動(dòng),從而會(huì)刪除該Session,以達(dá)到節(jié)省存儲(chǔ)空間的目的。
在學(xué)習(xí)完會(huì)話、會(huì)話跟蹤等知識(shí)點(diǎn)后,接著來(lái)學(xué)習(xí)一下如何進(jìn)行模擬登錄。
模擬登錄可以分為兩種方式,即手動(dòng)添加Cookie和自動(dòng)保存Cookie。
(1)手動(dòng)添加Cookie,該方法的應(yīng)用過(guò)程很簡(jiǎn)單。首先,需要在登錄頁(yè)面手動(dòng)輸入正確的登錄賬號(hào)和登錄密碼,然后,獲取登錄成功后待爬取頁(yè)面的Cookie并手動(dòng)保存;最后,將該Cookie添加至網(wǎng)絡(luò)爬蟲程序請(qǐng)求頭的首部字段Cookie中,這樣便可以在不輸入登錄賬號(hào)和登錄密碼的情況下,再次訪問(wèn)需要爬取的頁(yè)面。
下面以爬取快代理的賬戶管理頁(yè)面為例,講解如何通過(guò)手動(dòng)添加Cookie的方式進(jìn)行模擬登錄。
當(dāng)在不使用Cookie的情況下,使用網(wǎng)絡(luò)爬蟲爬取快代理的賬戶管理頁(yè)面(https://www.kuaidaili.com/usercenter/)時(shí),會(huì)被強(qiáng)制跳轉(zhuǎn)至登錄頁(yè)面,因?yàn)樵擁?yè)面只有在正常登錄后才可以訪問(wèn)。此時(shí),就可以通過(guò)手動(dòng)添加Cookie的方式進(jìn)行模擬登錄,以爬取賬戶管理頁(yè)面的內(nèi)容,其步驟如下:
第1步,進(jìn)入快代理的登錄頁(yè)面,如圖1-8所示,輸入正確的登錄賬號(hào)和登錄密碼,并單擊“登錄”按鈕。
第2步,成功登錄后,在當(dāng)前頁(yè)面打開開發(fā)者工具,如圖1-9所示,并單擊右上角的賬戶名,即可進(jìn)入賬戶管理頁(yè)面。

圖1-8 登錄頁(yè)面

圖1-9 成功登錄后的界面
第3步,進(jìn)入賬戶管理頁(yè)面后,在開發(fā)者工具中手動(dòng)保存當(dāng)前頁(yè)面的Cookie,如圖1-10所示。
第4步,將該Cookie添加到網(wǎng)絡(luò)爬蟲請(qǐng)求頭的首部字段Cookie中即可。
示例代碼如下:
#資源包\Code\chapter1\1.3\0118.py import urllib.request #請(qǐng)求URL url="https://www.kuaidaili.com/usercenter/overview" headers={ "User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400", #Cookie,如圖1-26所示 "Cookie":"channelid=bdtg_a10_a10a1;sid=1654743338035518;_gcl_au=1.1.662463456. 1654743338;_ga=GA1.2.228310457.1654743339;_gid=GA1.2.541324931.1654743339;Hm_lvt_ 7ed65b1cc4b810e9fd37959c9bb51b31 = 1654743339, 1654754105; sessionid = c29d392cbcdeddaa8bf12e3940514a51;Hm_lpvt_7ed65b1cc4b810e9fd37959c9bb51b31=1654754192" } req=urllib.request.Request(url=url,headers=headers) response=urllib.request.urlopen(req) html_code=response.read().decode('utf-8') with open("kuaidaili.html","w",encoding="utf-8")as f: f.write(html_code)

圖1-10 當(dāng)前頁(yè)面的Cookie
(2)自動(dòng)保存Cookie,該方法的應(yīng)用過(guò)程與手動(dòng)添加Cookie登錄相比更加復(fù)雜,因?yàn)樵撨^(guò)程需要應(yīng)用Handler處理器。
下面就以爬取快代理的賬戶管理頁(yè)面為例,講解一下如何通過(guò)自動(dòng)保存Cookie的方式進(jìn)行模擬登錄。
當(dāng)在不使用Cookie的情況下,使用網(wǎng)絡(luò)爬蟲爬取快代理的賬戶管理頁(yè)面(https://www.kuaidaili.com/usercenter/)時(shí),會(huì)被強(qiáng)制跳轉(zhuǎn)至登錄頁(yè)面,因?yàn)樵擁?yè)面只有在正常登錄后才可以訪問(wèn)。此時(shí),就可以通過(guò)自動(dòng)保存Cookie的方式進(jìn)行模擬登錄,以爬取賬戶管理頁(yè)面的內(nèi)容,其步驟如下:
第1步,使用http.cookiejar模塊中的CookieJar()方法自動(dòng)保存在登錄頁(yè)面登錄成功后的Cookie。
第2步,使用BaseHandler類的子類HTTPCookieProcessor創(chuàng)建HTTPCookieProcessor對(duì)象。
第3步,使用request模塊中的build_opener()方法創(chuàng)建opener對(duì)象,因?yàn)樵搶?duì)象可以自動(dòng)保存Cookie,并且可以實(shí)現(xiàn)會(huì)話保持。
第4步,對(duì)需要爬取的頁(yè)面發(fā)送包含上述已經(jīng)自動(dòng)保存Cookie的請(qǐng)求頭的HTTP請(qǐng)求,這里需要注意的是,不能直接使用urlopen()方法進(jìn)行發(fā)送,而是需要使用opener對(duì)象的open()方法進(jìn)行發(fā)送。
示例代碼如下:

4)使用代理IP
在運(yùn)行網(wǎng)絡(luò)爬蟲時(shí),經(jīng)常會(huì)遇到這樣的情況,即剛開始網(wǎng)絡(luò)爬蟲運(yùn)行一切正常,并且可以正常抓取所需要的數(shù)據(jù),然而隨著時(shí)間的推移就會(huì)出現(xiàn)錯(cuò)誤,例如“403 Forbidden”,而出現(xiàn)這種錯(cuò)誤的原因就是爬取的網(wǎng)站采取了一些反爬蟲措施,即網(wǎng)站會(huì)檢測(cè)訪問(wèn)IP在單位時(shí)間內(nèi)的請(qǐng)求次數(shù),如果超出了網(wǎng)站設(shè)定的閾值,就會(huì)拒絕為該請(qǐng)求提供服務(wù),并返回相關(guān)的錯(cuò)誤信息,而這種情況常常稱為“封禁IP”。
為了應(yīng)對(duì)該種反爬蟲措施,需要將IP進(jìn)行偽裝,即需要使用代理IP,其根據(jù)匿名程度,可以分為以下3種:
(1)高匿名代理IP,該代理IP會(huì)將數(shù)據(jù)包原封不動(dòng)地進(jìn)行轉(zhuǎn)發(fā),這樣服務(wù)器就會(huì)認(rèn)為其是一個(gè)普通的客戶端在進(jìn)行訪問(wèn),但其記錄的IP是代理服務(wù)器的IP。
(2)普通匿名代理IP,該代理IP會(huì)在數(shù)據(jù)包上進(jìn)行一定的改動(dòng),因此服務(wù)器有可能發(fā)現(xiàn)其使用的是代理服務(wù)器,也有一定概率追查到客戶端的真實(shí)IP。
(3)透明代理IP,該代理IP不但改動(dòng)了數(shù)據(jù)包,而且會(huì)告知服務(wù)器其客戶端的真實(shí)IP,所以這種代理IP除了能用緩存技術(shù)提高瀏覽速度,以及使用內(nèi)容過(guò)濾提高安全性之外,并無(wú)其他顯著作用。
再來(lái)學(xué)習(xí)一下設(shè)置代理IP的3種常用的方式,其包括以下3種:
(1)使用免費(fèi)代理服務(wù),該方式提供的代理IP質(zhì)量整體較差,建議從中選取高匿名代理IP,但是由于該種方式是免費(fèi)的,所以實(shí)際上其所提供的可用高匿名代理IP并不多,并且穩(wěn)定性不佳,使用前需要進(jìn)行篩選。
(2)使用付費(fèi)代理服務(wù),該方式提供的代理IP質(zhì)量較免費(fèi)代理服務(wù)要高出很多,并且種類繁多,便于用戶根據(jù)具體的項(xiàng)目需求進(jìn)行選擇。
(3)ADSL撥號(hào),該種方式應(yīng)用了ADSL撥號(hào)的特性,即撥一次號(hào),其IP也隨之更換,其特點(diǎn)就是穩(wěn)定性較高。
下面就以使用百度查詢當(dāng)前IP為例,講解一下如何使用代理IP。
由于網(wǎng)絡(luò)中可用的高質(zhì)量免費(fèi)代理IP不多,并且其穩(wěn)定性較差,所以本例使用快代理提供的收費(fèi)代理IP進(jìn)行訪問(wèn),其步驟如下:
第1步,登錄快代理,并購(gòu)買“私密代理”服務(wù)。
第2步,進(jìn)入“我的私密代理”,如圖1-11所示,并單擊“提取代理”。

圖1-11 我的私密代理
第3步,在“提取私密代理”頁(yè)面(如圖1-12所示)中單擊“立即提取”,這樣就可以看到所提取出來(lái)的代理IP,如圖1-13所示。

圖1-12 提取私密代理

圖1-13 私密代理提取結(jié)果
第4步,使用BaseHandler類的子類ProxyHandler創(chuàng)建ProxyHandler對(duì)象,其語(yǔ)法格式如下:
ProxyHandler(proxies)
其中,參數(shù)proxies表示代理IP,其可分為兩種,即免費(fèi)代理IP和收費(fèi)代理IP,具體格式如表1-9所示。
表1-9 參數(shù)proxies的格式

第5步,使用request模塊中的build_opener()方法創(chuàng)建opener對(duì)象,并使用opener對(duì)象的open()方法發(fā)送HTTP請(qǐng)求即可。
示例代碼如下:
#資源包\Code\chapter1\1.3\0120.py from urllib import request url='https://www.baidu.com/s?wd=ip' #免費(fèi)代理IP #proxy={ #"http":"http://42.56.238.40:3000", #"https":"http://42.56.238.40:3000" #} #收費(fèi)代理IP proxy={ "http":"http://xiazhengdong:3p0h090r@27.158.237.186:19674", "https":"http://xiazhengdong:3p0h090r@27.158.237.186:19674" } headers={ "User-Agent":"Mozilla/5.0(Windows NT 10.0;Win64;x64) AppleWebKit/537.36(KHTML,like Gecko)Chrome/74.0.3729.169 Safari/537.36", } handler=request.ProxyHandler(proxies=proxy) opener=request.build_opener(handler) req=request.Request(url=url,headers=headers) new_res=opener.open(req) html_code=new_res.read().decode('utf-8') with open("baidu.html","w",encoding="utf-8")as f: f.write(html_code)
此時(shí),運(yùn)行baidu.html文件,其顯示的IP為“27.158.237.186福建省漳州市電信”,如圖1-14所示,而筆者的真實(shí)IP為“113.234.4.216遼寧省大連市甘井子區(qū)聯(lián)通”,如圖1-15所示。
3.error模塊
該模塊為異常處理模塊,主要用于處理由request模塊產(chǎn)生的異常。
該模塊中常用的類如下。
1)URLError類
該類繼承于OSError類,request模塊產(chǎn)生的所有異常都可以由該類進(jìn)行處理。
該類的實(shí)例對(duì)象具有1個(gè)屬性,即屬性reason,用于表示產(chǎn)生異常的原因,示例代碼如下:

圖1-14 使用代理IP后所查詢到的IP

圖1-15 真實(shí)IP
#資源包\Code\chapter1\1.3\0121.py import urllib.error import urllib.request try: #請(qǐng)求一個(gè)不存在的URL responses=urllib.request.urlopen('http://www.oldxia.com/python') except urllib.error.urlError as e: print(type(e)) print(e.reason)
2)HTTPError類
該類繼承于URLError類,專門用于處理HTTP請(qǐng)求的異常。
該類的實(shí)例對(duì)象具有3個(gè)屬性,即屬性reason、屬性code和屬性headers,分別用于表示產(chǎn)生異常的原因、響應(yīng)狀態(tài)碼和請(qǐng)求頭的信息,示例代碼如下:
#資源包\Code\chapter1\1.3\0122.py import urllib.error import urllib.request try: #請(qǐng)求一個(gè)不存在的URL responses=urllib.request.urlopen('http://www.oldxia.com/python') except urllib.error.HTTPError as e: print(f'異常原因:{e.reason}') print(f'響應(yīng)狀態(tài)碼:{e.code}') print(f'請(qǐng)求頭:\n{e.headers}') except urllib.error.urlError as e: print(e.reason)
4.robotparser模塊
該模塊主要用于實(shí)現(xiàn)網(wǎng)站Robots協(xié)議的分析,并判斷網(wǎng)站是否可以被爬取。
在正式開始學(xué)習(xí)robotparser模塊之前,先來(lái)了解一下Robots協(xié)議。
Robots協(xié)議(也稱為爬蟲協(xié)議、機(jī)器人協(xié)議等)的全稱是網(wǎng)絡(luò)爬蟲排除標(biāo)準(zhǔn)(Robots Exclusion Protocol)。
Robots協(xié)議是國(guó)際互聯(lián)網(wǎng)界通行的道德規(guī)范,其基于以下原則建立:一是搜索技術(shù)應(yīng)服務(wù)于人類,同時(shí)尊重信息提供者的意愿,并維護(hù)其隱私權(quán);二是網(wǎng)站有義務(wù)保護(hù)其使用者的個(gè)人信息和隱私不被侵犯。
Robots協(xié)議有以下4點(diǎn)作用:第一,告知搜索引擎哪些頁(yè)面可以爬取,哪些頁(yè)面不可以爬??;第二,可以屏蔽一些網(wǎng)站中比較大的文件,例如圖片、音樂(lè)、視頻等,節(jié)省服務(wù)器帶寬;第三,可以屏蔽站點(diǎn)的一些死鏈接,以便于搜索引擎抓取網(wǎng)站內(nèi)容;第四,設(shè)置網(wǎng)站地圖鏈接,方便引導(dǎo)網(wǎng)絡(luò)爬蟲爬取頁(yè)面。
Robots協(xié)議通常是以robots.txt的文件形式存在的,并且一般存放于網(wǎng)站的根目錄下。
以下就是一個(gè)robots.txt文件,其內(nèi)容如下:
User-agent:* Allow:/xzd/upload/ Disallow:/
其中,User-agent表示對(duì)Robots協(xié)議有效的搜索引擎;Disallow表示不允許爬取的目錄;Allow表示允許爬取的目錄,一般與Disallow一起使用。
可以通過(guò)robotparser模塊中的RobotFileParser類對(duì)robots.txt文件進(jìn)行解析,其常用的方法如下。
1)set_url()方法
該方法用于設(shè)置robots.txt文件的URL,其語(yǔ)法格式如下:
set_url(url)
其中,參數(shù)url表示URL。
2)read()方法
該方法用于讀取robots.txt文件,其語(yǔ)法格式如下:
read()
3)can_fetch()方法
該方法用于獲取搜索引擎是否可以爬取指定的URL,其語(yǔ)法格式如下:
can_fetch(useragent,url)
其中,參數(shù)ueseragent表示搜索引擎;參數(shù)url表示URL。
4)mtime()方法
該方法用于獲取上次爬取和分析Robots協(xié)議的時(shí)間,其語(yǔ)法格式如下:
mtime()
示例代碼如下:
#資源包\Code\chapter1\1.3\0123.py import urllib.robotparser import datetime rfp=urllib.robotparser.RobotFileParser() rfp.set_url('http://www.oldxia.com/robotparser/robots.txt') rfp.read() res1=rfp.can_fetch('*','http://www.oldxia.com/xzd/upload/') #True print(res1) res2=rfp.can_fetch('*','http://www.oldxia.com/xzd/upload/index.html') #True print(res2) res3=rfp.can_fetch('*','http://www.oldxia.com/xzd/') #False print(res3) print(f'上次爬取和分析Robots協(xié)議的時(shí)間為{datetime.date.fromtimestamp(rfp.mtime())}')
- Visual FoxPro 程序設(shè)計(jì)
- NLTK基礎(chǔ)教程:用NLTK和Python庫(kù)構(gòu)建機(jī)器學(xué)習(xí)應(yīng)用
- PowerCLI Cookbook
- Java面向?qū)ο蟪绦蜷_發(fā)及實(shí)戰(zhàn)
- Mastering AndEngine Game Development
- BIM概論及Revit精講
- R Data Analysis Cookbook(Second Edition)
- Java SE實(shí)踐教程
- Java EE企業(yè)級(jí)應(yīng)用開發(fā)教程(Spring+Spring MVC+MyBatis)
- Vue.js應(yīng)用測(cè)試
- 從Excel到Python數(shù)據(jù)分析:Pandas、xlwings、openpyxl、Matplotlib的交互與應(yīng)用
- SpringBoot從零開始學(xué)(視頻教學(xué)版)
- Sails.js Essentials
- 大學(xué)計(jì)算機(jī)基礎(chǔ)實(shí)訓(xùn)教程
- Oracle Database XE 11gR2 Jump Start Guide