所謂網頁抓取,就是把URL地址中指定的網絡資源從網絡流中讀取出來,保存到本地。 在Python中有不少庫能夠用來抓取網頁,咱們先學習urllib2
。javascript
urllib2 是 Python2.7 自帶的模塊(不須要下載,導入便可使用)html
urllib2 官方文檔:https://docs.python.org/2/library/urllib2.htmljava
urllib2 源碼:https://hg.python.org/cpython/file/2.7/Lib/urllib2.pynode
urllib2
在 python3.x 中被改成urllib.request
python
咱們先來段代碼:web
# urllib2_urlopen.py # 導入urllib2 庫 import urllib2 # 向指定的url發送請求,並返回服務器響應的類文件對象 response = urllib2.urlopen("http://www.baidu.com") # 類文件對象支持 文件對象的操做方法,如read()方法讀取文件所有內容,返回字符串 html = response.read() # 打印字符串 print html
執行寫的python代碼,將打印結果ajax
Power@PowerMac ~$: python urllib2_urlopen.py
實際上,若是咱們在瀏覽器上打開百度主頁, 右鍵選擇「查看源代碼」,你會發現,跟咱們剛纔打印出來的是如出一轍。也就是說,上面的4行代碼就已經幫咱們把百度的首頁的所有代碼爬了下來。算法
一個基本的url請求對應的python代碼真的很是簡單。json
在咱們第一個例子裏,urlopen()的參數就是一個url地址;python3.x
可是若是須要執行更復雜的操做,好比增長HTTP報頭,必須建立一個 Request 實例來做爲urlopen()的參數;而須要訪問的url地址則做爲 Request 實例的參數。
咱們編輯urllib2_request.py
# urllib2_request.py import urllib2 # url 做爲Request()方法的參數,構造並返回一個Request對象 request = urllib2.Request("http://www.baidu.com") # Request對象做爲urlopen()方法的參數,發送給服務器並接收響應 response = urllib2.urlopen(request) html = response.read() print html
新建Request實例,除了必需要有 url 參數以外,還能夠設置另外兩個參數:
data(默認空):是伴隨 url 提交的數據(好比要post的數據),同時 HTTP 請求將從 "GET"方式 改成 "POST"方式。
headers(默認空):是一個字典,包含了須要發送的HTTP報頭的鍵值對。
這兩個參數下面會說到。
可是這樣直接用urllib2給一個網站發送請求的話,確實略有些唐突了,就比如,人家每家都有門,你以一個路人的身份直接闖進去顯然不是很禮貌。並且有一些站點不喜歡被程序(非人爲訪問)訪問,有可能會拒絕你的訪問請求。
可是若是咱們用一個合法的身份去請求別人網站,顯然人家就是歡迎的,因此咱們就應該給咱們的這個代碼加上一個身份,就是所謂的User-Agent
頭。
- 瀏覽器 就是互聯網世界上公認被容許的身份,若是咱們但願咱們的爬蟲程序更像一個真實用戶,那咱們第一步,就是須要假裝成一個被公認的瀏覽器。用不一樣的瀏覽器在發送請求的時候,會有不一樣的User-Agent頭。 urllib2默認的User-Agent頭爲:
Python-urllib/x.y
(x和y是Python主版本和次版本號,例如 Python-urllib/2.7)
#urllib2_useragent.py import urllib2 url = "http://www.itcast.cn" #IE 9.0 的 User-Agent,包含在 ua_header裏 ua_header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} # url 連同 headers,一塊兒構造Request請求,這個請求將附帶 IE9.0 瀏覽器的User-Agent request = urllib2.Request(url, headers = ua_header) # 向服務器發送這個請求 response = urllib2.urlopen(request) html = response.read() print html
在 HTTP Request 中加入特定的 Header,來構造一個完整的HTTP請求消息。
能夠經過調用
Request.add_header()
添加/修改一個特定的header 也能夠經過調用Request.get_header()
來查看已有的header。
# urllib2_headers.py import urllib2 url = "http://www.itcast.cn" #IE 9.0 的 User-Agent header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} request = urllib2.Request(url, headers = header) #也能夠經過調用Request.add_header() 添加/修改一個特定的header request.add_header("Connection", "keep-alive") # 也能夠經過調用Request.get_header()來查看header信息 # request.get_header(header_name="Connection") response = urllib2.urlopen(req) print response.code #能夠查看響應狀態碼 html = response.read() print html
# urllib2_add_headers.py import urllib2 import random url = "http://www.itcast.cn" ua_list = [ "Mozilla/5.0 (Windows NT 6.1; ) Apple.... ", "Mozilla/5.0 (X11; CrOS i686 2268.111.0)... ", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X.... ", "Mozilla/5.0 (Macintosh; Intel Mac OS... " ] user_agent = random.choice(ua_list) request = urllib2.Request(url) #也能夠經過調用Request.add_header() 添加/修改一個特定的header request.add_header("User-Agent", user_agent) # 第一個字母大寫,後面的所有小寫 request.get_header("User-agent") response = urllib2.urlopen(req) html = response.read() print html
GET
和POST
方法
urllib 僅能夠接受URL,不能建立 設置了headers 的Request 類實例;
可是 urllib 提供
urlencode
方法用來GET查詢字符串的產生,而 urllib2 則沒有。(這是 urllib 和 urllib2 常常一塊兒使用的主要緣由)編碼工做使用urllib的
urlencode()
函數,幫咱們將key:value
這樣的鍵值對轉換成"key=value"
這樣的字符串,解碼工做可使用urllib的unquote()
函數。(注意,不是urllib2.urlencode() )
# IPython2 中的測試結果 In [1]: import urllib In [2]: word = {"wd" : "傳智播客"} # 經過urllib.urlencode()方法,將字典鍵值對按URL編碼轉換,從而能被web服務器接受。 In [3]: urllib.urlencode(word) Out[3]: "wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2" # 經過urllib.unquote()方法,把 URL編碼字符串,轉換回原先字符串。 In [4]: print urllib.unquote("wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2") wd=傳智播客
GET請求通常用於咱們向服務器獲取數據,好比說,咱們用百度搜索傳智播客
:https://www.baidu.com/s?wd=傳智播客
瀏覽器的url會跳轉成如圖所示:
https://www.baidu.com/s?wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2
在其中咱們能夠看到在請求部分裏,http://www.baidu.com/s?
以後出現一個長長的字符串,其中就包含咱們要查詢的關鍵詞傳智播客,因而咱們能夠嘗試用默認的Get方式來發送請求。
# urllib2_get.py import urllib #負責url編碼處理 import urllib2 url = "http://www.baidu.com/s" word = {"wd":"傳智播客"} word = urllib.urlencode(word) #轉換成url編碼格式(字符串) newurl = url + "?" + word # url首個分隔符就是 ? headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"} request = urllib2.Request(newurl, headers=headers) response = urllib2.urlopen(request) print response.read()
首先咱們建立一個python文件, tiebaSpider.py,咱們要完成的是,輸入一個百度貼吧的地址,好比:
百度貼吧LOL吧第一頁:http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=0
第二頁: http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=50
第三頁: http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=100
發現規律了吧,貼吧中每一個頁面不一樣之處,就是url最後的pn的值,其他的都是同樣的,咱們能夠抓住這個規律。
http://tieba.baidu.com/f?kw=lol
# 模擬 main 函數 if __name__ == "__main__": kw = raw_input("請輸入須要爬取的貼吧:") # 輸入起始頁和終止頁,str轉成int類型 beginPage = int(raw_input("請輸入起始頁:")) endPage = int(raw_input("請輸入終止頁:")) url = "http://tieba.baidu.com/f?" key = urllib.urlencode({"kw" : kw}) # 組合後的url示例:http://tieba.baidu.com/f?kw=lol url = url + key tiebaSpider(url, beginPage, endPage)
def tiebaSpider(url, beginPage, endPage): """ 做用:負責處理url,分配每一個url去發送請求 url:須要處理的第一個url beginPage: 爬蟲執行的起始頁面 endPage: 爬蟲執行的截止頁面 """ for page in range(beginPage, endPage + 1): pn = (page - 1) * 50 filename = "第" + str(page) + "頁.html" # 組合爲完整的 url,而且pn值每次增長50 fullurl = url + "&pn=" + str(pn) #print fullurl # 調用loadPage()發送請求獲取HTML頁面 html = loadPage(fullurl, filename) # 將獲取到的HTML頁面寫入本地磁盤文件 writeFile(html, filename)
def loadPage(url, filename): ''' 做用:根據url發送請求,獲取服務器響應文件 url:須要爬取的url地址 filename: 文件名 ''' print "正在下載" + filename headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"} request = urllib2.Request(url, headers = headers) response = urllib2.urlopen(request) return response.read()
def writeFile(html, filename): """ 做用:保存服務器響應文件到本地磁盤文件裏 html: 服務器響應文件 filename: 本地磁盤文件名 """ print "正在存儲" + filename with open(filename, 'w') as f: f.write(html) print "-" * 20
其實不少網站都是這樣的,同類網站下的html頁面編號,分別對應網址後的網頁序號,只要發現規律就能夠批量爬取頁面了。
上面咱們說了Request請求對象的裏有data參數,它就是用在POST裏的,咱們要傳送的數據就是這個參數data,data是一個字典,裏面要匹配鍵值對。
輸入測試數據,再經過使用Fiddler觀察,其中有一條是POST請求,而向服務器發送的請求數據並非在url裏,那麼咱們能夠試着模擬這個POST請求。
因而,咱們能夠嘗試用POST方式發送請求。
import urllib import urllib2 # POST請求的目標URL url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule&smartresult=ugc&sessionFrom=null" headers={"User-Agent": "Mozilla...."} formdata = { "type":"AUTO", "i":"i love python", "doctype":"json", "xmlVersion":"1.8", "keyfrom":"fanyi.web", "ue":"UTF-8", "action":"FY_BY_ENTER", "typoResult":"true" } data = urllib.urlencode(formdata) request = urllib2.Request(url, data = data, headers = headers) response = urllib2.urlopen(request) print response.read()
Content-Length: 144
: 是指發送的表單數據長度爲144,也就是字符個數是144個。
X-Requested-With: XMLHttpRequest
:表示Ajax異步請求。
Content-Type: application/x-www-form-urlencoded
: 表示瀏覽器提交 Web 表單時使用,表單數據會按照 name1=value1&name2=value2 鍵值對形式進行編碼。
有些網頁內容使用AJAX加載,只要記得,AJAX通常返回的是JSON,直接對AJAX地址進行post或get,就返回JSON數據了。
"做爲一名爬蟲工程師,你最須要關注的,是數據的來源"
import urllib import urllib2 # demo1 url = "https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action" headers={"User-Agent": "Mozilla...."} # 變更的是這兩個參數,從start開始日後顯示limit個 formdata = { 'start':'0', 'limit':'10' } data = urllib.urlencode(formdata) request = urllib2.Request(url, data = data, headers = headers) response = urllib2.urlopen(request) print response.read() # demo2 url = "https://movie.douban.com/j/chart/top_list?" headers={"User-Agent": "Mozilla...."} # 處理全部參數 formdata = { 'type':'11', 'interval_id':'100:90', 'action':'', 'start':'0', 'limit':'10' } data = urllib.urlencode(formdata) request = urllib2.Request(url, data = data, headers = headers) response = urllib2.urlopen(request) print response.read()
GET方式是直接以連接形式訪問,連接中包含了全部的參數,服務器端用Request.QueryString獲取變量的值。若是包含了密碼的話是一種不安全的選擇,不過你能夠直觀地看到本身提交了什麼內容。
POST則不會在網址上顯示全部的參數,服務器端用Request.Form獲取提交的數據,在Form提交的時候。可是HTML代碼裏若是不指定 method 屬性,則默認爲GET請求,Form中提交的數據將會附加在url以後,以
?
分開與url分開。表單數據能夠做爲 URL 字段(method="get")或者 HTTP POST (method="post")的方式來發送。好比在下面的HTML代碼中,表單數據將由於 (method="get") 而附加到 URL 上:
<form action="form_action.asp" method="get"> <p>First name: <input type="text" name="fname" /></p> <p>Last name: <input type="text" name="lname" /></p> <input type="submit" value="Submit" /> </form>
如今隨處可見 https 開頭的網站,urllib2能夠爲 HTTPS 請求驗證SSL證書,就像web瀏覽器同樣,若是網站的SSL證書是通過CA認證的,則可以正常訪問,如:https://www.baidu.com/
等...
若是SSL證書驗證不經過,或者操做系統不信任服務器的安全證書,好比瀏覽器在訪問12306網站如:https://www.12306.cn/mormhweb/的時候,會警告用戶證書不受信任。(聽說 12306 網站證書是本身作的,沒有經過CA認證)
urllib2在訪問的時候則會報出SSLError:
import urllib2 url = "https://www.12306.cn/mormhweb/" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"} request = urllib2.Request(url, headers = headers) response = urllib2.urlopen(request) print response.read()
運行結果:
urllib2.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)>
因此,若是之後遇到這種網站,咱們須要單獨處理SSL證書,讓程序忽略SSL證書驗證錯誤,便可正常訪問。
import urllib import urllib2 # 1. 導入Python SSL處理模塊 import ssl # 2. 表示忽略未經覈實的SSL證書認證 context = ssl._create_unverified_context() url = "https://www.12306.cn/mormhweb/" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"} request = urllib2.Request(url, headers = headers) # 3. 在urlopen()方法裏 指明添加 context 參數 response = urllib2.urlopen(request, context = context) print response.read()
CA(Certificate Authority)是數字證書認證中心的簡稱,是指發放、管理、廢除數字證書的受信任的第三方機構,如北京數字認證股份有限公司、上海市數字證書認證中心有限公司等...
CA的做用是檢查證書持有者身份的合法性,並簽發證書,以防證書被僞造或篡改,以及對證書和密鑰進行管理。
現實生活中能夠用身份證來證實身份, 那麼在網絡世界裏,數字證書就是身份證。和現實生活不一樣的是,並非每一個上網的用戶都有數字證書的,每每只有當一我的須要證實本身的身份的時候才須要用到數字證書。
普通用戶通常是不須要,由於網站並不關心是誰訪問了網站,如今的網站只關心流量。可是反過來,網站就須要證實本身的身份了。
好比說如今釣魚網站不少的,好比你想訪問的是www.baidu.com,但其實你訪問的是www.daibu.com」,因此在提交本身的隱私信息以前須要驗證一下網站的身份,要求網站出示數字證書。
通常正常的網站都會主動出示本身的數字證書,來確保客戶端和網站服務器之間的通訊數據是加密安全的。
opener是 urllib2.OpenerDirector 的實例,咱們以前一直都在使用的urlopen,它是一個特殊的opener(也就是模塊幫咱們構建好的)。
可是基本的urlopen()方法不支持代理、cookie等其餘的HTTP/HTTPS高級功能。因此要支持這些功能:
Handler處理器
來建立特定功能的處理器對象;urllib2.build_opener()
方法使用這些處理器對象,建立自定義opener對象;open()
方法發送請求。若是程序裏全部的請求都使用自定義的opener,可使用urllib2.install_opener()
將自定義的 opener 對象 定義爲 全局opener,表示若是以後凡是調用urlopen,都將使用這個opener(根據本身的需求來選擇)
import urllib2 # 構建一個HTTPHandler 處理器對象,支持處理HTTP請求 http_handler = urllib2.HTTPHandler() # 構建一個HTTPHandler 處理器對象,支持處理HTTPS請求 # http_handler = urllib2.HTTPSHandler() # 調用urllib2.build_opener()方法,建立支持處理HTTP請求的opener對象 opener = urllib2.build_opener(http_handler) # 構建 Request請求 request = urllib2.Request("http://www.baidu.com/") # 調用自定義opener對象的open()方法,發送request請求 response = opener.open(request) # 獲取服務器響應內容 print response.read()
這種方式發送請求獲得的結果,和使用urllib2.urlopen()
發送HTTP/HTTPS請求獲得的結果是同樣的。
若是在 HTTPHandler()增長 debuglevel=1
參數,還會將 Debug Log 打開,這樣程序在執行的時候,會把收包和發包的報頭在屏幕上自動打印出來,方便調試,有時能夠省去抓包的工做。
# 僅須要修改的代碼部分: # 構建一個HTTPHandler 處理器對象,支持處理HTTP請求,同時開啓Debug Log,debuglevel 值默認 0 http_handler = urllib2.HTTPHandler(debuglevel=1) # 構建一個HTTPHSandler 處理器對象,支持處理HTTPS請求,同時開啓Debug Log,debuglevel 值默認 0 https_handler = urllib2.HTTPSHandler(debuglevel=1)
使用代理IP,這是爬蟲/反爬蟲的第二大招,一般也是最好用的。
不少網站會檢測某一段時間某個IP的訪問次數(經過流量統計,系統日誌等),若是訪問次數多的不像正常人,它會禁止這個IP的訪問。
因此咱們能夠設置一些代理服務器,每隔一段時間換一個代理,就算IP被禁止,依然能夠換個IP繼續爬取。
urllib2中經過ProxyHandler來設置使用代理服務器,下面代碼說明如何使用自定義opener來使用代理:
#urllib2_proxy1.py import urllib2 # 構建了兩個代理Handler,一個有代理IP,一個沒有代理IP httpproxy_handler = urllib2.ProxyHandler({"http" : "124.88.67.81:80"}) nullproxy_handler = urllib2.ProxyHandler({}) proxySwitch = True #定義一個代理開關 # 經過 urllib2.build_opener()方法使用這些代理Handler對象,建立自定義opener對象 # 根據代理開關是否打開,使用不一樣的代理模式 if proxySwitch: opener = urllib2.build_opener(httpproxy_handler) else: opener = urllib2.build_opener(nullproxy_handler) request = urllib2.Request("http://www.baidu.com/") # 1. 若是這麼寫,只有使用opener.open()方法發送請求才使用自定義的代理,而urlopen()則不使用自定義代理。 response = opener.open(request) # 2. 若是這麼寫,就是將opener應用到全局,以後全部的,無論是opener.open()仍是urlopen() 發送請求,都將使用自定義代理。 # urllib2.install_opener(opener) # response = urlopen(request) print response.read()
免費的開放代理獲取基本沒有成本,咱們能夠在一些代理網站上收集這些免費代理,測試後若是能夠用,就把它收集起來用在爬蟲上面。
免費短時間代理網站舉例:
若是代理IP足夠多,就能夠像隨機獲取User-Agent同樣,隨機選擇一個代理去訪問網站。
import urllib2 import random proxy_list = [ {"http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"} ] # 隨機選擇一個代理 proxy = random.choice(proxy_list) # 使用選擇的代理構建代理處理器對象 httpproxy_handler = urllib2.ProxyHandler(proxy) opener = urllib2.build_opener(httpproxy_handler) request = urllib2.Request("http://www.baidu.com/") response = opener.open(request) print response.read()
可是,這些免費開放代理通常會有不少人都在使用,並且代理有壽命短,速度慢,匿名度不高,HTTP/HTTPS支持不穩定等缺點(免費沒好貨)。
因此,專業爬蟲工程師或爬蟲公司會使用高品質的私密代理,這些代理一般須要找專門的代理供應商購買,再經過用戶名/密碼受權使用(捨不得孩子套不到狼)。
HTTPPasswordMgrWithDefaultRealm()
類將建立一個密碼管理對象,用來保存 HTTP 請求相關的用戶名和密碼,主要應用兩個場景:
ProxyBasicAuthHandler()
)HTTPBasicAuthHandler()
)若是咱們使用以前的代碼來使用私密代理,會報 HTTP 407 錯誤,表示代理沒有經過身份驗證:
urllib2.HTTPError: HTTP Error 407: Proxy Authentication Required
因此咱們須要改寫代碼,經過:
HTTPPasswordMgrWithDefaultRealm()
:來保存私密代理的用戶密碼ProxyBasicAuthHandler()
:來處理代理的身份驗證。#urllib2_proxy2.py import urllib2 import urllib # 私密代理受權的帳戶 user = "mr_mao_hacker" # 私密代理受權的密碼 passwd = "sffqry9r" # 私密代理 IP proxyserver = "61.158.163.130:16816" # 1. 構建一個密碼管理對象,用來保存須要處理的用戶名和密碼 passwdmgr = urllib2.HTTPPasswordMgrWithDefaultRealm() # 2. 添加帳戶信息,第一個參數realm是與遠程服務器相關的域信息,通常沒人管它都是寫None,後面三個參數分別是 代理服務器、用戶名、密碼 passwdmgr.add_password(None, proxyserver, user, passwd) # 3. 構建一個代理基礎用戶名/密碼驗證的ProxyBasicAuthHandler處理器對象,參數是建立的密碼管理對象 # 注意,這裏再也不使用普通ProxyHandler類了 proxyauth_handler = urllib2.ProxyBasicAuthHandler(passwdmgr) # 4. 經過 build_opener()方法使用這些代理Handler對象,建立自定義opener對象,參數包括構建的 proxy_handler 和 proxyauth_handler opener = urllib2.build_opener(proxyauth_handler) # 5. 構造Request 請求 request = urllib2.Request("http://www.baidu.com/") # 6. 使用自定義opener發送請求 response = opener.open(request) # 7. 打印響應內容 print response.read()
有些Web服務器(包括HTTP/FTP等)訪問時,須要進行用戶身份驗證,爬蟲直接訪問會報HTTP 401 錯誤,表示訪問身份未經受權:
urllib2.HTTPError: HTTP Error 401: Unauthorized
若是咱們有客戶端的用戶名和密碼,咱們能夠經過下面的方法去訪問爬取:
import urllib import urllib2 # 用戶名 user = "test" # 密碼 passwd = "123456" # Web服務器 IP webserver = "http://192.168.199.107" # 1. 構建一個密碼管理對象,用來保存須要處理的用戶名和密碼 passwdmgr = urllib2.HTTPPasswordMgrWithDefaultRealm() # 2. 添加帳戶信息,第一個參數realm是與遠程服務器相關的域信息,通常沒人管它都是寫None,後面三個參數分別是 Web服務器、用戶名、密碼 passwdmgr.add_password(None, webserver, user, passwd) # 3. 構建一個HTTP基礎用戶名/密碼驗證的HTTPBasicAuthHandler處理器對象,參數是建立的密碼管理對象 httpauth_handler = urllib2.HTTPBasicAuthHandler(passwdmgr) # 4. 經過 build_opener()方法使用這些代理Handler對象,建立自定義opener對象,參數包括構建的 proxy_handler opener = urllib2.build_opener(httpauth_handler) # 5. 能夠選擇經過install_opener()方法定義opener爲全局opener urllib2.install_opener(opener) # 6. 構建 Request對象 request = urllib2.Request("http://192.168.199.107") # 7. 定義opener爲全局opener後,可直接使用urlopen()發送請求 response = urllib2.urlopen(request) # 8. 打印響應內容 print response.read()
Cookie 是指某些網站服務器爲了辨別用戶身份和進行Session跟蹤,而儲存在用戶瀏覽器上的文本文件,Cookie能夠保持登陸信息到用戶下次與服務器的會話。
HTTP是無狀態的面向鏈接的協議, 爲了保持鏈接狀態, 引入了Cookie機制 Cookie是http消息頭中的一種屬性,包括:
Cookie名字(Name) Cookie的值(Value) Cookie的過時時間(Expires/Max-Age) Cookie做用路徑(Path) Cookie所在域名(Domain), 使用Cookie進行安全鏈接(Secure)。 前兩個參數是Cookie應用的必要條件,另外,還包括Cookie大小(Size,不一樣瀏覽器對Cookie個數及大小限制是有差別的)。
Cookie由變量名和值組成,根據 Netscape公司的規定,Cookie格式以下:
Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE
Cookies在爬蟲方面最典型的應用是斷定註冊用戶是否已經登陸網站,用戶可能會獲得提示,是否在下一次進入此網站時保留用戶信息以便簡化登陸手續。
# 獲取一個有登陸信息的Cookie模擬登錄 import urllib2 # 1. 構建一個已經登陸過的用戶的headers信息 headers = { "Host":"www.renren.com", "Connection":"keep-alive", "Upgrade-Insecure-Requests":"1", "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language":"zh-CN,zh;q=0.8,en;q=0.6", # 便於終端閱讀,表示不支持壓縮文件 # Accept-Encoding: gzip, deflate, sdch, # 重點:這個Cookie是保存了密碼無需重複登陸的用戶的Cookie,這個Cookie裏記錄了用戶名,密碼(一般通過RAS加密) "Cookie": "anonymid=ixrna3fysufnwv; depovince=GW; _r01_=1; JSESSIONID=abcmaDhEdqIlM7riy5iMv; jebe_key=f6fb270b-d06d-42e6-8b53-e67c3156aa7e%7Cc13c37f53bca9e1e7132d4b58ce00fa3%7C1484060607478%7C1%7C1484060607173; jebecookies=26fb58d1-cbe7-4fc3-a4ad-592233d1b42e|||||; ick_login=1f2b895d-34c7-4a1d-afb7-d84666fad409; _de=BF09EE3A28DED52E6B65F6A4705D973F1383380866D39FF5; p=99e54330ba9f910b02e6b08058f780479; ap=327550029; first_login_flag=1; ln_uact=mr_mao_hacker@163.com; ln_hurl=http://hdn.xnimg.cn/photos/hdn521/20140529/1055/h_main_9A3Z_e0c300019f6a195a.jpg; t=214ca9a28f70ca6aa0801404dda4f6789; societyguester=214ca9a28f70ca6aa0801404dda4f6789; id=327550029; xnsid=745033c5; ver=7.0; loginfrom=syshome" } # 2. 經過headers裏的報頭信息(主要是Cookie信息),構建Request對象 urllib2.Request("http://www.renren.com/", headers = headers) # 3. 直接訪問renren主頁,服務器會根據headers報頭信息(主要是Cookie信息),判斷這是一個已經登陸的用戶,並返回相應的頁面 response = urllib2.urlopen(request) # 4. 打印響應內容 print response.read()
可是這樣作太過複雜,咱們先須要在瀏覽器登陸帳戶,而且設置保存密碼,而且經過抓包才能獲取這個Cookie,那有麼有更簡單方便的方法呢?
在Python處理Cookie,通常是經過cookielib
模塊和 urllib2模塊的HTTPCookieProcessor
處理器類一塊兒使用。
cookielib
模塊:主要做用是提供用於存儲cookie的對象
HTTPCookieProcessor
處理器:主要做用是處理這些cookie對象,並構建handler對象。
該模塊主要的對象有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。
CookieJar:管理HTTP cookie值、存儲HTTP請求生成的cookie、向傳出的HTTP請求添加cookie的對象。整個cookie都存儲在內存中,對CookieJar實例進行垃圾回收後cookie也將丟失。
FileCookieJar (filename,delayload=None,policy=None):從CookieJar派生而來,用來建立FileCookieJar實例,檢索cookie信息並將cookie存儲到文件中。filename是存儲cookie的文件名。delayload爲True時支持延遲訪問訪問文件,即只有在須要時纔讀取文件或在文件中存儲數據。
MozillaCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,建立與
Mozilla瀏覽器 cookies.txt兼容
的FileCookieJar實例。LWPCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,建立與
libwww-perl標準的 Set-Cookie3 文件格式
兼容的FileCookieJar實例。
其實大多數狀況下,咱們只用CookieJar(),若是須要和本地文件交互,就用 MozillaCookjar() 或 LWPCookieJar()
咱們來作幾個案例:
# urllib2_cookielibtest1.py import urllib2 import cookielib # 構建一個CookieJar對象實例來保存cookie cookiejar = cookielib.CookieJar() # 使用HTTPCookieProcessor()來建立cookie處理器對象,參數爲CookieJar()對象 handler=urllib2.HTTPCookieProcessor(cookiejar) # 經過 build_opener() 來構建opener opener = urllib2.build_opener(handler) # 4. 以get方法訪問頁面,訪問以後會自動保存cookie到cookiejar中 opener.open("http://www.baidu.com") ## 能夠按標準格式將保存的Cookie打印出來 cookieStr = "" for item in cookiejar: cookieStr = cookieStr + item.name + "=" + item.value + ";" ## 捨去最後一位的分號 print cookieStr[:-1]
咱們使用以上方法將Cookie保存到cookiejar對象中,而後打印出了cookie中的值,也就是訪問百度首頁的Cookie值。
運行結果以下:
BAIDUID=4327A58E63A92B73FF7A297FB3B2B4D0:FG=1;BIDUPSID=4327A58E63A92B73FF7A297FB3B2B4D0;H_PS_PSSID=1429_21115_17001_21454_21409_21554_21398;PSTM=1480815736;BDSVRTM=0;BD_HOME=0
# urllib2_cookielibtest2.py import cookielib import urllib2 # 保存cookie的本地磁盤文件名 filename = 'cookie.txt' # 聲明一個MozillaCookieJar(有save實現)對象實例來保存cookie,以後寫入文件 cookiejar = cookielib.MozillaCookieJar(filename) # 使用HTTPCookieProcessor()來建立cookie處理器對象,參數爲CookieJar()對象 handler = urllib2.HTTPCookieProcessor(cookiejar) # 經過 build_opener() 來構建opener opener = urllib2.build_opener(handler) # 建立一個請求,原理同urllib2的urlopen response = opener.open("http://www.baidu.com") # 保存cookie到本地文件 cookiejar.save()
# urllib2_cookielibtest2.py import cookielib import urllib2 # 建立MozillaCookieJar(有load實現)實例對象 cookiejar = cookielib.MozillaCookieJar() # 從文件中讀取cookie內容到變量 cookie.load('cookie.txt') # 使用HTTPCookieProcessor()來建立cookie處理器對象,參數爲CookieJar()對象 handler = urllib2.HTTPCookieProcessor(cookiejar) # 經過 build_opener() 來構建opener opener = urllib2.build_opener(handler) response = opener.open("http://www.baidu.com")
import urllib import urllib2 import cookielib # 1. 構建一個CookieJar對象實例來保存cookie cookie = cookielib.CookieJar() # 2. 使用HTTPCookieProcessor()來建立cookie處理器對象,參數爲CookieJar()對象 cookie_handler = urllib2.HTTPCookieProcessor(cookie) # 3. 經過 build_opener() 來構建opener opener = urllib2.build_opener(cookie_handler) # 4. addheaders 接受一個列表,裏面每一個元素都是一個headers信息的元祖, opener將附帶headers信息 opener.addheaders = [("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36")] # 5. 須要登陸的帳戶和密碼 data = {"email":"mr_mao_hacker@163.com", "password":"alaxxxxxime"} # 6. 經過urlencode()轉碼 postdata = urllib.urlencode(data) # 7. 構建Request請求對象,包含須要發送的用戶名和密碼 request = urllib2.Request("http://www.renren.com/PLogin.do", data = postdata) # 8. 經過opener發送這個請求,並獲取登陸後的Cookie值, opener.open(request) # 9. opener包含用戶登陸後的Cookie值,能夠直接訪問那些登陸後才能夠訪問的頁面 response = opener.open("http://www.renren.com/410043129/profile") # 10. 打印響應內容 print response.read()
模擬登陸要注意幾點:
- 登陸通常都會先有一個HTTP GET,用於拉取一些信息及得到Cookie,而後再HTTP POST登陸。
- HTTP POST登陸的連接有多是動態的,從GET返回的信息中獲取。
- password 有些是明文發送,有些是加密後發送。有些網站甚至採用動態加密的,同時包括了不少其餘數據的加密信息,只能經過查看JS源碼得到加密算法,再去破解加密,很是困難。
- 大多數網站的登陸總體流程是相似的,可能有些細節不同,因此不能保證其餘網站登陸成功。
在咱們用urlopen或opener.open
方法發出一個請求時,若是urlopen或opener.open
不能處理這個response,就產生錯誤。
這裏主要說的是URLError和HTTPError,以及對它們的錯誤處理。
URLError 產生的緣由主要有:
- 沒有網絡鏈接
- 服務器鏈接失敗
- 找不到指定的服務器
咱們能夠用try except
語句來捕獲相應的異常。下面的例子裏咱們訪問了一個不存在的域名:
# urllib2_urlerror.py import urllib2 requset = urllib2.Request('http://www.ajkfhafwjqh.com') try: urllib2.urlopen(request, timeout=5) except urllib2.URLError, err: print err
運行結果以下:
<urlopen error [Errno 8] nodename nor servname provided, or not known>
urlopen error,錯誤代碼8,錯誤緣由是沒有找到指定的服務器。
HTTPError是URLError的子類,咱們發出一個請求時,服務器上都會對應一個response應答對象,其中它包含一個數字"響應狀態碼"。
若是urlopen或opener.open
不能處理的,會產生一個HTTPError,對應相應的狀態碼,HTTP狀態碼錶示HTTP協議所返回的響應的狀態。
注意,urllib2能夠爲咱們處理重定向的頁面(也就是3開頭的響應碼),100-299範圍的號碼錶示成功,因此咱們只能看到400-599的錯誤號碼。
# urllib2_httperror.py import urllib2 requset = urllib2.Request('http://blog.baidu.com/itcast') try: urllib2.urlopen(requset) except urllib2.HTTPError, err: print err.code print err
運行結果以下:
404 HTTP Error 404: Not Found
HTTP Error,錯誤代號是404,錯誤緣由是Not Found,說明服務器沒法找到被請求的頁面。
一般產生這種錯誤的,要麼url不對,要麼ip被封。
因爲HTTPError的父類是URLError,因此父類的異常應當寫到子類異常的後面,因此上述的代碼能夠這麼改寫:
# urllib2_botherror.py import urllib2 requset = urllib2.Request('http://blog.baidu.com/itcast') try: urllib2.urlopen(requset) except urllib2.HTTPError, err: print err.code except urllib2.URLError, err: print err else: print "Good Job"
運行結果以下:
404
1xx:信息 100 Continue 服務器僅接收到部分請求,可是一旦服務器並無拒絕該請求,客戶端應該繼續發送其他的請求。 101 Switching Protocols 服務器轉換協議:服務器將聽從客戶的請求轉換到另一種協議。 2xx:成功 200 OK 請求成功(其後是對GET和POST請求的應答文檔) 201 Created 請求被建立完成,同時新的資源被建立。 202 Accepted 供處理的請求已被接受,可是處理未完成。 203 Non-authoritative Information 文檔已經正常地返回,但一些應答頭可能不正確,由於使用的是文檔的拷貝。 204 No Content 沒有新文檔。瀏覽器應該繼續顯示原來的文檔。若是用戶按期地刷新頁面,而Servlet能夠肯定用戶文檔足夠新,這個狀態代碼是頗有用的。 205 Reset Content 沒有新文檔。但瀏覽器應該重置它所顯示的內容。用來強制瀏覽器清除表單輸入內容。 206 Partial Content 客戶發送了一個帶有Range頭的GET請求,服務器完成了它。 3xx:重定向 300 Multiple Choices 多重選擇。連接列表。用戶能夠選擇某連接到達目的地。最多容許五個地址。 301 Moved Permanently 所請求的頁面已經轉移至新的url。 302 Moved Temporarily 所請求的頁面已經臨時轉移至新的url。 303 See Other 所請求的頁面可在別的url下被找到。 304 Not Modified 未按預期修改文檔。客戶端有緩衝的文檔併發出了一個條件性的請求(通常是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。服務器告訴客戶,原來緩衝的文檔還能夠繼續使用。 305 Use Proxy 客戶請求的文檔應該經過Location頭所指明的代理服務器提取。 306 Unused 此代碼被用於前一版本。目前已再也不使用,可是代碼依然被保留。 307 Temporary Redirect 被請求的頁面已經臨時移至新的url。 4xx:客戶端錯誤 400 Bad Request 服務器未能理解請求。 401 Unauthorized 被請求的頁面須要用戶名和密碼。 401.1 登陸失敗。 401.2 服務器配置致使登陸失敗。 401.3 因爲 ACL 對資源的限制而未得到受權。 401.4 篩選器受權失敗。 401.5 ISAPI/CGI 應用程序受權失敗。 401.7 訪問被 Web 服務器上的 URL 受權策略拒絕。這個錯誤代碼爲 IIS 6.0 所專用。 402 Payment Required 此代碼尚沒法使用。 403 Forbidden 對被請求頁面的訪問被禁止。 403.1 執行訪問被禁止。 403.2 讀訪問被禁止。 403.3 寫訪問被禁止。 403.4 要求 SSL。 403.5 要求 SSL 128。 403.6 IP 地址被拒絕。 403.7 要求客戶端證書。 403.8 站點訪問被拒絕。 403.9 用戶數過多。 403.10 配置無效。 403.11 密碼更改。 403.12 拒絕訪問映射表。 403.13 客戶端證書被吊銷。 403.14 拒絕目錄列表。 403.15 超出客戶端訪問許可。 403.16 客戶端證書不受信任或無效。 403.17 客戶端證書已過時或還沒有生效。 403.18 在當前的應用程序池中不能執行所請求的 URL。這個錯誤代碼爲 IIS 6.0 所專用。 403.19 不能爲這個應用程序池中的客戶端執行 CGI。這個錯誤代碼爲 IIS 6.0 所專用。 403.20 Passport 登陸失敗。這個錯誤代碼爲 IIS 6.0 所專用。 404 Not Found 服務器沒法找到被請求的頁面。 404.0 沒有找到文件或目錄。 404.1 沒法在所請求的端口上訪問 Web 站點。 404.2 Web 服務擴展鎖定策略阻止本請求。 404.3 MIME 映射策略阻止本請求。 405 Method Not Allowed 請求中指定的方法不被容許。 406 Not Acceptable 服務器生成的響應沒法被客戶端所接受。 407 Proxy Authentication Required 用戶必須首先使用代理服務器進行驗證,這樣請求才會被處理。 408 Request Timeout 請求超出了服務器的等待時間。 409 Conflict 因爲衝突,請求沒法被完成。 410 Gone 被請求的頁面不可用。 411 Length Required "Content-Length" 未被定義。若是無此內容,服務器不會接受請求。 412 Precondition Failed 請求中的前提條件被服務器評估爲失敗。 413 Request Entity Too Large 因爲所請求的實體的太大,服務器不會接受請求。 414 Request-url Too Long 因爲url太長,服務器不會接受請求。當post請求被轉換爲帶有很長的查詢信息的get請求時,就會發生這種狀況。 415 Unsupported Media Type 因爲媒介類型不被支持,服務器不會接受請求。 416 Requested Range Not Satisfiable 服務器不能知足客戶在請求中指定的Range頭。 417 Expectation Failed 執行失敗。 423 鎖定的錯誤。 5xx:服務器錯誤 500 Internal Server Error 請求未完成。服務器遇到不可預知的狀況。 500.12 應用程序正忙於在 Web 服務器上從新啓動。 500.13 Web 服務器太忙。 500.15 不容許直接請求 Global.asa。 500.16 UNC 受權憑據不正確。這個錯誤代碼爲 IIS 6.0 所專用。 500.18 URL 受權存儲不能打開。這個錯誤代碼爲 IIS 6.0 所專用。 500.100 內部 ASP 錯誤。 501 Not Implemented 請求未完成。服務器不支持所請求的功能。 502 Bad Gateway 請求未完成。服務器從上游服務器收到一個無效的響應。 502.1 CGI 應用程序超時。 · 502.2 CGI 應用程序出錯。 503 Service Unavailable 請求未完成。服務器臨時過載或當機。 504 Gateway Timeout 網關超時。 505 HTTP Version Not Supported 服務器不支持請求中指明的HTTP協議版本