Python模塊之urllib和urllib2結合使用

簡介:
urllib2
python的一個獲取urlUniform Resource Locators,統一資源定址器)的模塊。它用urlopen函數的形式提供了一個非常簡潔的接口。這使得用各種各樣的協議獲取url成爲可能。它同時 也提供了一個稍微複雜的接口來處理常見的狀況-如基本的認證,cookies,代理,等等。這些都是由叫做openerhandler的對象來處理的。

以下是獲取url最簡單的方式:

import urllib2
response = urllib2.urlopen('http://python.org/')
html = response.read()

許多urlib2的使用都是如此簡單(注意我們本來也可以用一個以”ftp:”"file等開頭的url取代”HTTP”開頭的url. 而,這篇教程的目的是解釋關於HTTP更復雜的情形。HTTP建基於請求和迴應(requests &responses -客戶端製造請求服務器返回迴應。urlib2用代 表了你正在請求的HTTP requestRequest對象反映了這些。用它最簡單的形式,你建立了一個Request對象來明確指明你想要獲取的url。調用urlopen 數對請求的url返回一個respons對象。這個respons是一個像file的對象,這意味着你能用.read()函數操作這個respon對象:

import urllib2

req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)
the_page = response.read()

注意urlib2利用了同樣的Request接口來處理所有的url協議。例如,你可以像這樣請求一個ftpRequest

req = urllib2.Request('ftp://example.com/')

對於HTTPRequest對象允許你做兩件額外的事:第一,你可以向服務器發送數據。第二,你可以向服務器發送額外的信息(metadata),這些信息可以是關於數據本身的,或者是關於這個請求本身的這些信息被當作HTTP頭髮送。讓我們依次看一下這些。

數據:
有時你想向一個URL發送數據(通常這些數據是代表一些CGI腳本或者其他的web應用)。對於HTTP,這通常叫做一個Post。當你發送一個你 在網上填的form(表單)時,這通常是你的瀏覽器所做的。並不是所有的Post請求都來自HTML表單,這些數據需要被以標準的方式encode,然後 作爲一個數據參數傳送給Request對象。Encoding是在urlib中完成的,而不是在urlib2中完成的。

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)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()

如果你不傳送數據參數,urlib2使用了一個GET請求。一個GET請求和POST請求的不同之處在於POST請求通常具有邊界效應:它們以某種 方式改變系統的狀態。(例如,通過網頁設置一條指令運送一英擔罐裝牛肉到你家。)雖然HTTP標準清楚的說明Post經常產生邊界效應,而get從不產生 邊界效應,但沒有什麼能阻止一個get請求產生邊界效應,或一個Post請求沒有任何邊界效應。數據也能被url自己加密(Encoding)然後通過一 get請求發送出去。

這通過以下實現:
>>> import urllib2
>>> import urllib
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.urlencode(data)
>>> print url_values
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib2.open(full_url)

頭:
我們將會在這裏討論一個特殊的HTTP頭,來闡釋怎麼向你的HTTP請求中加入頭。
有一些網站不希望被某些程序瀏覽或者針對不同的瀏覽器返回不同的版本。默認情況下,urlib2把自己識別爲Python-urllib/x.y(這裏的 xypython發行版的主要或次要的版本號,如, Python-urllib/2.5),這些也許會混淆站點,或者完全不工作。瀏覽器區別自身的方式是通過User-Agent頭。當你建立一個 Request對象時,你可以加入一個頭字典。接下來的這個例子和上面的請求一樣,不過它把自己定義爲IE的一個版本。

import urllib
import urllib2

url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
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()

Respons同樣有兩種有用的方法。當我們出差錯之後,看一下關於info and geturl的部分。

異常處理:

不能處理一個respons時,urlopen拋出一個urlerror(雖然像平常一樣對於python APIs,內建異常如,ValueError, TypeError等也會被拋出。)
HTTPerror
HTTP URL在特別的情況下被拋出的URLError的一個子類。
urlerror

通常,urlerror被拋出是因爲沒有網絡連接(沒有至特定服務器的連接)或者特定的服務器不存在。在這種情況下,含有reason屬性的異常將被拋出,以一種包含錯誤代碼和文本錯誤信息的tuple形式。

e.g.
>>> req = urllib2.Request('http://www.pretend_server.org')
>>> try: urllib2.urlopen(req)
>>> except URLError, e:
>>> print e.reason
>>>
(4, 'getaddrinfo failed')

當一個錯誤被拋出的時候,服務器返回一個HTTP錯誤代碼和一個錯誤頁。你可以使用返回的HTTP錯誤示例。這意味着它不但具有code屬性,而且 同時具有readgeturl,和infomethods屬性。>>> req = urllib2.Request('http://www.python.org/fish.html')>>> try:>>> urllib2.urlopen(req)>>> except URLError, e:>>> print e.code>>> print e.read()>>>404...... etc

容錯:
如果你準備處理HTTP錯誤和URL錯誤這裏有兩種基本的方法,我更傾向於後一種:

1.
from urllib2 import Request, urlopen, URLError, HTTPError
req = Request(someurl)
try:
response = urlopen(req)
except HTTPError, e:
print 'The server couldn/'t fulfill the request.'
print 'Error code: ', e.code
except URLError, e:
print 'We failed to reach a server.'
print 'Reason: ', e.reason
else:
# everything is fine

注意:HTTP錯誤異常必須在前面,否則URL錯誤也會捕獲一個HTTP錯誤。
2
from urllib2 import Request, urlopen, URLError
req = Request(someurl)
try:
response = urlopen(req)
except URLError, 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

注意:URL錯誤是IO錯誤異常的一個子類。這意味着你能避免引入(importURL錯誤而使用:

from urllib2 import Request, urlopen
req = Request(someurl)
try:
response = urlopen(req)
except IOError, 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

極少數環境下,urllib2能夠拋出socket.error.

INFO and GETURL
urlopen
返回的response(或者HTTP錯誤實例)有兩個有用的方法:infogeturl

geturl–它返回被獲取網頁的真正的url。這是很有用的,因爲urlopen(或使用的opener對象)也許會伴隨一個重定向。
獲取的網頁url也許和要求的網頁url不一樣。

info–它返回一個像字典的對象來描述獲取的網頁,尤其是服務器發送的頭。它現在一般是httplib.HTTPMessage的一個實例。
典型的頭包含'Content-length', 'Content-type', 等等。看一下Quick Reference to HTTP Headers中,HTTP頭列表,還有
關於他們簡單的解釋和使用方法。
Openers 
Handlers
當你獲取一個URL時,你使用一個opener(一個可能以一個比較迷糊名字命名的實例–urllib2.OpenerDirector)。正常情況下
我們一直使用默認的opener,通過urlopen,但你也可以創建自定義的openersopener使用操作器(handlers)。所有的重活都交給這些handlers來做。每一個handler知道
怎麼打開url以一種獨特的url協議(httpftp等等),或者怎麼處理打開url的某些方面,如,HTTP重定向,或者HTTP cookie

你將會創建openers如果你想要用安裝特別的handlers獲取url,例如,獲取一個處理cookieopener,或者一個不處理重定向的opener

枚舉一個OpenerDirector,然後多次調用.add_handler(some_handler_instance)來創建一個opener
或者,你可以用build_opener,這是一個很方便的創建opener對象的函數,它只有一個函數調用。build_opener默認會加入許多
handlers
,但是提供了一個快速的方法添加更多東西和/或使默認的handler失效。
其他你想要的handlers能夠處理代理,authentication和其他平常但是又有些特殊的情況。
install_opener
能被用於創建一個opener對象,(全局)默認的opener。這意味着調用urlopen將會用到你剛安裝的opener
opener
對象有一個open方法,它可以被直接調用來獲取url以一種和urlopen函數同樣的方式:沒有必要調用install_opener,除非是爲了方便。

Basic Authentication:(基本驗證)

爲了解釋創建和安裝一個handler,我們將會使用 HTTPBasicAuthHandler。更多關於這個東西的內容和詳細討論包括一個Basic Authentication如何工作的解說參見 Basic Authentication Tutorial.

當需要Authentication的時候,服務器發送一個頭(同時還有401代碼)請求Authentication。它詳細指明瞭一個Authentication和一個域。這個頭看起來像:

Www-authenticate: SCHEME realm=”REALM”.
e.g.
Www-authenticate: Basic realm=”cPanel Users”

客戶端然後就會用包含在頭中的正確的帳戶和密碼重新請求這個域。這是基本驗證。爲了簡化這個過程,我們可以創建一個
HTTPBasicAuthHandler
opener的實例來使用這個handler
HTTPBasicAuthHandler
用一個叫做密碼管理的對象來處理url和用戶名和密碼的域的映射。如果你知道域是什麼(從服務器發送的authentication
頭中),那你就可以使用一個HTTPPasswordMgr。多數情況下人們不在乎域是什麼。那樣使用HTTPPasswordMgrWithDefaultRealm就很方便。它
允許你爲一個url具體指定用戶名和密碼。這將會在你沒有爲一個特殊的域提供一個可供選擇的密碼鎖時提供給你。我們通過提供None作爲add_password方法域的參數指出
這一點。
最高級別的url是需要authentication的第一個url。比你傳遞給.add_password()url更深的url同樣也會匹配。

# create a password manager
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
# Add the username and password.
# If we knew the realm, we could use it instead of “None“.
top_level_url = “http://example.com/foo/”
password_mgr.add_password(None, top_level_url, username, password)

handler = urllib2.HTTPBasicAuthHandler(password_mgr)
# create “opener” (OpenerDirector instance)
opener = urllib2.build_opener(handler)
# use the opener to fetch a URL
opener.open(a_url)
# Install the opener.
# Now all calls to urllib2.urlopen use our opener.
urllib2.install_opener(opener)

注意:在以上的示例中我們只給build_opener提供了HTTPBasicAuthHandler。默認opener有對普通情況的操作器handlers- ProxyHandler, UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor.
高級別url實際上是一個完整的url(包括http:協議組件和主機名可選的端口號),如”http://example.com”或者是一個授權(同樣,主機名,可選的端口號)
”"example.com”  “example.com:8080″(後一個示例包含了一個端口號)。授權,如果被呈現,一定不能包含用戶信息-”oe@password:example.com”
是不正確的、
代理:
urllib2
將會自動檢測你的代理設置並使用它們。這是通過 ProxyHandler實現的,它是操作器鏈的一部分。正常情況下,這是個好東西,但是也有它不那麼有用的偶然情況。
一個做這些的方法是安裝我們自己的ProxyHandler,不用任何定義任何代理。使用一個和建立Basic Authentication操作器相似的步驟可以實現:

>>> proxy_support = urllib2.ProxyHandler({})
>>> opener = urllib2.build_opener(proxy_support)
>>> urllib2.install_opener(opener)
注意:
目前urllib2不支持通過代理獲取HTTPs位置。這是一個問題。
sockets
layers
python
支持獲取層疊的網頁的源碼。urllib2使用httplib library,而httplib library反過來使用socket library
對於python2.3你可以指明一個socket應該在超時之前等待response多久。這在這些不得不獲取網頁的應用中很有用。默認socket模塊沒有超時而且能夠掛起。
目前,socket超時在urllib2或者httplib水平中不可見。然而,你可以全局地爲所有socket設置默認的超時。

import socket
import urllib2
# timeout in seconds
timeout = 10
socket.setdefaulttimeout(timeout)
# this call to urllib2.urlopen now uses the default timeout
# we have set in the socket module
req = urllib2.Request('http://www.voidspace.org.uk')

response = urllib2.urlopen(req) 

http://hi.baidu.com/han_life/blog/item/8677342693411504918f9d9b.html 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章