如何使用 urllib 包獲取網路資源

鴨梨山大發表於2015-05-10

簡介

相關文章

你還可以在這篇文章中找到對使用Python獲取網路資源有幫助的資訊:

urllib.request是一個用於獲取URL(Uniform Resource Locators)的Python模組。它提供的介面(以urlopen函式的形式)非常簡單。它可以用不同的協議去獲取URL。同時它還提供了稍微複雜些的介面,讓我們能在一些常見的場景下使用,如基礎認證,Cookies,代理等等。這些介面是通過handler和opener物件來提供的。

urllib.request通過相關的網路協議(例如FTP,Http)支援多種“URL模式”下(以URL中的冒號之前的字串識別,例如“ftp”是“ftp://python.org/”的 URL模式)的URL獲取。本篇教程重點放在最常見的場景中,即Http。

urlopen在簡單的場景中極易使用。然而當你在開啟Http URL的時候遇到錯誤或是不正常的情況時,你將會需要一些超文字傳輸協議的知識。最全面且最權威的Http參考文是RFC 2616。但這不是一個通俗易懂的技術文件。本篇HOWTO意在講述urllib的使用方法,輔以足夠的Http細節去幫助你理解。本文並不是 urllib.request 文件的替代, 而是一個補充。

URL的獲取

urllib.request最簡單的用法如下:

如果你想通過URL獲取一個資源並儲存在某個臨時的空間,你可以通過urlretrieve() 函式去實現:

urllib的許多用法就是這麼簡單(注意,除了“http”,我們還以使用以“ftp”,“file”等開頭的URL)。無論如何,本教程的目的在於講解更復雜的案例,且重點放在Http的案例上。

Http基於請求和響應——客戶端作出請求而伺服器傳送響應。urllib.request通過Request物件對映了你正在做的Http請求。建立最簡單的Request物件例項 ,你只需要指定目標URL。呼叫urlopen並傳入所建立的Request例項,將會返回該URL請求的response物件。該response物件類似於file,這意味著你可以在它上面呼叫.read()

應該注意到urllib.request使用了同樣的Request的介面去處理所有的URL模式。比如,你可以像這樣做一個FTP請求:

在Http的案例中,Request物件可以做兩樣額外的事情。首先,你可以傳入要發給伺服器的資料。其次,你可以傳入額外的關於資料或關於該請求本身的資訊(“後設資料”)給伺服器端——這些資訊會作為Http的“headers”傳輸。接下來讓我們依次來了解他們。

Data

有時你會想要向一個URL傳輸資料(通常這裡的URL指的是一個CGI(Common Gateway Interface公共閘道器介面)指令碼或是其他的網路應用)。在Http裡,這常常是通過POST請求所完成的。這也是當你填好一個頁面中的HTML表單並提交時,你的瀏覽器所做之事。但並不是所有的POST請求都是來自表單:你可以在你自己的網路應用裡用POST請求去傳送任意資料。通常在HTML表單中,資料需要以標準方式編碼然後通過data引數傳給Request物件。一般會使用 urllib.parse 庫來進行編碼。

注意,有時候我們也會需要到其他型別的編碼(比如,通過HTML表單上傳檔案 —— 點選 HTML規範, 表單的提交 瞭解更多細節).

如果你不給data引數傳值,urllib將使用GET請求。GET請求和POST請求之間的一個不同點在於,POST請求通常有“副作用”:它們在某種意義上改變了系統的狀態(例如給網站下一個訂單,要求送100斤的豬肉罐頭到你家門口)。雖然Http標準聲稱POST請求很有可能造成副作用,同時GET請求從不造成副作用,但是GET請求仍可能產生副作用,POST請求也不一定就會造成副作用。在Http GET請求裡,資料也可以被編碼進URL。

實現方法:

注意:完整的URL是在原URL後面加上 ?以及編碼的結果而生成的。

Headers

我們下面將會討論一個具體的Http header, 向大家展示怎麼向你的Http請求新增header。

一些網站 [1] 不喜歡被程式訪問,也不喜歡匹配多種瀏覽器 [2]。預設情況下,urllib將自己設定為Python-urllib/x.y (這裡 xy 分別是Python的主要和次要版本號, 如Python-urllib/2.5), 這會把網站搞糊塗,或者乾脆就不能正常執行。 瀏覽器通過 User-Agent header [3]來定位自己。建立一個Request 物件時你可以傳入一個header的dictionary。 下面的例子建立的請求跟之前的一樣,唯一不同的地方是該請求將自己標為IE瀏覽器的某個版本 [4]

響應也有兩個有用的方法。在我們待會了解出現了問題時的情形後,可以看這一節info and geturl 去學習這兩個方法。

處理異常

urlopen 會在它無法處理一個響應時丟擲 URLError (Python的API也常常這樣丟擲內建的異常,如 ValueError, TypeError等等)。 HttpErrorURLError的子類,在Http URL的一些情況中會被丟擲。 這些異常類來自urllib.error 模組。

e.g. 通常URL錯誤的產生原因是沒有網路連線(即沒有到達指定伺服器的路由),或是指定的伺服器不存在。這時,被丟擲的異常將會有“reason”屬性。該屬性是一個包含錯誤碼及錯誤文字的元組(tuple)。如:

Http錯誤

每個來自伺服器的Http響應都有一個數字的“狀態碼”。狀態碼有時會表示伺服器無法實現請求。預設的handler將會幫你處理這樣的一些響應(例如,如果響應是一個要求客戶端從另外的URL獲取文件,即“重定向(redirection)”,urllib會幫你處理它)。而無法被處理的響應,HttpError將會被urlopen丟擲。常見的錯誤包括“404”(page not found無法找到頁面), “403”(request forbidden請求被禁止), 和“401” (authentication required需要驗證)。

欲瞭解Http錯誤碼,請閱讀RFC2616的第十節。

被丟擲的HttpError例項會有一個整數的“code”屬性,對應伺服器傳送的錯誤。

Error Codes

由於預設的handler會處理重定向(300範圍內的程式碼),並且100-299範圍的程式碼表示成功,所以你一般只會看到400-599範圍內的錯誤碼。 http.server.BaseHttpRequestHandler.responses是一個很有用的響應程式碼字典,它包含了RFC2616裡所用到的全部響應碼。下面是該字典的重現:

當一個錯誤被伺服器響應丟擲時,返回一個Http 錯誤碼一個錯誤頁面。你可以使用HttpError例項作為一個返回頁面中的響應。這意味著同code屬性,它還有read, geturl, 和info方法,如urllib.response模組返回的一樣:

小結

如果你想為HttpErrorURLError作好準備,這裡有兩個方法。我個人推薦第二個。

第一個

注意: except HttpError 一定要放在前面, 不然except URLError 會獲取HttpError

第二個

info and geturl

urlopen (或HttpError 例項)返回的響應有兩個有用的方法, info()geturl() ,定義在urllib.response模組中.

geturl – 它返回獲取的頁面的真實URL。在urlopen (或使用的opener物件) 可能帶有一個重定向時,它很有幫助。 獲取的頁面的URL不一定跟請求的URL相同。

info – 它返回一個字典-就像一個物件,用於描述獲取的頁面,特別是伺服器傳送的header。它是一個http.client.HttpMessage 例項。常見的header有‘Content-length’, ‘Content-type’等等。點選Quick Reference to Http Headers 檢視Http header列表,內含各個header的簡單介紹和用法。

Openers and Handlers

當你需要獲取一個URL時,你需要一個opener(一個看起來不太容易理解的物件urllib.request.OpenerDirector的例項)。一般情況下我們都會通過urlopen來使用預設的opener,但是你可以自己建立不同的opener。Opener會使用handler。所有的“重活”由handler去承擔。每個handler知道如何去以某個特定的URL模式(http,ftp等等)開啟URL,或是如何處理URL啟動的某個方面,比如Http重定向或Http cookie。

如果你想用某個已建立的handler去獲取URL,你需要建立opener,例如一個處理cookie的opener,或是一個不處理重定向的opener。

要建立一個opener,你需要初始化一個OpenerDirector,然後重複呼叫.add_handler(some_handler_instance)。或者,你可以使用build_opener,一個便利的建立opener的函式,只需呼叫一次該函式便可建立opener。build_opener預設新增了一些handler,但是提供了便捷的途徑去新增和/或重寫預設的handler。

如果你想知道的話,還有其他種類的handler可以適用於代理,驗證以及其他常見但又有些特殊的情形。

install_opener 可以用來建立一個opener物件,即(全域性)預設opener。這意味著urlopen將會使用你建立的opener。

Opener物件有一個open方法,可以直接用來像urlopen一樣去獲取URL:除非更方便,否則沒必要呼叫install_opener

基本驗證

為了演示一個handler的建立和設定,我們將用到HttpBasicAuthHandler。想了解更多關於這方面的細節——包括基本驗證是如何執行的——請看Basic Authentication Tutorial。當需要驗證時,伺服器會傳送一個header(同時還有401錯誤碼)來請求驗證。這將會指定驗證方案以及一個“realm”。Header看起來是這樣的。

客戶端接著應該用正確的使用者名稱和密碼進行重新請求(請求的header中包含realm)。這就是“基本驗證”。為了簡化這個過程,我們可以建立一個HttpBasicAuthHandler例項和一個使用該handler的opener。

HttpBasicAuthHandler使用一個叫password manager的物件去處理URL和realm,密碼和使用者名稱之間的對映。如果你知道realm是什麼(根據伺服器發來的驗證header),那麼你就可以使用HttpPasswordMgr。通常人們不會關心realm是什麼。在這種情況下,使用HttpPasswordMgrWithDefaultRealm會很方便。它允許你指定一個URL預設的使用者名稱和密碼。它會在你沒有給某個realm提供使用者名稱和密碼的時候起到作用。實現這種情況,我們需要將add_password 方法的realm引數設定為None

最上層的URL是第一個要求驗證的URL。只要是比你傳給.add_password()的URL“更深”的URL都可以匹配上。

注意在上面的例子中,我們只提供HttpBasicAuthHandler給build_opener。預設的情況下,opener有處理普通情況的handler —— ProxyHandler(如果代理的環境變數如http_proxy已經設定的話),UnknownHandler, HttpHandlerHttpDefaultErrorHandler,HttpRedirectHandlerFTPHandlerFileHandlerDataHandlerHttpErrorProcessor。

toplevel_url實際上要麼是一個完整的URL(包括“Http:”模式部分以及主機名和可選的埠號)比如“http://example.com/” ,要麼是一個“主體”(即主機名和可選的埠號)例如“example.com”或“example.com:8080”(後者包含了埠號)。該主體如果出現的話,不能包含“使用者資訊”部分——如“joe@password:example.com”就是不對的。

代理

urllib 將會自動檢測你的代理設定並使用它們。這是通過ProxyHandler來實現的,當代理設定被檢測到時,它是普通handler鏈的一部分。通常來說這是好事,但是它不一定會帶來幫助[5]。一個不用定義代理的實現方式是建立我們自己的ProxyHandler。這個實現方法類似於建立一個基本認證handler:

請注意目前urllib.request不支援通過代理獲取https位置。然而,這可以通過擴充套件urllib.request來實現,見[6]

Sockets and Layers

Python支援從多層級網頁中獲取資源。urllib使用http.client庫,而該庫使用了socket庫。在Python2.3中,你可以指定一個socket等待響應的時間。這對於需要獲取網頁的一些應用來說很有用。預設情況下,scoket模組沒有超時時間的設定而是一直掛著。目前socket超時在http.clienturllib.request層是隱藏的。然後你可以將所有socket的預設超時設定成全域性的,方法是:

註腳

本篇文章由John Lee審閱和修改。

[1] Like Google for example. The proper way to use google from a program is to use PyGoogle of course.
[2] Browser sniffing is a very bad practise for website design – building sites using web standards is much more sensible. Unfortunately a lot of sites still send different versions to different browsers.
[3] The user agent for MSIE 6 is ‘Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)’
[4] For details of more Http request headers, see Quick Reference to Http Headers.
[5] In my case I have to use a proxy to access the internet at work. If you acodeempt to fetch _localhost URLs through this proxy it blocks them. IE is set to use the proxy, which urllib picks up on. In order to test scripts with a localhost server, I have to prevent urllib from using the proxy.
[6] urllib opener for SSL proxy (CONNECT method): ASPN Cookbook Recipe.

相關文章