1. 程式人生 > >Python之美[從菜鳥到高手]--urllib原始碼分析

Python之美[從菜鳥到高手]--urllib原始碼分析

    urllib提供了較好的封裝,可以很方便的讀取http,ftp,file等協議資料,本篇只關注http。urllib的底層還是使用httplib模組,相比於httplib,urllib介面更加好用,功能更加強大。支援http代理,可從環境變數中獲取代理資訊,支援http basic auth,可自動處理302等。但也有不足,如不支援gzip等壓縮編碼,不支援摘要認證,NTML認證等。

urllib快速使用

urllib的urlopen方法很好用,代理使用如下:

import urllib
opener=urllib.urlopen('http://www.baidu.com/',proxies={'http':'http://root:
[email protected]
:808'}) print opener.code
urlopen返回一個類檔案物件,具有一些列方法,read(),readline(),readlines(),close()等,其中info()方法和readhers屬性都是返回http.HTTPMessage例項。HTTPMessage是繼承自mimetools.Message,具有讀取狀態碼或郵件頭這樣格式的內容。
注意:http://www.baidu.com/需要在域名後加"/",不然會報"IOError: ('http protocol error', 0, 'got a bad status line', None)”,且代理站點需要新增http字首。
urllib還有一個比較好用的下載方法,urlretrieve(url, filename=None, reporthook=None, data=None)該函式可指定回撥函式reporthook(blocknum, bs, size),預設下載1024*8位元組回撥一次,也就是bs大小,blocknum是塊數量,其實就是回撥的次數,size是下載檔案總大小,通過"Content-Length"獲取。data是需要post的值,filename是下載檔名。返回一個(本地filename,HTTPMessage)元組
import urllib
def reporthook(bk,bs,total):
    print bk*bs,'b'
filename,message=urllib.urlretrieve("http://ww.baidu.com/",None,reporthook)
print message.getheader('Content-Length')
返回結果:
8192 b
16384 b
11394
你會看到大小不太一樣,難免的,因為最後一次可能沒有bs大小可讀了,但回撥還是呼叫了。urlretrieve是基於URLopener.retrieve的,在看當作也遇到了一點疑惑,在獲取“Content-Length"頭資訊時,程式碼如下:
fp = self.open(url, data)
headers = fp.info()
if "content-length" in headers:
    size = int(headers["Content-Length"])
看的我疑惑了,這headers到底是什麼型別啊,怎麼那麼像字典,還能自動處理大小寫?
原來這是rfc822.Message例項,定義了__contains__和__getitem__,果然原始碼之下,沒有祕密可言。
class Message:
    def __contains__(self, name):
        """Determine whether a message contains the named header."""
        return name.lower() in self.dict
        
    def __getitem__(self, name):
        """Get a specific header, as from a dictionary."""
        return self.dict[name.lower()]

urllib原始碼分析

1.總體流程

  urlopen其實是urllib.FancyURLopener的例項,且可全域性快取,避免了多次建立。FancyURLopener是繼承自urlib.URLopener的。FancyURLopener更好的處理了一些特殊狀態情況,如302,401,407等,如支援最大10次302跳轉。而URLopener出現302等會直接報IOError狀態碼異常,如果為了獲取真實返回狀態碼需要用URLopener,URLopener通樣支援代理等基本特性,如:
當出現狀態碼異常時,URLopener將呼叫http_error方法,如果存在“ 'http_error_%d' % errcode”方法的話,http_error將呼叫。FancyURLopener定義了較多的特殊狀態碼處理函式,如http_error_302方法。所以說,如果想定製自己的urllib可以通用繼承於URLopener或FancyURLopener,並定義自己的狀態碼處理函式。

2.http basic auth(基本認證)

這是很簡單的一種認證方式,每次訪問通過base64加密將"使用者名稱:密碼”作為“Authorization”頭髮送,並沒有作為cookie。下面是我簡化的請求-響應過程資訊
GET / HTTP/1.1
Host: 127.0.0.1

HTTP/1.1 401 Authorization Required
WWW-Authenticate: Basic realm="my site"

----------------------------------------------------------

GET / HTTP/1.1
Host: 127.0.0.1
Authorization: Basic cm9vdDpyb290

HTTP/1.1 200 OK
我們可以簡單配置一下apache,修改httpd.conf,在需要認證的的directory下,新增AllowOverride authconfig,
<Directory "c:/wamp/www/">
    AllowOverride authconfig
    Order Deny,Allow
    Deny from all
    Allow from 127.0.0.1
</Directory>
然後在c:/wamp/www/目錄下新建.htaccess,我的如下
authname "my site"
authtype basic
authuserfile c:/wamp/www/user.txt
require valid-user
其中authname也就是提示資訊,會出現在“WWW-Authenticate”頭的“realm"(以前一直很奇怪,realm到底是什麼?)。authuserfile最好絕對路徑。
我們知道了認證方式,那麼編碼也就簡單了。
user_passwd, realhost = splituser(realhost) #realhost類似root:[email protected]
if user_passwd:
    import base64
    auth = base64.b64encode(user_passwd).strip()
    else:
        auth = None
    h = httplib.HTTP(host)
    if auth: h.putheader('Authorization', 'Basic %s' % auth)
當url中沒有提供使用者名稱:密碼時,但站點的確需要認證時,將返回401狀態碼,這時將呼叫http_error_401,如果出現"www-authenticate"響應頭,將判斷是否是basic認證。如果是將呼叫retry_http_basic_auth,這個函式從快取中尋找有無該站點的使用者名稱:密碼,如果沒有將呼叫prompt_user_passwd,提示使用者輸入(如果在GUI環境下需要重寫該函式),並將使用者名稱:密碼快取,如下

第二次請求是不需要使用者名稱和密碼的,使用者名稱密碼快取在auth_cache字典中。(PS:寫程式碼時快取思想很重要)

3.代理

    代理其實和http basic認證一樣,都是通過傳送請求頭來實現的,下面是我使用工具CCProxy設定本地代理後擷取的請求--響應,如下:
GET / HTTP/1.1
Host: www.baidu.com


HTTP/1.0 407 Unauthorized
Server: Proxy
Proxy-Authenticate: Basic realm="CCProxy Authorization"


GET / HTTP/1.1
Host: www.baidu.com
Proxy-Authorization: Basic cm9vdDpyb290Mg==

HTTP/1.1 200 OK
第一次訪問百度,由於設定了代理,所以返回了407狀態碼,”其中Basic realm“就是網頁認證對話標題。如果代理需要”使用者名稱:密碼“認證,和http基本認證一樣,每次都將”使用者名稱:密碼“base64加密作為”Proxy-Authorization“。URLopener建構函式如下:
def __init__(self, proxies=None, **x509):
        if proxies is None:
            proxies = getproxies()
        assert hasattr(proxies, 'has_key'), "proxies must be a mapping"
        self.proxies = proxies
我們看到,如果沒有設定代理,將呼叫getproxies()函式獲取本地環境變數中的代理。
def getproxies_environment():
    proxies = {}
    for name, value in os.environ.items():
        name = name.lower()
        if value and name[-6:] == '_proxy':
            proxies[name[:-6]] = value
    return proxies
本地環境變數中代理一般設定如下,如在linux中.bashrc設定
export http_proxy="16.111.53.167:808"
export ftp_proxy="16.111.53.167:328"
如果設定代理,將向代理主機發送請求,其餘和HTTP Basic認證相同。

urllib提供的有用函式

1. quote(s, safe = '/')   url編碼,將保留字元編碼為%20格式,主要用來編碼path前面資訊

    其實這個函式主要用來給path,query編碼用的,如果將整個url編碼肯定有問題的, 因為預設只有數字+字母+'_.-/'不編碼,如果需要傳遞整個url,可將第二個引數設定為"%/:=&?~#+!$,;'@()*[]|",其實urllib會主動編碼的,程式碼如下:
fullurl = quote(fullurl, safe="%/:=&?~#+!$,;'@()*[]|")

增補:從python2.6版本才會主動編碼,2.5以及低版本不會

2. unquote(s):   quote的逆過程。

3. quote_plus(s, safe = ''):  query字串編碼,與quote不同點就是將“空格”用“+"表示,主要用於urlencode函式中

4. unquote_plus(s):   quote_plus的逆過程。

5.urlencode(query,doseq=0) :   將2元素的序列或字典編碼成查詢字串,doseq允許第二元素也可以為序列,query字串都是用quote_plus編碼。


注意:這裡有必要對quote,quote_plus和urlencode做個說明

quote用於url的編碼,而quote_plus用於post的data編碼,所以而urlencode內部是用quote_plus實現的,所以urlencode一般只用做post data使用,get時不用。

官方文件說明:

urllib.unquote(string)
Replace %xx escapes by their single-character equivalent.

Example: unquote('/%7Econnolly/') yields '/~connolly/'.

urllib.unquote_plus(string)
Like unquote(), but also replaces plus signs by spaces, as required for unquoting HTML form values.

urllib.urlencode(query[, doseq])
Convert a mapping object or a sequence of two-element tuples to a “percent-encoded” string, suitable to pass to urlopen() above as the optional data argument. This is useful to pass a dictionary of form fields to a POST request. The resulting string is a series of key=value pairs separated by '&' characters, where both key and value are quoted using quote_plus() above. When a sequence of two-element tuples is used as the query argument, the first element of each tuple is a key and the second is a value. The value element in itself can be a sequence and in that case, if the optional parameter doseq is evaluates to True, individual key=value pairs separated by '&' are generated for each element of the value sequence for the key. The order of parameters in the encoded string will match the order of parameter tuples in the sequence. The urlparse module provides the functions parse_qs() and parse_qsl() which are used to parse query strings into Python data structures.

6 .url2pathname:   將file協議url轉換為本地路徑

    以windows來說,file://host/dirs...,如何是本地可省略host,如     看幾個簡單結果:        7. urllib提供了一些列解析url各部分的函式,和urlparse不同的是基本上都是用正則匹配的。如獲取協議的splittype函式,注意_typeprog正則只在第一次的編譯,如果有類似情況可以仿寫。不過,話說回來,如果用的比較多,並不在意匯入該模組時花費一點時間編譯正則物件,完全可以在全域性編譯。
_typeprog = None
def splittype(url):
    """splittype('type:opaquestring') --> 'type', 'opaquestring'."""
    global _typeprog
    if _typeprog is None:
        import re
        _typeprog = re.compile('^([^/:]+):')

    match = _typeprog.match(url)
    if match:
        scheme = match.group(1)
        return scheme.lower(), url[len(scheme) + 1:]
    return None, url

相關推薦

Python[高手]--urllib原始碼分析

    urllib提供了較好的封裝,可以很方便的讀取http,ftp,file等協議資料,本篇只關注http。urllib的底層還是使用httplib模組,相比於httplib,urllib介面更加好

Python[高手]--讀"一道面試題看 HashMap 的儲存方式"的聯想

在 HashMap 中存放的一系列鍵值對,其中鍵為某個我們自定義的型別。放入 HashMap 後,我們在外部把某一個 key 的屬性進行更改,然後我們再用這個 key 從 HashMap 裡取出元素,這時候 HashMap 會返回什麼?如何面試者直接答“這要看自定義型別的ha

Python[高手]--一步一步動手給Python寫擴充套件(愛初體驗)

    一直對Python擴充套件很感興趣,剛好看到了Extending and Embedding the Python Interpreter文件,看的是最低版本(由於工作中用的是2.x, ̄□ ̄),官方文件    我使用的IDE是Code::Blocks 12.11,

Python[高手]--Python垃圾回收機制及gc模組詳解

    Python中的垃圾回收是以引用計數為主,標記-清除和分代收集為輔。引用計數最大缺陷就是迴圈引用的問題,所以Python採用了輔助方法。本篇文章並不詳細探討Python的垃圾回收機制的內部實現,而是以gc模組為切入點學習Python的垃圾回收機制,如果想深入可以讀讀

Java[高手演變]字符串

tween gin new 有關 菜鳥 article user 再看 use 一、String 1、String簡介 初始化: 一般由String聲明的字符串,長度是不可變的,這也是它與StringBuffer和StringBuilder最直觀的一個區別。一般初始化方式:

Java[高手演變]集合類【吐血推薦!講得太好了!!!】

source: http://blog.csdn.net/zhangerqing/article/details/8122075 最近在找工作,目前還沒有定下來,拿到了一個公司的offer,不過被當白菜了,正在商量薪資方面的事情。隨著百度面試的失敗,夢想再次破滅

Java[高手演變]spring框架初識

初識輕量級Java開源框架 --- Spring作者:eggspring是一個輕量級Java框架,其核心思想就是DI(Dependency Injection,即依賴注入)和IoC(Inversion of Control,即控制反轉),因為其開源、低侵入性,現在已經席捲了很大一部分市場,其最大競爭對手乃是J

Java[高手演變]設計模式

轉自:http://blog.csdn.net/zhangerqing/article/details/8194653     設計模式(Design Patterns)                                   ——可複

Java[高手演變]Java

轉載自:http://blog.csdn.net/zhangerqing/article/details/8731044 Java面試複習提綱 作者:egg 郵箱:[email protected] 微博:http://weibo.com/xtfggef 部落格:http

Java[高手演變]資料結構基礎、線性表、棧和佇列、陣列和字串

Java面試寶典之資料結構基礎 —— 線性表篇作者:egg郵箱:[email protected]這部分內容作為計算機專業最基礎的知識,幾乎被所有企業選中用來作考題,因此,本章我們從本章開始,我們將從基礎方面對資料結構進行講解,內容主要是線性表,包括棧、佇列、陣列、

Java[高手演變]系列博文閱讀導航

隨著博文越來越多,為部落格新增一個導航很有必要!本部落格將相繼開通Java、CloudFoundry、Linux、Ruby等專欄,都會設立目錄,希望讀者朋友們能更加方便的閱讀!在閱讀的過程中有任何問題,請聯絡:egg。QQ群:169480361(請在本博文下面留言,驗證資訊為

Java[高手演練]Linux篇——壓縮及解壓縮命令tar的使用

-z :是否同時具有 gzip 的屬性?亦即是否需要用 gzip 壓縮-j :是否同時具有 bzip2 的屬性?亦即是否需要用 bzip2 壓縮-v :壓縮的過程中顯示檔案!這個常用,但不建議用在背景執行過程-f :使用檔名,請留意,在 f 之後要立即接檔名喔!不要再加引數   例如使用『 tar -zcvf

Java[高手演變]JVM記憶體管理及垃圾回收

很多Java面試的時候,都會問到有關Java垃圾回收的問題,提到垃圾回收肯定要涉及到JVM記憶體管理機制,Java語言的執行效率一直被C、C++程式設計師所嘲笑,其實,事實就是這樣,Java在執行效率方面確實很低,一方面,Java語言採用面向物件思想,這也決定了其必然是開發效

Java[高手演練]ThreadLocal原理分析

作者:二青簡介早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。當使用ThreadLocal維護變數時,ThreadLocal為

Java[高手演練]初識Hadoop

初識Hadoop這是一篇轉載的文章,閱讀原文請點選:原文地址Hadoop一直是我想學習的技術,正巧最近專案組要做電子商城,我就開始研究Hadoop,雖然最後鑑定Hadoop不適用我們的專案,但是我會繼續研究下去,技多不壓身。《Hadoop基礎教程》是我讀的第一本Hadoop書

演算法[高手演練]一些個小演算法

1、10000以內的完數/* * 問題描述:求10000以內的完數。 * 完數即:一個數等於它的不同因子的和 * 如6=1+2+3.8!=1+2+4 * xtfggef 2012/5/16 */ #include<iostream> #include<fst

python學習如何到老手

程序 一個 tro style python 菜鳥 全局 好的 模塊   好的代碼,一定是重構出來的! 先讓程序能跑起來 先初步重構代碼 增加註釋 完成程序後,考慮異常,讓程序健壯起來 把全局變量,單獨提到一個配置文件中 增加一些測試用例 增加日誌模塊 性能上的優化 第三

自學Python路--入門篇:爬蟲

第一次寫部落格&第一次自學Python&第一次實戰 Hi,親們,本部落格只是個人瞎寫著記錄的: 作為已經做BI工程師三年的人竟然第一次接觸Python,自學Python 哎~~是不是很晚呢 畢業第一年進入SAP BW模組 第二年已經差不多開

Python高手》已經出版,購買送視頻課程

RoCE 老師 water 知識結構 如果 ESS 套餐 mage 14. 好消息,《Python從菜鳥到高手》已經出版!!! ??JetBrains官方推薦圖書!JetBrains官大中華區市場部經理趙磊作序!送2400分鐘同步視頻課程!500個案例,400道Pytho

Python高手(3):聲明變量

ble href edi 一個 android 聲明變量 字符串類型 的人 重要 變量(variable)是Python語言中一個非常重要的概念。變量的主要作用就是為Python程序中的某個值起一個名字。類似於“張三”、“李四”、“王二麻子”一樣的人名,便於記憶。 ??在P