python網絡爬蟲學習筆記
By 鐘桓
9月 4 2014 更新日期:9月 4 2014
文章檔案夾
- 1. 介紹:
- 2. 從簡單語句中開始:
- 3. 傳送資料給server
- 4. HTTP頭—描寫叙述資料的資料
- 5. 異常
- 5.0.1. URLError
- 5.0.2. HTTPError
- 5.0.3. 處理異常
- 5.0.4. info和geturl
6. Opener和Handler 7. Basic Authentication 8. 代理 9. Timeout 設定 10. Cookie 11. Debug Log 12. 參考資料:
介紹:
網絡爬蟲的名字非常有意思,英文名稱web spider。
真得非常形象。蜘蛛結網為了擷取食物,而我們的爬蟲程式,也是為了擷取網絡上的資源。
這篇blog是本人學習過程中的記錄。學習過程中,使用的語言是python2.7;python2.7有兩個子產品,urllib和urllib2,這兩個子產品提供了非常好的網絡訪問的功能。以下會更好的體會。值得一提的時,在python3中。将urllib和urllib2這兩個子產品合為一個urllib。感興趣的能夠看這裡
urllib和urllib2是python中功能強大得網絡工作庫,它們讓你的網絡訪問像檔案訪問一樣(比如。檔案訪問我們要先open()一個檔案。它的操作也是類似的,後面就會看到樣例)。之是以可以這麼友善。由于這些子產品的内部非常好的使用不同的網絡協定來完畢這些功能,(學過網絡應該了解,訪問一個網頁這個簡單的過程,事實上涉及到非常多的網絡協定,像http。dns等等,而urllib和urllib2封裝了這些協定。讓我們不用和它們打交道,僅僅須要調用這些子產品的方法來完畢我們須要的功能)。同一時候,這些子產品也提供一些略微更複雜一點的借口來處理一些情形,比如使用者認證,cookies和代理等等。
以下讓我們開始來學習它們吧。
從簡單語句中開始:
前面說過。使用兩個子產品,訪問網頁變得就會像訪問檔案一樣友善。
在普通情況下。urllib2訪問會更好些(效率上更好,隻是urllib還是須要使用,後面會介紹須要urllib做一些事情)。是以以下我們來看看使用urllib2的最簡單的樣例。
import urllib2;
response = urllib2.urlopen("http://www.zhonghuan.info");
html = response.read();
print html;
在終端下下輸入指令行 python test.py > zhonghuan.html ,
打開檔案後顯示的是我的個人blog首頁的html代碼:

這是最簡單的一個利用urllib2訪問網頁的樣例。urllib2是依據URL中:前面的部分來推斷是用什麼協定訪問的,比如,上面的樣例我們用的時http,這裡也能夠換成ftp:,file:,等。。。
我們能夠不用去了解它内部是怎樣封裝這些網絡協定的。
urllib2中能夠用一個鏡像對象(Request Object)來表示我們http訪問,它标示你想要訪問的URL位址,我們來看一下以下的樣例。
import urllib2
req = urllib2.Request('http://www.zhonghuan.info')
response = urllib2.urlopen(req)
the_page = response.read()
print(the_page)
req變量就是一個Request對象。它确切的标示了你要訪問的URL位址。
(這裡是http://www.zhonghuan.info)。對于其他的形式的訪問,比如ftp和file。形式也是類似的。詳細能夠看[這裡][2];
事實上。Request對象還能做兩個額外的事情。
- 你能夠發送資料給server。
- 你能夠發送一些額外的資訊(又叫中繼資料。描寫叙述資料的資料。一些語言裡面的元類是生成類的類,如python就在這些語言中;是以中繼資料,顧名思義。描寫叙述資料的資料,那麼這些被描寫叙述的資料是什麼呢?上面中,還有request對象的一些資訊。而這些描寫叙述被放在http頭部發送出去了。有關http header,能夠看這裡);
傳送資料給server
有時候,你須要發送資料給server,這個位址是URL表示的。通常呢。這個位址的指向是CGI(Common Gateway Interface)腳本或者是其他一些網絡應用。(關于CGI腳本,能夠看這裡。簡單的說就是處理上傳資料的腳本程式)。
在HTTP訪問中。通常使用哪個POST方式将資料發送出去,就好像你填完了html中得表單,你須要把表單中得資料發送出去。通常這裡使用post請求。當然,post使用還有其他的情況。不單單指的是表單這一種情況。
讓我們先看以下的代碼:
import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
data = urllib.urlencode(values) #資料須要又一次編碼成合适的格式,這裡使用的時urllib中得方法,由于urllib2中沒有編碼的方法
req = urllib2.Request(url, data) # #這裡将須要上傳的資料。傳遞給了equest對象,作為它的參數
response = urllib2.urlopen(req)
the_page = response.read()
關于其他類型的資料上傳,能夠看這裡
除了使用post方式上傳資料外,還能夠使用get方式上傳資料,get上傳和post上傳明顯的差别就是get上傳的資料會在URL中得尾部顯示出來。
能夠看以下的代碼:
import urllib
import urllib2
data = {}
data['name'] = 'Somebody Here'
data['location'] = 'Northampton'
data['language'] = 'Python'
url_values = urllib.urlencode(data)
print url_values # 這裡的順序不一定
url = 'http://www.example.com/example.cgi'
full_url = url + '?' + url_values
data = urllib2.urlopen(full_url)
能夠悄悄列印出來url_value的形式。
HTTP頭—描寫叙述資料的資料
如今。我們來讨論一下HTTP頭。來看看怎樣在你的HTTP的Request對象,添加一個HTTP頭。
有一些站點,它比較智能,它不喜歡被程式訪問(非人為的點選僅僅會加重它server的負擔)。
或者有些站點更加智能點。對于不同的浏覽器,會發送不同的網頁資料。
但是呢,urllib2預設,會這樣标示自己,
Python-urllib/x.y
(當中。x和y各自是大小版本,比如我如今使用的時
Python-urllib/2.7
);而這些資料可能會讓一些網站認為迷惑。要是遇上了不喜歡被程式訪問的網站,那麼這種訪問可能會直接被忽視。
是以,你能夠構造一些身份。讓網站不會拒絕你。看以下的樣例。
import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' #user_agent用來标示你的浏覽器的,向這裡就是mozilla
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
headers = { 'User-Agent' : user_agent }
data = urllib.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
the_page = response.read()
異常
異常時常有,要小心提防呐。想想一般檔案操作的時候,會有什麼異常呢?檔案無法打開,什麼權限不夠啊。檔案不存在啊等等異常。相同的。對于URL訪問。也會遇到這些問題,(python一些内部異常,比如ValueError,TypeError等異常。也可能會發生)
URLError
先說說URLError。當沒有網絡連接配接,或者訪問的server位址不存在的時候,在這樣的情況下。URLError會被抛出來,這個時候,URLError異常會有個“reason”屬性。它是一個元組。包括error code(int型)和text error message(string型),看以下的代碼
import urllib
import urllib2
req = urllib2.Request('http://www.pretend_server.org')
try: urllib2.urlopen(req)
except urllib2.URLError as e:
print e.reason
輸出
[Errno 8] nodename nor servname provided, or not known
;輸出的内容就是reason。
HTTPError
每個HTTP訪問,會從server那兒獲得一個“status code”(狀态碼)。通常這些狀态碼告訴我們server無法滿足一些訪問(直白點說就是一些資料而已,僅僅隻是表示的是目前訪問的狀态,比方目前訪問被拒絕了,status code能夠告訴你,你哪些舉動過分了。出格了。注意,鹹豬腳不能夠有啊~~)。
隻是呢,urllib2的預設處理器可以幫助你處理一些server的響應,比方說你目前訪問的網址被server重定向了,就是說你的server給你一個新的URL訪問了,處理器會幫你直接去訪問新的URL。
可是預設的處理器畢竟功能有限。它不能幫助你解決全部問題。比方你訪問的站點不存在了(相應404錯誤。我們有時候會看到這個錯誤),或者你的訪問被禁止了(相應403錯誤,禁止的原因可能是由于你的權限不夠啦等等),又或者是須要你驗證啦(相應401)。詳細的其他錯誤本文就不介紹啦,詳細能夠看這裡
讓我們看以下的程式,看一下當HTTPError的404錯誤,也就是頁面不存在時候。它會輸出點什麼。
import urllib
import urllib2
req = urllib2.Request('http://www.zhonghuan.info/no_way')
try: urllib2.urlopen(req)
except urllib2.HTTPError as e:
print e.code;
print e.read();
輸出:
404
<!DOCTYPE html>
…
<title>Page not found · GitHub Pages</title>
…
處理異常
如果你想要捕捉HTTPError和URLError,有兩種主要的方法。推薦另外一種噢!
第一種:
from urllib2 import Request, urlopen, URLError, HTTPError
req = Request(http://zhonghuan.info)
try:
response = urlopen(req)
except HTTPError as e:
print 'The server couldn\'t fulfill the request.'
print 'Error code: ', e.code
except URLError as e:
print 'We failed to reach a server.'
print 'Reason: ', e.reason
else:
# everything is fine
第一種方法。HTTPError一定要放在URLError前面,原因呢,和非常多語言的異常處理機制一樣,HTTPError是URLError的子類,假設發生了HTTPError。它能夠被當做是URLError被捕捉。
另外一種:
from urllib2 import Request, urlopen, URLError
req = Request(someurl)
try:
response = urlopen(req)
except URLError as e:
if hasattr(e, 'reason'):
print 'We failed to reach a server.'
print 'Reason: ', e.reason
elif hasattr(e, 'code'):
print 'The server couldn\'t fulfill the request.'
print 'Error code: ', e.code
else:
# everything is fine
info和geturl
這裡介紹兩個方法info()和geturl();
geturl():該方法會傳回訪問的頁面的真實的URL,它的價值在于我們訪問的網頁可能會被重定向,是以導緻訪問的URL和我們輸入的可能不一樣。看以下的樣例:
import urllib
import urllib2
url = 'http://weibo.com/u/2103243911';
req = urllib2.Request(url);
response = urllib2.urlopen(req)
print "URL:",url;
print "After redirection:",response.geturl();
以我的微網誌個人首頁為例,事實上真實訪問被重定向了,真實的網址,從輸出中能夠看出:
URL: http://weibo.com/u/2103243911
After redirection: http://passport.weibo.com/visitor/visitor?
a=enter&url=http%3A%2F%2Fweibo.com%2Fu%2F2103243911&_rand=1409761358.1794
info():能夠得到描寫叙述頁面的資訊,傳回的是一個
httplib.HTTPMessage
執行個體,列印出來非常像字典。
看以下的代碼:
import urllib
import urllib2
url = 'http://zhonghuan.info';
req = urllib2.Request(url);
response = urllib2.urlopen(req);
print response.info();
print response.info().__class__;
輸出:
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Last-Modified: Tue, 02 Sep 2014 17:01:39 GMT
Expires: Wed, 03 Sep 2014 15:23:02 GMT
Cache-Control: max-age=600
Content-Length: 4784
Accept-Ranges: bytes
Date: Wed, 03 Sep 2014 16:38:29 GMT
Via: 1.1 varnish
Age: 5127
Connection: close
X-Served-By: cache-lax1433-LAX
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1409762309.465760,VS0,VE0
Vary: Accept-Encoding
Class: httplib.HTTPMessage
Opener和Handler
這裡介紹Opener和Handler。
什麼是Opener呢?事實上上面的樣例我們一直在用Opener了,就是urlopen。這個是預設的opener。網絡訪問情況非常多,你能夠建立比較合适的opener,
什麼是Handler呢?事實上Opener會調用Handler來處理訪問中得瑣事,是以Handler非常重要,對于特定的協定(比如FTP,HTTP)。它知道怎樣怎樣處理訪問。比如它會幫你處理重定向問題。
在訪問的時候。你可能對于Opener有一些要求,比如,你希望得到的Opener可以處理cookie,或者你不希望Opener幫助你處理重定向。
我們怎樣生成須要得Opener呢?(這裡插一下,個人認為這裡的Opener生成方式,和設計模式中得生成器歐式,又叫建造者模式,英文名稱Builder Pattern;有些相似,隻是不全然一樣,但總認為,在看下去之前。先了解一下這個模式會有優點。沒有接觸過的朋友能夠看這篇Builder pattern);
要建立一個 opener,能夠執行個體化一個OpenerDirector,
然後調用.add_handler(some_handler_instance)。
隻是,能夠使用build_opener。這是一個更加友善的函數,用來建立opener對象,他僅僅須要一次函數調用。build_opener預設加入幾個處理器,但提供快捷的方法來加入或更新預設處理器。
其它的處理器handlers你也許會希望處理代理,驗證,和其它經常使用但有點特殊的情況。
剛剛提到handler會幫我們處理重定向,可是。假設我們不想要重定向呢,該怎麼辦,自己定義一個handler。
看以下的代碼:
mport urllib
import urllib2
class RedirectHandler(urllib2.HTTPRedirectHandler):# 這個RedirectHandler繼承了HTTPRedirectHandler,隻是,它覆寫了父類的方法,讓它什麼都不做。失去了重定向的功能。
def http_error_301(self, req, fp, code, msg, headers):
pass
def http_error_302(self, req, fp, code, msg, headers):
pass
webo = "http://weibo.com/u/2103243911"; #訪問的是我的微網誌頁面,由于正常情況下,訪問時會發生重定向
opener = urllib2.build_opener(RedirectHandler) #這裡,我們自己定義了一個opener,加入了一個重定向時處理的自己定義handler
response = opener.open(webo);# response = urllib2.urlopen(webo);
print response.geturl();
urllib2.install_opener(opener); #安裝自己定義的opener,以後調用urllib2的時候,傳回的就是這個opener。
輸出結果是:
urllib2.HTTPError: HTTP Error 302: Moved Temporarily
之是以發生http error 302,是由于本來訪問我的微網誌個人首頁的時候。它應該發生重定向的,但是我們自己的重定向Handler什麼都不做,結果就是發生異常了。
能夠看看以下的urllib2關于建立自己定義Opener的類圖
Basic Authentication
假設一個站點。他提供注冊登入這些功能。那麼一般它實username/password,假設你訪問的頁面,系統要求你提供username/password,這個過程叫做Authentication,實在server那端所做的操作。它給一些頁面提供了安全保護。
一個主要的Authentication(驗證)過程是這種:
- client提出請求訪問某些頁面。
- server傳回一個錯誤,要求進行身份驗證。
- client把username/password(一般這樣)編碼後發給server。
- server檢查這對username/password是否正确,然後傳回使用者請求的頁面或者是一些錯誤。
上面的過程。還有可能是其他形式,這裡僅僅是舉個比較普遍的。
通常server傳回的是401錯誤,表明訪問的網頁未授權。同一時候。傳回的response的header内有形如
WWW-Authenticate: SCHEME realm="REALM".
的内容,比如,你想要訪問cPanel的管理應用程式,你會收到這種header:
WWW-Authenticate: Basic realm="cPanel"
(cPanel 是一套在網頁寄存業中最享負盛名的商業軟體。其基于 Linux 和 BSD 系統及以 PHP 開發且性質為閉源軟體;cPanel 主要是面向客戶權級的控制系統)
當我們訪問頁面的時候,opener會調用handler來處理各種情況。而處理Authentication的handler是urllib2.HTTPBasicAuthHandler。同一時候須要一個使用者password管理器urllib2.HTTPPasswordMgr。
不幸的時。HTTPPasswordMgr有一個小問題,就是在擷取網頁前,你須要知道它的realm。幸運的是,它有一個表兄弟HTTPPasswordMgrWithDefaultRealm,這個表兄弟能夠事先不知道realm,在realm參數位置上,能夠傳一個None進去。它更友好的被使用。
以下參考以下的代碼:
import urllib2
url = 'http://www.weibo.com'#分别相應域名,賬号。密碼
username = 'zhonghuan'
password = 'forget_it'
passman = urllib2.HTTPPasswordMgrWithDefaultRealm() #建立密碼管理器
passman.add_password(None, url, username, password)# 參數形式(realm,URL,UserName,Password)
authhandler = urllib2.HTTPBasicAuthHandler(passman)#建立Authentication的handler
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener) #和上面介紹的一樣。install_opener後,每次調用urllib2的urlopen,傳回的就是這個opener
pagehandle = urllib2.urlopen(url)
代理
有時候,我們本機不能直接訪問,須要代理server去訪問。urllib2對這個設定代理支援的還不錯,能夠直接執行個體化ProxyHandler,它的參數是一個map,key值是代理的訪問協定名稱,value值是代理的位址。看以下的代碼實作。
import urllib2
enable_proxy = True
proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080'})
null_proxy_handler = urllib2.ProxyHandler({})
if enable_proxy:
opener = urllib2.build_opener(proxy_handler)
else:
opener = urllib2.build_opener(null_proxy_handler)
urllib2.install_opener(opener)
Timeout 設定
在老版 Python 中,urllib2 的 API 并沒有暴露 Timeout 的設定,要設定 Timeout 值,僅僅能更改 Socket 的全局 Timeout 值。
import urllib2
import socket
socket.setdefaulttimeout(10) # 10 秒鐘後逾時
urllib2.socket.setdefaulttimeout(10) # 還有一種方式
在 Python 2.6 以後,逾時能夠通過 urllib2.urlopen() 的 timeout 參數直接設定。
import urllib2
response = urllib2.urlopen('http://www.google.com', timeout=10)
Cookie
urllib2 對 Cookie 的處理也是自己主動的。假設須要得到某個 Cookie 項的值,能夠這麼做:
import urllib2
import cookielib
cookie = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
response = opener.open('http://www.google.com')
for item in cookie:
if item.name == 'some_cookie_item_name':
print item.value
Debug Log
使用 urllib2 時,能夠通過以下的方法把 debug Log 打開,這樣收發包的内容就會在螢幕上列印出來。友善調試。有時能夠省去抓包的工作
import urllib2
httpHandler = urllib2.HTTPHandler(debuglevel=1)
httpsHandler = urllib2.HTTPSHandler(debuglevel=1)
opener = urllib2.build_opener(httpHandler, httpsHandler)
urllib2.install_opener(opener)
response = urllib2.urlopen('http://www.google.com')
參考資料:
- python urllib2的使用 (推薦)
- python網絡爬蟲新手教程 (推薦)
- CGI腳本入門 (推薦)
- urllib2 源代碼小剖
- Python 标準庫 urllib2 的使用細節 (推薦)
- Authentication with Python (推薦)
- http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
版權聲明:本文部落客原創文章。部落格,未經同意不得轉載。